2011-02-08 13 views
6

Quando il modello ha una proprietà IEnumerable<T> che è implementato come un iterator (cioè yield return), di DefaultModelBinder MVC non può legarsi a tale proprietà quando i valori ricevuti utilizzano la sintassi di parentesi quadra (es "Foo[0]").Associazione modello MVC: perché non è possibile eseguire il binding a una proprietà iteratore?

Esempio Modello:

namespace ModelBinderTest 
{ 
    using System.Collections.Generic; 
    public class MyModel 
    { 
     private List<string> fooBacking = new List<string>(); 
     public IEnumerable<string> Foo 
     { 
      get 
      { 
       foreach (var o in fooBacking) 
       { 
        yield return o; // <-- ITERATOR BREAKS MODEL BINDING 
       } 
      } 
      set { fooBacking = new List<string>(value); } 
     } 

     private List<string> barBacking = new List<string>(); 
     public IEnumerable<string> Bar 
     { 
      get 
      { 
       // Returning any non-iterator IEnumerable works here. Eg: 
       return new List<string>(barBacking); 
      } 
      set { barBacking = new List<string>(value); } 
     } 
    } 
} 

riuscendo esempio :

namespace ModelBinderTest 
{ 
    using System; 
    using System.Linq; 
    using System.Web.Mvc; 
    using Microsoft.VisualStudio.TestTools.UnitTesting; 

    [TestClass] 
    [CLSCompliant(false)] 
    public class DefaultModelBinderTestIterator 
    { 
     [TestMethod] 
     public void BindsIterator() 
     { 
      // Arrange 
      var model = new MyModel(); 

      ModelBindingContext bindingContext = new ModelBindingContext() 
      { 
       FallbackToEmptyPrefix = true, 
       ModelMetadata = ModelMetadataProviders 
            .Current 
            .GetMetadataForType(null, model.GetType()), 
       ModelName = "", 
       ValueProvider = new NameValueCollectionValueProvider(
        new System.Collections.Specialized.NameValueCollection() 
         { 
          { "Foo[0]", "foo" }, 
          { "Bar[0]", "bar" }, 
         }, 
        System.Globalization.CultureInfo.InvariantCulture 
       ) 
      }; 

      DefaultModelBinder binder = new DefaultModelBinder(); 

      // Act 
      MyModel updatedModel = (MyModel)binder.BindModel(
            new ControllerContext(), bindingContext); 

      // Assert 
      Assert.AreEqual(1, updatedModel.Bar.Count(), 
          "Bar property should have been updated"); 
      Assert.AreEqual("bar", updatedModel.Bar.ElementAtOrDefault(0), 
          "Bar's first element should have been set"); 

      Assert.AreEqual(1, updatedModel.Foo.Count(), 
          "Foo property should have been updated"); 
      Assert.AreEqual("foo", updatedModel.Foo.ElementAtOrDefault(0), 
          "Foo's first element should have been set"); 
     } 

    } 
} 

La prova unità sopra aggiornerà la struttura Bar del mio modello di essere ["bar"] problema (con o senza parentesi quadre nelle chiavi di raccolta), ma non riuscirà a legare nulla alla proprietà Foo.

Qualcuno sa (a un livello basso) perché l'implementazione di una proprietà IEnumerable come iteratore può causare il fallimento del binding del modello?

io non sono veramente interessati a soluzioni alternative , ma piuttosto un po 'di analisi, come ho esaurito la mia conoscenza del quadro ottenere fino a questo punto;)


1: Un test di unità era il modo più semplice per isolare il problema per SO, piuttosto che passare attraverso un intero esempio di applicazione MVC.

2: Ad esempio, so che se tolgo parentesi quadre dall'ingresso e riutilizzare la stessa "Foo" chiave per tutti i valori, il modello vincolante funzionerà. Tuttavia il vero caso di fallimento richiede parentesi quadre poiché ogni elemento della collezione è un tipo complesso con le sue proprietà secondarie. Oppure un'altra soluzione: aggiungi un parametro non iteratore IEnumerable<T> all'azione e assegna a alla proprietà direttamente all'interno dell'azione. Ugh.

risposta

3

Abbastanza semplice, davvero. Lo DefaultModelBinder non sovrascriverà l'istanza IEnumerable<> se non è nulla. Se è nulla, creerà un nuovo List<T> e lo riempirà.

Se non è nullo, ha determinati tipi di elenchi che sa come gestire. Se il tuo elenco implementa ICollection<>, allora lo riempirà. Ma la tua istanza (con yield) non può essere aggiornata affatto!

Se si ha dimestichezza con la sovrascrittura di foobacking, è possibile aggirare il problema scrivendo un raccoglitore di modelli personalizzato.

+1

Hai ragione, l'implementazione di qualcosa come ['yield null'] (http://stackoverflow.com/questions/1765400/yield-return-with-null#1765422) si rivela un'ulteriore soluzione, in quanto deve attivare Raccoglitore di modelli per assegnare una nuova lista. Due domande: 1) Perché rimuovere le parentesi quadre dall'input (cioè passare '" Foo "' invece di '" Foo [0] "') funziona? 2) 'Bar' non restituisce null. Perché è stato aggiornato con successo? –

+0

2) È facile. La vostra lista di 'back ' implementa 'ICollection', quindi' DefaultModelBinder' può aggiornarlo. Non sono sicuro di (1) - il binding del modello è bizzarro ed è probabilmente non intenzionale. –

Problemi correlati