/// <summary> /// Removes validation errors for fields that the user wasn't able to edit anyway, /// based on information provided by <paramref name="fieldsInfo"/> /// </summary> public static void RemoveIgnoredErrors(this Controller controller, ModelFieldsAccessibility fieldsInfo) { controller.ModelState .Select(pair => pair.Key) .Where(path => fieldsInfo.IgnoredValidationPaths.Any(path.StartsWith)) .ToList() // Break reliance on ModelState, otherwise "Collection was modified; enumeration operation may not execute" .ForEach(path => controller.ModelState.Remove(path)); }
/// <summary> /// Renders the editor template, controlled by <paramref name="fieldsInfo"/>. /// Does nothing if this field is not set to be shown in <paramref name="fieldsInfo"/>. /// </summary> public static MvcHtmlString EditorFor <TModel, TValue>( this HtmlHelper <TModel> html, Expression <Func <TModel, TValue> > expression, ModelFieldsAccessibility fieldsInfo ) { return(DoFieldsInfoAwareEditor( html, expression, fieldsInfo, () => html.EditorFor(expression) )); }
public ActionResult NewCourseRep(ElectionForm form) { ModelFieldsAccessibility fieldsInfo = NewDefaultFieldsInfo; // Ignore stuff that isn't supposed to be in new election fieldsInfo.ReplaceUneditableWithOldValues(form, new ElectionForm()); this.RemoveIgnoredErrors(fieldsInfo); if (ModelState.IsValid) { Election election = Mapper.Map <Election>(form); election.Type = ElectionType.CourseRepresentative; election.PositionGenerationInProcess = true; ElectionStateChange createChangeInfo = new ElectionStateChange { Election = election, PreviousState = null, TargetState = election.State, IsCausedByUser = true, InstigatorUsername = User.Identity.GetUserId(), CompletedAt = DateTime.Now }; db.Elections.Add(election); db.ElectionStateChanges.Add(createChangeInfo); using (DbContextTransaction transaction = db.Database.BeginTransaction()) { db.SaveChanges(); // This needs to be after the first big transaction - otherwise EF gets confused about order of actions election.PopulateAutomaticStateTransitions(); db.SaveChanges(); transaction.Commit(); } AuditLogManager.RecordNewElection(createChangeInfo); // Schedule the job to loop over the DB to generate the positions BackgroundJob.Enqueue(() => GeneratePositions.Execute(election.Id, JobCancellationToken.Null)); return(RedirectToAction("Details", new { id = election.Id })); } ViewData[FormConstants.FieldsInfoKey] = fieldsInfo; return(View(form)); }
public ActionResult NewCouncil(CouncilElectionForm form) { ModelFieldsAccessibility fieldsInfo = NewCouncilFieldsInfo; // Ignore stuff that isn't supposed to be in new election fieldsInfo.ReplaceUneditableWithOldValues(form, new CouncilElectionForm()); this.RemoveIgnoredErrors(fieldsInfo); if (ModelState.IsValid) { Election election = Mapper.Map <Election>(form); election.Type = ElectionType.StudentCouncil; CouncilElectionData councilData = Mapper.Map <CouncilElectionData>(form); councilData.Election = election; ElectionStateChange createChangeInfo = new ElectionStateChange { Election = election, PreviousState = null, TargetState = election.State, IsCausedByUser = true, InstigatorUsername = User.Identity.GetUserId(), CompletedAt = DateTime.Now }; db.Elections.Add(election); db.CouncilElectionData.Add(councilData); db.ElectionStateChanges.Add(createChangeInfo); using (DbContextTransaction transaction = db.Database.BeginTransaction()) { db.SaveChanges(); // This needs to be after the first big transaction - otherwise EF gets confused about order of actions election.PopulateAutomaticStateTransitions(); db.SaveChanges(); transaction.Commit(); } AuditLogManager.RecordNewElection(createChangeInfo); return(RedirectToAction("Details", new { id = election.Id })); } ViewData[FormConstants.FieldsInfoKey] = fieldsInfo; return(View(form)); }
private static MvcHtmlString DoFieldsInfoAwareEditor <TModel, TValue>( HtmlHelper <TModel> html, Expression <Func <TModel, TValue> > expression, ModelFieldsAccessibility fieldsInfo, Func <MvcHtmlString> displayDelegate ) { string property = html.PropertyName(expression); if (!fieldsInfo.IsVisible(property)) { return(MvcHtmlString.Empty); } ViewDataDictionary viewData = html.ViewDataContainer.ViewData; List <string> dataKeysToRemove = new List <string>(); Dictionary <string, object> dataPrevValues = new Dictionary <string, object>(); // "Hide" current fields info from new editor viewData.PrepareChangeReversal(FormConstants.FieldsInfoKey, dataKeysToRemove, dataPrevValues); viewData.Remove(FormConstants.FieldsInfoKey); // Augment new additionalViewData with information from fieldsInfo if (fieldsInfo.IsComplex(property)) { viewData[FormConstants.FieldsInfoKey] = fieldsInfo.GetSubFieldInfo(property); } else if (!fieldsInfo.CanBeChangedByUser(property)) { viewData.PrepareChangeReversal(FormConstants.DisabledKey, dataKeysToRemove, dataPrevValues); viewData[FormConstants.DisabledKey] = true; } // Render the editor MvcHtmlString result = displayDelegate(); // Reverse the changes to ViewData dataKeysToRemove.ForEach(s => viewData.Remove(s)); dataPrevValues.ForEach(pair => viewData[pair.Key] = pair.Value); return(result); }
public ActionResult Edit(int id) { Election election = db.Elections.Find(id); if (election == null) { return(HttpNotFound()); } CouncilElectionData councilData = null; CouncilElectionForm councilForm = null; ElectionForm form; if (election.Type == ElectionType.StudentCouncil) { councilData = db.CouncilElectionData.First(data => data.ElectionId == election.Id); form = councilForm = GenerateFormForCouncil(election, councilData); } else { form = GenerateFormForCourseRep(election); } ModelFieldsAccessibility fieldsInfo = ElectionLifecycleInfo.GetWhatCanBeEditedCouncil(election); ViewData[FormConstants.FieldsInfoKey] = fieldsInfo; ViewBag.Election = election; fieldsInfo.EnsureAllowedDefaultKind( ModelFieldsAccessibility.Kind.Editable, nameof(AdminElectionsController) + "." + nameof(Edit) ); if (Request.HttpMethod.ToUpper() != "POST") { // Just show the template return(View("Edit", form)); } AntiForgery.Validate(); // Update the form based on data that we received // ReSharper disable once ConvertIfStatementToNullCoalescingExpression - we need the compiler to specify different generic arguments if (councilForm != null) { TryUpdateModel(councilForm); } else { TryUpdateModel(form); } // Get the original form so that we use old values for uneditable fields CouncilElectionForm councilOriginalForm = null; ElectionForm originalForm; if (councilForm != null) { originalForm = councilOriginalForm = GenerateFormForCouncil(election, councilData); } else { originalForm = GenerateFormForCourseRep(election); } // Replace all uneditable values with old ones fieldsInfo.ReplaceUneditableWithOldValues(form, originalForm); // As the role IDs are sent from user, we need to make sure that they weren't changed if (councilForm != null && fieldsInfo.CanBeChangedByUser(nameof(CouncilElectionForm.Roles))) { IEnumerable <int?> initialRoleIds = councilOriginalForm.Roles.Select(role => role.Id); IEnumerable <int?> newRoleIds = councilForm.Roles.Select(role => role.Id); if (!initialRoleIds.SequenceEqual(newRoleIds)) { throw new Exception("The IDs of roles were changed by user input"); } } // Validate again (since some rules are relative to other fields and can be affected by operations above) TryValidateModel(form); // Ignore the failures from uneditable fields this.RemoveIgnoredErrors(fieldsInfo); if (!ModelState.IsValid) { // The validation failed so we just display the form again return(View("Edit", form)); } // Record the admin action AdminActionRecord actionRecord = CreateActionRecord(election, AdminActionRecord.RecordType.Edit); actionRecord.SetFormChangeSet(FormChangeSet.Generate(form, originalForm)); db.AdminActionRecords.Add(actionRecord); // Validation passed with the fields that are allowed to change. Persist the changes Mapper.Map(form, election); if (councilData != null) { Mapper.Map(form, councilData); } db.SaveChanges(); BackgroundJob.Enqueue <SynchronizeDelayedJobsJob>(job => job.Execute(election.Id)); AuditLogManager.RecordElectionEdit(User, election); return(RedirectToAction("Details", new { id })); }
public static ModelFieldsAccessibility GetWhatCanBeEditedCouncil(Election election) { if (!CanEdit(election)) { throw new Exception( $"Cannot call {nameof(GetWhatCanBeEditedCouncil)} when the election isn't editable" ); } bool isCouncil = election.Type == ElectionType.StudentCouncil; // Mark everything editable by default ModelFieldsAccessibility fieldsInfo = isCouncil ? CouncilElectionForm.DefaultCouncilFieldsInfo(ModelFieldsAccessibility.Kind.Editable) : ElectionForm.DefaultFieldsInfo(ModelFieldsAccessibility.Kind.Editable); if (!ShowResultsAdmin(election)) { fieldsInfo.MarkNotShown(nameof(ElectionForm.ResultsText)); } if (election.State == ElectionState.Disabled) { // Everything (expect ResultsText) can be edited return(fieldsInfo); } if (election.State == ElectionState.PreNominations || IsAfter(election, ElectionState.PreNominations)) { if (isCouncil) { fieldsInfo.MarkNotEditable(nameof(CouncilElectionForm.Roles)); } fieldsInfo .GetSubFieldInfo(nameof(ElectionForm.Nominations)) .MarkNotEditable(nameof(ElectionPhaseForm.BeginsAt)); if (election.State == ElectionState.PreNominations) { return(fieldsInfo); } } if (election.State == ElectionState.Nominations || IsAfter(election, ElectionState.Nominations)) { fieldsInfo.MarkNotShown(nameof(ElectionForm.NominationsStartedEmail)); // Eligibility is not refreshed automatically after nominations have started fieldsInfo.MarkNotShown(nameof(ElectionForm.DisableAutomaticEligibility)); if (election.State == ElectionState.Nominations) { return(fieldsInfo); } } if (election.State == ElectionState.PreVoting || IsAfter(election, ElectionState.PreVoting)) { fieldsInfo.MarkNotShown(nameof(ElectionForm.Nominations)); if (isCouncil) { fieldsInfo.MarkNotShown(nameof(CouncilElectionForm.NominationsAlmostOverEmail)); } fieldsInfo.MarkNotShown(nameof(ElectionForm.PostNominationsEmail)); if (election.State == ElectionState.PreVoting) { return(fieldsInfo); } } if (election.State == ElectionState.Voting || IsAfter(election, ElectionState.Voting)) { if (isCouncil) { fieldsInfo.MarkNotShown(nameof(CouncilElectionForm.VotingStartedEmail)); } fieldsInfo .GetSubFieldInfo(nameof(ElectionForm.Voting)) .MarkNotEditable(nameof(ElectionPhaseForm.BeginsAt)); if (election.State == ElectionState.Voting) { return(fieldsInfo); } } if (election.State == ElectionState.Closed || IsAfter(election, ElectionState.Closed)) { if (isCouncil) { fieldsInfo.MarkNotShown(nameof(CouncilElectionForm.Roles)); } fieldsInfo.MarkNotShown(nameof(ElectionForm.Voting)); if (isCouncil) { fieldsInfo.MarkNotShown(nameof(CouncilElectionForm.VotingAlmostOverEmail)); } fieldsInfo.MarkNotShown(nameof(ElectionForm.PostVotingEmail)); if (election.State == ElectionState.Closed) { return(fieldsInfo); } } return(fieldsInfo); }