Когда моя модель имеет свойство IEnumerable<T>
, которое реализовано как итератор (то есть 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<T>
параметр к действию и присвойте , что , свойству непосредственно внутри действия.Тьфу.