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.
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? –
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. –