diff --git a/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs b/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs index bf3586a174..e1b57709f3 100644 --- a/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs +++ b/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs @@ -143,7 +143,7 @@ namespace Umbraco.Web.Macros //bubble up the model state from the main view context to our custom controller. //when merging we'll create a new dictionary, otherwise you might run into an enumeration error // caused from ModelStateDictionary - controller.ModelState.Merge(new ModelStateDictionary(viewContext.ViewData.ModelState)); + controller.ModelState.MergeSafe(new ModelStateDictionary(viewContext.ViewData.ModelState)); controller.ControllerContext = new ControllerContext(request, controller); //call the action to render var result = controller.Index(); diff --git a/src/Umbraco.Web/ModelStateExtensions.cs b/src/Umbraco.Web/ModelStateExtensions.cs index 08868e4e5f..29f4eff120 100644 --- a/src/Umbraco.Web/ModelStateExtensions.cs +++ b/src/Umbraco.Web/ModelStateExtensions.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; @@ -8,24 +7,51 @@ namespace Umbraco.Web { internal static class ModelStateExtensions { + /// + /// Safely merges ModelState + /// + /// + /// + /// The MVC5 System.Web.Mvc.ModelStateDictionary.Merge method is not safe + public static void MergeSafe(this ModelStateDictionary state, ModelStateDictionary dictionary) + { + if (dictionary == null) + return; + // Need to stuff this into a temporary new dictionary that we're allowed to alter, + // if we alter "state" in this enumeration, it fails with + // "Collection was modified; enumeration operation may not execute" + var tempDictionary = new ModelStateDictionary(state); + foreach (var entryKey in dictionary.Keys) + { + tempDictionary[entryKey] = dictionary[entryKey]; + } + // Update state with updated dictionary + state = tempDictionary; + } - /// - /// Merges ModelState that has names matching the prefix - /// - /// - /// - /// - public static void Merge(this ModelStateDictionary state, ModelStateDictionary dictionary, string prefix) + /// + /// Merges ModelState that has names matching the prefix + /// + /// + /// + /// + public static void Merge(this ModelStateDictionary state, ModelStateDictionary dictionary, string prefix) { - if (dictionary == null) - return; - foreach (var keyValuePair in dictionary + if (dictionary == null) + return; + // Need to stuff this into a temporary new dictionary that we're allowed to alter, + // if we alter "state" in this enumeration, it fails with + // "Collection was modified; enumeration operation may not execute" + var tempDictionary = new ModelStateDictionary(state); + foreach (var keyValuePair in dictionary //It can either equal the prefix exactly (model level errors) or start with the prefix. (property level errors) .Where(keyValuePair => keyValuePair.Key == prefix || keyValuePair.Key.StartsWith(prefix + "."))) { - state[keyValuePair.Key] = keyValuePair.Value; + tempDictionary[keyValuePair.Key] = keyValuePair.Value; } - } + // Update state with updated dictionary + state = tempDictionary; + } /// /// Checks if there are any model errors on any fields containing the prefix diff --git a/src/Umbraco.Web/Mvc/ControllerExtensions.cs b/src/Umbraco.Web/Mvc/ControllerExtensions.cs index 03e31f34c4..28626e70fb 100644 --- a/src/Umbraco.Web/Mvc/ControllerExtensions.cs +++ b/src/Umbraco.Web/Mvc/ControllerExtensions.cs @@ -140,7 +140,7 @@ namespace Umbraco.Web.Mvc { //when merging we'll create a new dictionary, otherwise you might run into an enumeration error // caused from ModelStateDictionary - result.ViewData.ModelState.Merge(new ModelStateDictionary(controller.ViewData.ModelState)); + result.ViewData.ModelState.MergeSafe(new ModelStateDictionary(controller.ViewData.ModelState)); // Temporarily copy the dictionary to avoid enumerator-modification errors var newViewDataDict = new ViewDataDictionary(controller.ViewData); diff --git a/src/Umbraco.Web/Mvc/MergeModelStateToChildActionAttribute.cs b/src/Umbraco.Web/Mvc/MergeModelStateToChildActionAttribute.cs index 7d2df43962..28eee44939 100644 --- a/src/Umbraco.Web/Mvc/MergeModelStateToChildActionAttribute.cs +++ b/src/Umbraco.Web/Mvc/MergeModelStateToChildActionAttribute.cs @@ -32,7 +32,7 @@ namespace Umbraco.Web.Mvc { if (filterContext.Controller.ControllerContext.IsChildAction) { - filterContext.Controller.ViewData.ModelState.Merge( + filterContext.Controller.ViewData.ModelState.MergeSafe( filterContext.Controller.ControllerContext.ParentActionViewContext.ViewData.ModelState); } } diff --git a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs index 1c12e7e498..70200c627a 100644 --- a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs +++ b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs @@ -110,7 +110,7 @@ namespace Umbraco.Web.Mvc tempDataDictionary.Save(context, new SessionStateTempDataProvider()); var viewCtx = new ViewContext(context, new DummyView(), new ViewDataDictionary(), tempDataDictionary, new StringWriter()); - viewCtx.ViewData.ModelState.Merge(context.Controller.ViewData.ModelState); + viewCtx.ViewData.ModelState.MergeSafe(context.Controller.ViewData.ModelState); foreach (var d in context.Controller.ViewData) viewCtx.ViewData[d.Key] = d.Value; @@ -124,7 +124,7 @@ namespace Umbraco.Web.Mvc /// private static void CopyControllerData(ControllerContext context, ControllerBase controller) { - controller.ViewData.ModelState.Merge(context.Controller.ViewData.ModelState); + controller.ViewData.ModelState.MergeSafe(context.Controller.ViewData.ModelState); foreach (var d in context.Controller.ViewData) controller.ViewData[d.Key] = d.Value;