chinthaka88 / sharp-architecture

Automatically exported from code.google.com/p/sharp-architecture
Other
0 stars 0 forks source link

S#arp Architecture model binder enumeration error #129

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
I am receiving an error from the SharpArchitecture model binder. 
"Collection was modified; enumeration operation may not execute."  Stack
trace below.

My MVC pages to create and edit my SettingsModel throws the error.  This
started happening when we upgraded to the release version of S#arp
Architecture 1.0.   My class has a few Lists as properties.  One of the
Lists contains objects with another list as a property.  I'm not sure which
list is throwing the error.  Can anyone provide direction as to how to
troubleshoot this or things to look for in my models that might cause it?  

This is my SettingsModel class:

    public class SettingsModel : Entity
    {
        public SettingsModel() 
        {
            AttributeSettingsList = new List<AttributeSettingsModel>();
        }

        public virtual void AddAttributeSettings(AttributeSettingsModel attSettings)
        {
            AttributeSettingsList.Add(attSettings);
            attSettings.Settings = this;
        }

        [NotNullNotEmpty(Message = "Description must be provided")]
        public virtual string Description { get; set; }

        [DomainSignature]
        [Range(0, 100, Message = "ModelAPercentage must be between 0 and 100")]
        public virtual int ModelAPercentage { get; set; }

        [DomainSignature]
        [Range(0, 100, Message = "ModelBPercentage must be between 0 and 100")]
        public virtual int ModelBPercentage { get; set; }

        public virtual IList<AttributeSettingsModel> AttributeSettingsList { get;
set; }

        public virtual IList<EntityMappingModel> EntityMappingList { get; set; }

        public SettingsModel(string Description, int ModelAPercentage, int
ModelBPercentage)
            : this() 
        {
            this.Description = Description;
            this.ModelAPercentage = ModelAPercentage;
            this.ModelBPercentage = ModelBPercentage;
        }

    }

This is the SharpArchitecture method throwing the error: 

        private void SetEntityCollectionProperty(ModelBindingContext
bindingContext,
            PropertyDescriptor propertyDescriptor, object value) {

            if (value as IEnumerable != null &&

IsSimpleGenericBindableEntityCollection(propertyDescriptor.PropertyType)) {

                object entityCollection =
propertyDescriptor.GetValue(bindingContext.Model);
                Type entityCollectionType = entityCollection.GetType();

                foreach (object entity in (value as IEnumerable)) {
                    entityCollectionType.InvokeMember("Add",
                        BindingFlags.Public | BindingFlags.Instance |
BindingFlags.InvokeMethod, null, entityCollection,
                        new object[] { entity });
                }
            }
        }

Here is the stack trace:

[InvalidOperationException: Collection was modified; enumeration operation
may not execute.]
   System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource
resource) +51
   System.Collections.Generic.Enumerator.MoveNextRare() +7661017
   System.Collections.Generic.Enumerator.MoveNext() +61

SharpArch.Web.ModelBinder.SharpModelBinder.SetEntityCollectionProperty(ModelBind
ingContext
bindingContext, PropertyDescriptor propertyDescriptor, Object value) +358
   SharpArch.Web.ModelBinder.SharpModelBinder.SetProperty(ControllerContext
controllerContext, ModelBindingContext bindingContext, PropertyDescriptor
propertyDescriptor, Object value) +61
   System.Web.Mvc.DefaultModelBinder.BindProperty(ControllerContext
controllerContext, ModelBindingContext bindingContext, PropertyDescriptor
propertyDescriptor) +265

SharpArch.Web.ModelBinder.SharpModelBinder.BindProperty(ControllerContext
controllerContext, ModelBindingContext bindingContext, PropertyDescriptor
propertyDescriptor) +225
   System.Web.Mvc.DefaultModelBinder.BindProperties(ControllerContext
controllerContext, ModelBindingContext bindingContext) +125

System.Web.Mvc.DefaultModelBinder.BindComplexElementalModel(ControllerContext
controllerContext, ModelBindingContext bindingContext, Object model) +293
   System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext
controllerContext, ModelBindingContext bindingContext) +772
   System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext
controllerContext, ModelBindingContext bindingContext) +345
   SharpArch.Web.ModelBinder.SharpModelBinder.BindModel(ControllerContext
controllerContext, ModelBindingContext bindingContext) +39
   System.Web.Mvc.DefaultModelBinder.UpdateCollection(ControllerContext
controllerContext, ModelBindingContext bindingContext, Type elementType) +408
   System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext
