Когда моя модель имеет свойство IEnumerable
, которое реализовано как итератор (то есть yield return
), MVC DefaultModelBinder
не может связываться с этим свойством, когда входящие значения используют синтаксис квадратных скобок (например, «Foo [0]»
).
Пример модели:
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); }
}
}
}
Пример сбоя 1 :
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
в качестве итератора приведет к сбою привязки модели?
Меня не особо интересуют обходные пути 2 ], а скорее некоторый анализ, поскольку я исчерпал свои знания о фреймворке, зашедшем так далеко;)
1: Модульный тест был самым простым способом изолировать проблему для SO, а не рассматривать весь пример приложения MVC .
2: Например, я знаю, что если я уберу квадратные скобки из ввода и повторно использую один и тот же ключ «Foo»
для всех значений, привязка модели будет работать.Однако в случае реальной неудачи требуются квадратные скобки, поскольку каждый элемент в коллекции представляет собой сложный тип со своими собственными подсвойствами. Или другой обходной путь: добавьте к действию параметр без итератора IEnumerable
и назначьте , что свойству непосредственно внутри действия. Фу.