Calling UpdateModel with a collection of complex data types reset all non-bound values?
asp.net-mvc, model-binding, updatemodel
Solution
UPDATE: I stepped through mvc source code (particularly `DefaultModelBinder` class) and here is what I found:
The class determines we are trying to bind a collection so it calls the method: `UpdateCollection(...)` which creates an inner `ModelBindingContext` that has a `null` `Model` property. Afterwards, that context is sent to the method `BindComplexModel(...)` which checks the `Model` property for `null` and creates a new instance of the model type if that is the case.
That's what causes the values to be reset.
And so, only the values that are coming through the form/query string/route data are populated, the rest remains in its initialized state.
I was able to make very few changes to `UpdateCollection(...)` to fix this problem.
Here is the method with my changes:
internal object UpdateCollection(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType) {
IModelBinder elementBinder = Binders.GetBinder(elementType);
// build up a list of items from the request
List<object> modelList = new List<object>();
for (int currentIndex = 0; ; currentIndex++) {
string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex);
if (!DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, subIndexKey)) {
// we ran out of elements to pull
break;
}
// **********************************************************
// The DefaultModelBinder shouldn't always create a new
// instance of elementType in the collection we are updating here.
// If an instance already exists, then we should update it, not create a new one.
// **********************************************************
IList containerModel = bindingContext.Model as IList;
object elementModel = null;
if (containerModel != null && currentIndex < containerModel.Count)
{
elementModel = containerModel[currentIndex];
}
//*****************************************************
ModelBindingContext innerContext = new ModelBindingContext() {
Model = elementModel, // assign the Model property
ModelName = subIndexKey,
ModelState = bindingContext.ModelState,
ModelType = elementType,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
object thisElement = elementBinder.BindModel(controllerContext, innerContext);
// we need to merge model errors up
VerifyValueUsability(controllerContext, bindingContext.ModelState, subIndexKey, elementType, thisElement);
modelList.Add(thisElement);
}
// if there weren't any elements at all in the request, just return
if (modelList.Count == 0) {
return null;
}
// replace the original collection
object collection = bindingContext.Model;
CollectionHelpers.ReplaceCollection(elementType, collection, modelList);
return collection;
}
Problem
I'm not sure if this is a bug in the DefaultModelBinder class or what. But UpdateModel usually doesn't change any values of the model except the ones it found a match for. Take a look at the following: ``` [AcceptVerbs(HttpVerbs.Post)] public ViewResult Edit(List<int> Ids) { // Load list of persons from the database List<Person> people = GetFromDatabase(Ids); // shouldn't this update only the Name & Age properties of each Person object // in the collection and leave the rest of the properties (e.g. Id, Address) // with their original value (whatever they were when retrieved from the db) UpdateModel(people, "myPersonPrefix", new string[] { "Name", "Age" }); // ... } ``` What happens is UpdateModel creates new Person objects, assign their Name & Age properties from the ValueProvider and put them in the argument List<>, which makes the rest of the properties set to their default initial value (e.g. Id = 0) so what is going on here?