Skip to content

Commit

Permalink
U4-6721 Error when submitting Macros, Collection was modified; enumer…
Browse files Browse the repository at this point in the history
…ation operation may not execute. (after project has been updated to MVC5)

#U4-6721 In Progress

(cherry picked from commit 34f44b0)
  • Loading branch information
nul800sebastiaan committed Jul 21, 2015
1 parent 2f714d0 commit aa9b188
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 18 deletions.
2 changes: 1 addition & 1 deletion src/Umbraco.Web/Macros/PartialViewMacroEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public string Execute(MacroModel macro, IPublishedContent content)
//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();
Expand Down
52 changes: 39 additions & 13 deletions src/Umbraco.Web/ModelStateExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
Expand All @@ -8,24 +7,51 @@ namespace Umbraco.Web
{
internal static class ModelStateExtensions
{
/// <summary>
/// Safely merges ModelState
/// </summary>
/// <param name="state"></param>
/// <param name="dictionary"></param>
/// <remarks>The MVC5 System.Web.Mvc.ModelStateDictionary.Merge method is not safe</remarks>
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;
}

/// <summary>
/// Merges ModelState that has names matching the prefix
/// </summary>
/// <param name="state"></param>
/// <param name="dictionary"></param>
/// <param name="prefix"></param>
public static void Merge(this ModelStateDictionary state, ModelStateDictionary dictionary, string prefix)
/// <summary>
/// Merges ModelState that has names matching the prefix
/// </summary>
/// <param name="state"></param>
/// <param name="dictionary"></param>
/// <param name="prefix"></param>
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;
}

/// <summary>
/// Checks if there are any model errors on any fields containing the prefix
Expand Down
2 changes: 1 addition & 1 deletion src/Umbraco.Web/Mvc/ControllerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ internal static void EnsureViewObjectDataOnResult(this ControllerBase controller
{
//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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext.Controller.ControllerContext.IsChildAction)
{
filterContext.Controller.ViewData.ModelState.Merge(
filterContext.Controller.ViewData.ModelState.MergeSafe(
filterContext.Controller.ControllerContext.ParentActionViewContext.ViewData.ModelState);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Umbraco.Web/Mvc/UmbracoPageResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ private static void EnsureViewContextForWebForms(ControllerContext context)
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;
Expand All @@ -108,7 +108,7 @@ private static void EnsureViewContextForWebForms(ControllerContext context)
/// </summary>
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;
Expand Down

0 comments on commit aa9b188

Please sign in to comment.