Calling UpdateModel with a collection of complex data types reset all non-bound values?

前端 未结 3 2309
情话喂你
情话喂你 2020-12-01 13:17

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.

相关标签:
3条回答
  • 2020-12-01 13:51

    You just gave me an idea to dig into ASP.NET MVC 2 source code. I have been struggling with this for two weeks now. I found out that your solution will not work with nested lists. I put a breakpoint in the UpdateCollection method ,and it never gets hit. It seems like the root level of model needs to be a list for this method to be called

    This is in short the model I have..I also have one more level of generic lists, but this is just a quick sample..

    public class Borrowers
    {
       public string FirstName{get;set;}
       public string LastName{get;set;}
       public List<Address> Addresses{get;set;}
    }
    

    I guess that, I will need to dig deeper to find out what is going on.

    UPDATE: The UpdateCollection still gets called in asp.net mvc 2, but the problem with the fix above is related to this HERE

    0 讨论(0)
  • 2020-12-01 14:09

    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;
    

    }

    0 讨论(0)
  • 2020-12-01 14:12

    Rudi Breedenraed just wrote an excellent post describing this problem and a very helpful solution. He overrides the DefaultModelBinder and then when it comes across a collection to update, it actually updates the item instead of creating it new like the default MVC behavior. With this, UpdateModel() and TryUpdateModel() behavior is consistent with both the root model and any collections.

    0 讨论(0)
提交回复
热议问题