2009-07-30 9 views
7

Non sono sicuro se si tratta di un errore nella classe DefaultModelBinder o cosa. Ma UpdateModel di solito non modifica alcun valore del modello tranne quelli per cui ha trovato una corrispondenza. Date un'occhiata al seguente:La chiamata a UpdateModel con una raccolta di tipi di dati complessi reimposta tutti i valori non vincolati?

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

Quello che succede è UpdateModel crea nuovi oggetti Person, assegnare loro nome & proprietà Età dal ValueProvider e metterle nella lista degli argomenti <>, il che rende il resto della le proprietà impostate sul loro valore iniziale predefinito (es. Id = 0) quindi cosa sta succedendo qui?

risposta

8

UPDATE: ho fatto un passo attraverso il codice MVC sorgente (in particolare DefaultModelBinder classe) e qui è quello che ho trovato:

La classe determina che stiamo cercando di legare una collezione così chiama il metodo: UpdateCollection(...) che crea un interno ModelBindingContext che ha una proprietà nullModel. Successivamente, tale contesto viene inviato al metodo BindComplexModel(...) che controlla la proprietà Model per null e crea un'istanza nuova del tipo di modello, se questo è il caso.

Ecco cosa provoca il ripristino dei valori.

E così, vengono popolati solo i valori che stanno arrivando attraverso i dati del form/query string/route, il resto rimane nel suo stato inizializzato.

Sono stato in grado di apportare pochissime modifiche a UpdateCollection(...) per risolvere questo problema.

Ecco il metodo con le mie modifiche:

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

Hai appena mi ha dato l'idea di scavare in ASP.NET MVC 2 Codice sorgente. Ho lottato con questo per due settimane ora. Ho scoperto che la tua soluzione non funzionerà con gli elenchi annidati. Inserisco un punto di interruzione nel metodo UpdateCollection e non viene mai colpito. Sembra che il livello radice del modello debba essere un elenco per questo metodo da chiamare

Questo è in breve il modello che ho .. Ho anche un altro livello di elenchi generici, ma questo è solo un breve esempio ..

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

Immagino che, avrò bisogno di scavare più a fondo per scoprire cosa sta succedendo.

UPDATE: Il UpdateCollection ancora viene chiamato in asp.net mvc 2, ma il problema con la correzione di cui sopra è legato a questo HERE

4

Rudi Breedenraed appena scritto un eccellente post che descrive questo problema e una soluzione molto utile . Sostituisce DefaultModelBinder e quindi quando incontra una raccolta da aggiornare, in realtà aggiorna l'elemento anziché crearlo come il comportamento MVC predefinito. Con questo, il comportamento UpdateModel() e TryUpdateModel() è coerente sia con il modello radice sia con eventuali raccolte.

+0

è vero anche per MVC 3? – Vidar

+0

@Vidar Sì, ha paura. – nfplee