2011-02-08 3 views
6

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

ответ

3

Довольно простой, действительно. DefaultModelBinder не перезапишет ваш экземпляр IEnumerable<>, если он не является нулевым. Если он равен нулю, он создаст новый List<T> и заполнит его.

Если это не null, у него есть определенные типы списков, в которых он знает, как бороться. Если ваш список реализует ICollection<>, он заполнит его. Но ваш экземпляр (с yield) не может быть обновлен вообще!

Если вам удобно переписывать foobacking, тогда вы можете обойти это, написав собственное связующее устройство.

+1

Вы правы, реализуя что-то вроде ['yield null'] (http://stackoverflow.com/questions/1765400/yield-return-with-null#1765422), оказывается еще одним обходным решением, поскольку оно должно запускать чтобы связать новый список. Два вопроса: 1) Почему удаление квадратных скобок из ввода (т.е. передача '' Foo "' вместо '' Foo [0] "') работает? 2) 'Bar' не возвращает null. Почему он успешно обновлен? –

+0

2) Легко. Ваша поддержка 'List ' реализует 'ICollection', поэтому' DefaultModelBinder' может его обновить. Не уверен относительно (1) - привязка модели изворотлива, и это, вероятно, непреднамеренно. –

Смежные вопросы