Kevin,
I've run into this problem as well and I've come up with some changes
to the SharpModelBinder that resolve this issue. I've posted the
updated code to Issue 129:
http://code.google.com/p/sharp-architecture/issues/detail?id=129
along with some comments. You should be able to add this to your Web
project and just update the Global.asax.cs to use the new
CustomSharpModelBinder as the default binder instead of the
SharpModelBinder from the SharpArch.Web assembly.
After digging into the SharpModelBinder code, I've got some concerns
on the rationale for how it is currently implemented in trunk.
1) It is handling binding scenarios for both Entities and Entity
Collections but if we only implemented custom binding for Entities,
the default model binder implementation will create the generic
collection and call out to the custom binding code for each entity
that needs to be fetched from the DB. This may be the main reason why
the issues with binding collections are happening.
2) Implementing custom valid providers seems more complicated than
just putting the code to pull entities from the DB in the binder. I'm
not sure if this design was to promote Seperation of Concerns but it
isn't strictly necessary and IMO just makes the binder more complex
and harder to reason about.
The updated code I posted basically just gets rid of all the custom
value provider code and implements pulling a single Entity from the DB
only when the Model.Name is an exact match to a value we've been
provided by the default value provider. This means we only pull from
the DB when your form contains a value that maps directly to an entity
property or a subindex of an entity collection property (see examples
below). Otherwise, we let the default model binding behavior create
new entities and set all the properties as necessary. I then added
back in all the validation-related code as well as the code for
setting the protected ID property.
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" />
---
Hopefully Billy or anyone else that has worked on the
DefaultModelBinder can review this and provide some feedback.
On Nov 25, 8:55 pm, Kevin Amerson <
kevin.amer...@gmail.com> wrote:
> I'm seeing some strange behavior in the model binder setting items in a
> collection. It seems like the binder is adding the items twice, and this
> method throws an illegal operation exception indicating that values in the
> collection in the loop have been modified, seems like "value" and
> "entityCollection" are the same collection.
>
> /// <summary>
> /// If the property being bound is a simple, generic collection of
> entiy objects, then use
> /// reflection to get past the protected visibility of the
> collection property, if necessary.
> /// </summary>
> 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 });
> }*