controllerContext, ModelBindingContext bindingContext) +756
   System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext
controllerContext, ModelBindingContext bindingContext) +345
   SharpArch.Web.ModelBinder.SharpModelBinder.BindModel(ControllerContext
controllerContext, ModelBindingContext bindingContext) +39
   System.Web.Mvc.DefaultModelBinder.BindProperty(ControllerContext
controllerContext, ModelBindingContext bindingContext, PropertyDescriptor
propertyDescriptor) +231

SharpArch.Web.ModelBinder.SharpModelBinder.BindProperty(ControllerContext
controllerContext, ModelBindingContext bindingContext, PropertyDescriptor
propertyDescriptor) +225
   System.Web.Mvc.DefaultModelBinder.BindProperties(ControllerContext
controllerContext, ModelBindingContext bindingContext) +125

System.Web.Mvc.DefaultModelBinder.BindComplexElementalModel(ControllerContext
controllerContext, ModelBindingContext bindingContext, Object model) +293
   System.Web.Mvc.DefaultModelBinder.BindComplexModel(ControllerContext
controllerContext, ModelBindingContext bindingContext) +772
   System.Web.Mvc.DefaultModelBinder.BindModel(ControllerContext
controllerContext, ModelBindingContext bindingContext) +345
   SharpArch.Web.ModelBinder.SharpModelBinder.BindModel(ControllerContext
controllerContext, ModelBindingContext bindingContext) +39

System.Web.Mvc.ControllerActionInvoker.GetParameterValue(ControllerContext
controllerContext, ParameterDescriptor parameterDescriptor) +219

System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext
controllerContext, ActionDescriptor actionDescriptor) +109
   System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext
controllerContext, String actionName) +399
   System.Web.Mvc.Controller.ExecuteCore() +126
   System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +27

System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext
requestContext) +7
   System.Web.Mvc.MvcHandler.ProcessRequest(HttpContextBase httpContext) +151
   System.Web.Mvc.MvcHandler.ProcessRequest(HttpContext httpContext) +57

System.Web.Mvc.MvcHandler.System.Web.IHttpHandler.ProcessRequest(HttpContext 
httpContext)
+7

System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Ex
ecute()
+181
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean&
completedSynchronously) +75

Original issue reported on code.google.com by burnwith...@gmail.com on 16 Sep 2009 at 10:23

GoogleCodeExporter commented 9 years ago
I found that value and entityCollection were pointing to the same reference. 
When
SetEntityCollectionProperty enumerates value and changes entityCollection by 
adding
to it, the error gets thrown. In fact, it seems this iteration is unnecesary 
since
the collection appears to be fully populated. However, there may be use cases 
where
this is necessary that I am not aware of.

Original comment by mwrock...@gmail.com on 20 Sep 2009 at 10:19

GoogleCodeExporter commented 9 years ago
I hit this on my latest project and dug into it a little more with a simple 
test 
case. It seems to be that this snippet in the SharpModelBinder.cs class is 
where the 
failure starts:

        private void ReplaceValueProviderWithCustomValueProvider(ModelBindingContext 
bindingContext,
            PropertyDescriptor propertyDescriptor, Type propertyType, Type 
typeOfReplacementValueProvider) {

            string valueProviderKey =
                CreateSubPropertyName(bindingContext.ModelName, 
propertyDescriptor.Name);

            ValueProviderResult defaultResult;

            bool couldGetDefaultResult =
                bindingContext.ValueProvider.TryGetValue(valueProviderKey, out 
defaultResult);

            if (couldGetDefaultResult) {
                ValueProviderResult replacementValueProvider =
                    CreateReplacementValueProviderOf(typeOfReplacementValueProvider, 
propertyType, defaultResult);

                bindingContext.ValueProvider.Remove(valueProviderKey);
                bindingContext.ValueProvider.Add(valueProviderKey, 
replacementValueProvider);
            }
        }

The TryGetValue call returns false because the valueProviderKey is the name of 
the 
property on the model but the value provider only contains the indexed values. 
Ex. 
Model.LineItems versus Model.LineItems[0], Model.LineItems[1], etc. Since the 
couldGetDefaultResult variable ends up being false, it never replaces the value 
provider so the whole thing blows up later on in the process.

