【问题标题】:MVC Model Binding: why can't I bind to an iterator property?MVC 模型绑定:为什么我不能绑定到迭代器属性?
【发布时间】:2011-02-08 20:51:23
【问题描述】:

当我的模型具有作为iterator 实现的IEnumerable<T> 属性(即yield return)时,当传入值使用方括号语法(例如"Foo[0]")时,MVC 的DefaultModelBinder 无法绑定到该属性。

示例模型:

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&lt;T&gt; 参数添加到操作,并将 that 直接分配给操作内的属性。呃。

【问题讨论】:

    标签: asp.net-mvc iterator ienumerable model-binding


    【解决方案1】:

    非常简单,真的。 DefaultModelBinder 不会覆盖您的 IEnumerable&lt;&gt; 实例,如果它是非空的。如果为空,则会创建一个新的List&lt;T&gt; 并填充它。

    如果它是非空的,它有某些类型的列表它知道如何处理。如果您的列表实现ICollection&lt;&gt;,那么它将填充它。但是您的实例(使用yield)根本无法更新!

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

    【讨论】:

    • 你是对的,实现类似yield null 的东西被证明是另一种解决方法,因为它必须触发模型绑定器来分配一个新列表。但是有两个问题:1) 为什么从输入中删除方括号(即传递"Foo" 而不是"Foo[0]")有效? 2) Bar 不返回 null。为什么更新成功了?
    • 2) 很简单。你的支持List&lt;string&gt; 实现了ICollection,所以DefaultModelBinder 可以更新它。不确定(1)——模型绑定很奇怪,可能是无意的。
    猜你喜欢
    • 2013-09-09
    • 1970-01-01
    • 2012-01-07
    • 1970-01-01
    • 2014-05-31
    • 2011-08-07
    • 1970-01-01
    • 1970-01-01
    • 2012-06-18
    相关资源
    最近更新 更多