2009-07-30 3 views
7

Je ne suis pas sûr s'il s'agit d'un bogue dans la classe DefaultModelBinder ou quoi. Mais UpdateModel ne modifie généralement pas les valeurs du modèle sauf celles pour lesquelles il a trouvé une correspondance. Jetez un oeil à l'adresse suivante:L'appel de UpdateModel avec une collection de types de données complexes réinitialise toutes les valeurs non liées?

[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" }); 
    // ... 
} 

Qu'est-ce qui se passe est UpdateModel crée nouveaux objets personne, attribuer leur nom & propriétés de l'âge de la ValueProvider et les mettre dans la liste des arguments <>, ce qui rend le reste de les propriétés définies à leur valeur initiale par défaut (par exemple Id = 0) alors que se passe-t-il ici?

Répondre

8

MISE À JOUR: je suis entré dans le code source mvc (en particulier DefaultModelBinder de classe) et voici ce que je trouve:

La classe détermine que nous essayons de lier une collection de sorte qu'il appelle la méthode: UpdateCollection(...) qui crée une propriété interne ModelBindingContext qui a une propriété nullModel. Ensuite, ce contexte est envoyé à la méthode BindComplexModel(...) qui vérifie la propriété Model pour null et crée une nouvelle instance du type de modèle si tel est le cas.

C'est ce qui provoque la réinitialisation des valeurs. Ainsi, seules les valeurs qui traversent le formulaire/chaîne de requête/données de route sont remplies, le reste reste dans son état initialisé.

J'ai été en mesure de faire très peu de changements à UpdateCollection(...) pour résoudre ce problème.

Voici la méthode avec mes changements:

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; 

}

1

Vous venez de me donner une idée de creuser dans le code source ASP.NET MVC 2. Cela fait deux semaines que je me bats avec ça. J'ai découvert que votre solution ne fonctionnerait pas avec les listes imbriquées. Je mets un point d'arrêt dans la méthode UpdateCollection, et il ne sera jamais touché. Il semble que le niveau racine du modèle doit être une liste pour que cette méthode soit appelée

Ceci est en bref le modèle que j'ai .. J'ai aussi un niveau de plus de listes génériques, mais ce n'est qu'un échantillon rapide

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

Je suppose que, je devrais creuser plus profondément pour savoir ce qui se passe.

MISE À JOUR: Le UpdateCollection obtient encore appelé asp.net mvc 2, mais le problème avec le correctif ci-dessus est lié à ce HERE

4

Rudi Breedenraed vient d'écrire un excellent post décrivant ce problème et une solution très utile . Il remplace le DefaultModelBinder et lorsqu'il rencontre une collection à mettre à jour, il met à jour l'élément au lieu de le créer comme le comportement MVC par défaut. Avec ce comportement, UpdateModel() et TryUpdateModel() est cohérent avec le modèle racine et toutes les collections.

+0

est-ce vrai aussi pour MVC 3? – Vidar

+0

@Vidar Oui, j'ai peur. – nfplee

Questions connexes