I was able to work around this by changing my view so that the form values 
posted for 
the entity collection all used the same name as the collection property and 
then it 
worked correctly. It seems indexing it using array notation is causing the 
Sharp 
Model Binder to choke.

If my model was:

public class TestViewModel
{
    public IList<SomeEntity> SomeEntities { get; set; }
}

This view code failed:

<input name="SomeEntities[0]" id="SomeEntities[0]" />
<input name="SomeEntities[1]" id="SomeEntities[1]" />
<input name="SomeEntities[2]" id="SomeEntities[2]" />

But this view code worked:

<input name="SomeEntities" id="SomeEntities[0]" />
<input name="SomeEntities" id="SomeEntities[1]" />
<input name="SomeEntities" id="SomeEntities[2]" />

Hope this gets resolved soon and that this workaround is helpful for someone.

Original comment by darc...@gmail.com on 8 Dec 2009 at 6:13

GoogleCodeExporter commented 9 years ago
BTW, it seems this workaround only helps in scenarios where you are selecting 
multiple 
existing entities for a list, not in the scenario where you are 
creating/editing 
multiple sub-entities since you need to have the [] index notation there.

I'm hitting this on my current project and will likely spend a little bit of 
time 
trying to figure out how to get it working. Will report back when/if I find a 
workaround.

Original comment by darc...@gmail.com on 10 Dec 2009 at 9:36

GoogleCodeExporter commented 9 years ago
After poring over the MVC DefaultModelBinder as well as the SharpModelBinder 
and trying to understand 
it all, I have been able to put together some changes to the Sharp Model Binder 
that fixes this issue 
with binding to collections of entities.

What I've basically done is pulled out all the custom value provider code and 
implemented it as just a 
custom model binder only for entity types (not entity collection types). The 
rationale here is that the 
default MVC ModelBinder implementation for populating a collection works fine 
and will call out to the 
custom code for each entity that needs to go into the collection. Also, we only 
care about our custom 
behavior for existing entities that we are pulling from the DB. I then went 
back and added back in all 
the code from the SharpModelBinder for setting the protected Id property and 
doing all the validation-
related stuff.

Tested scenarios:

1) Controller action taking lists of entities directly

public ActionResult Something(IList<MyEntity> ExistingEntities, IList<MyEntity> 
NewEntities)
{ ... }

View Code:
<!-- These are ID values and the list will be populated with entity objects 
from the DB -->
<input name="ExistingEntities[0]" value="1" />
<input name="ExistingEntities[1]" value="2" />
<input name="ExistingEntities[2]" value="3" />

<!-- The list will be populated with new entity objects and the Number property 
set -->
<input name="NewEntities[0].Number" value="ABC" />
<input name="NewEntities[1].Number" value="DEF" />
<input name="NewEntities[2].Number" value="GHI" />

2) Controller action taking a viewmodel object that contains lists of entities

public ActionResult Something(MyEntityViewModel viewModel)
{ ... }

public class MyEntityViewModel
{
    public IList<MyEntity> NewEntities { get; set; }
    public IList<MyEntity> ExistingEntities { get; set; }
}

View Code (The same view code from above works as well):

<!-- These are ID values and the list will be populated with entity objects 
from the DB -->
<input name="viewModel.ExistingEntities[0]" value="1" />
<input name="viewModel.ExistingEntities[1]" value="2" />
<input name="viewModel.ExistingEntities[2]" value="3" />

<!-- The list will be populated with new entity objects and the Number property 
set -->
<input name="viewModel.NewEntities[0].Number" value="ABC" />
<input name="viewModel.NewEntities[1].Number" value="DEF" />
<input name="viewModel.NewEntities[2].Number" value="GHI" />

Hope this helps. I'm going to post a message in the discussion group to see if 
Billy has any comments 
on these changes.

Original comment by darc...@gmail.com on 11 Dec 2009 at 2:19

Attachments:

GoogleCodeExporter commented 9 years ago
With the changes to the SharpModelBinder for MVC 2, we will have to revisit this
after the 2010 Q1 release

Original comment by alec.whi...@gmail.com on 23 Feb 2010 at 5:16

GoogleCodeExporter commented 9 years ago
Issue fixed in v1.5

Original comment by alec.whi...@gmail.com on 25 Mar 2010 at 5:00