2011-02-08 73 views
6

当我的模型的IEnumerable<T>属性实现为iterator(即yield return)时,当传入值使用方括号语法(例如"Foo[0]")时,MVC的DefaultModelBinder无法绑定到该属性。MVC模型绑定:为什么我不能绑定到迭代器属性?

范例模型:

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应用程序示例。例如,我知道如果我从输入中删除方括号并为所有值重新使用相同的"Foo"键,则模型绑定将起作用。然而,真正失败的情况下需要方括号,因为集合中的每个项目都是具有自己的子属性的复杂类型。或者另一种解决方法:将非迭代器IEnumerable<T>参数添加到操作中,并将指定直接添加到操作内部的属性。啊。

回答

3

很简单,真的。如果DefaultModelBinder非空,则它不会覆盖您的IEnumerable<>实例。如果它为空,它将创建一个新的List<T>并填充它。

如果它非空,它就会知道如何处理某些类型的列表。如果您的列表执行ICollection<>,那么它将填充它。但是你的实例(yield)根本无法更新!

如果您对覆盖foobacking感到满意,那么您可以通过编写自定义模型绑定器来解决此问题。

+1

你说得对,实施类似[`yield null`](http://stackoverflow.com/questions/1765400/yield-return-with-null#1765422)证明是另一种解决方法,因为它必须触发模型联编程序分配一个新的列表。两个问题虽然: 1)为什么从输入中删除方括号(即通过`“Foo”`而不是`“Foo [0]”`)工作? 2)`Bar`不返回null。为什么它成功更新? – 2011-02-09 16:13:53

相关问题