Когда моя модель имеет IEnumerable<T>
свойство, которое реализуется как iterator (т.е. yield return
), MVC-х DefaultModelBinder
не может связываться с этим свойством при использовании входящих значений синтаксиса квадратных скобок (например, "Foo[0]"
).MVC Model Binding: почему я не могу привязать свойство итератора?
Пример Модель:
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); }
}
}
}
В противном случае пример :
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");
}
}
}
, приведенный выше тестовый модуль обновляет Bar
свойство моей модели не будет ["bar"]
никаких проблем (с или без квадратных скобок в ключах коллекции), но не сможет связать что-либо с свойством Foo
.
Кто-нибудь знает (на низком уровне), почему реализация свойства IEnumerable
как итератора приведет к сбою привязки модели к ошибке?
Я на самом деле не заинтересован в обходных , а некоторый анализ, как я исчерпал свои знания в рамках получать это далеко;)
1: Тест блока был самый простой способ изолировать проблему для SO, а не проходить через весь пример приложения MVC.
2: Например, я знаю, что если удалить квадратные скобки из входных данных и использовать тот же самый "Foo"
ключ для всех значений, модель привязки будет работать. Однако реальный неудачный случай требует квадратных скобок, поскольку каждый элемент в коллекции является сложным типом с его собственными под-свойствами. Или другое обходное решение: добавьте параметр non-iterator IEnumerable<T>
к действию и назначьте , что к собственности непосредственно внутри действия. Тьфу.
Вы правы, реализуя что-то вроде ['yield null'] (http://stackoverflow.com/questions/1765400/yield-return-with-null#1765422), оказывается еще одним обходным решением, поскольку оно должно запускать чтобы связать новый список. Два вопроса: 1) Почему удаление квадратных скобок из ввода (т.е. передача '' Foo "' вместо '' Foo [0] "') работает? 2) 'Bar' не возвращает null. Почему он успешно обновлен? –
2) Легко. Ваша поддержка 'List' реализует 'ICollection', поэтому' DefaultModelBinder' может его обновить. Не уверен относительно (1) - привязка модели изворотлива, и это, вероятно, непреднамеренно. –