2011-02-08 38 views
6

Khi mô hình của tôi có thuộc tính IEnumerable<T> được triển khai dưới dạng iterator (ví dụ: yield return), DefaultModelBinder của MVC không thể liên kết với thuộc tính đó khi giá trị đến sử dụng cú pháp khung vuông (ví dụ: "Foo[0]").Mô hình MVC Binding: tại sao tôi không thể liên kết với một thuộc tính iterator?

Ví dụ mẫu:

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); } 
     } 
    } 
} 

Không dụ :

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"); 
     } 

    } 
} 

Các đơn vị kiểm tra trên sẽ cập nhật các Bar tài sản của mô hình của tôi là ["bar"] không có vấn đề (có hoặc không có dấu ngoặc vuông trong các khóa thu thập), nhưng sẽ không ràng buộc bất cứ điều gì với thuộc tính Foo.

Có ai biết (ở mức thấp) tại sao việc triển khai thuộc tính IEnumerable làm trình lặp sẽ khiến mô hình ràng buộc không thành công?

Tôi không thực sự quan tâm đến cách giải quyết , mà đúng hơn là một số phân tích, như tôi đã kiệt sức hiểu biết của tôi về khuôn khổ nhận này đến nay;)


1: Một thử nghiệm đơn vị là cách dễ nhất để cô lập vấn đề cho SO, thay vì trải qua toàn bộ ví dụ ứng dụng MVC.

2: Ví dụ: tôi biết rằng nếu tôi xóa dấu ngoặc vuông khỏi đầu vào và sử dụng lại cùng một khóa "Foo" cho tất cả giá trị, mô hình ràng buộc sẽ hoạt động. Tuy nhiên, trường hợp thất bại thực sự yêu cầu các dấu ngoặc vuông vì mỗi mục trong bộ sưu tập là một kiểu phức tạp với các thuộc tính con của riêng nó. Hoặc một cách giải quyết khác: thêm thông số IEnumerable<T> không lặp lại cho hành động và chỉ định rằng cho thuộc tính trực tiếp bên trong hành động. Ugh.

Trả lời

3

Khá đơn giản, thực sự. DefaultModelBinder sẽ không ghi đè trường hợp IEnumerable<> của bạn nếu nó không phải là không. Nếu nó là null, nó sẽ tạo ra một List<T> mới và điền vào nó.

Nếu nó không rỗng, nó có một số loại danh sách nhất định mà nó biết cách xử lý. Nếu danh sách của bạn thực hiện ICollection<>, thì nó sẽ lấp đầy nó. Nhưng trường hợp của bạn (với yield) không thể cập nhật được!

Nếu bạn cảm thấy thoải mái khi ghi đè foobacking thì bạn có thể giải quyết vấn đề này bằng cách viết một mô hình tùy chỉnh.

+1

Bạn nói đúng, thực hiện một cái gì đó như ['yield null'] (http://stackoverflow.com/questions/1765400/yield-return-with-null#1765422) chứng minh là một cách giải quyết khác, vì nó phải kích hoạt chất kết dính mô hình để gán một danh sách mới. Hai câu hỏi mặc dù: 1) Tại sao loại bỏ dấu ngoặc vuông khỏi đầu vào (tức là chuyển 'Foo '' thay vì '" Foo [0] "') hoạt động? 2) 'Bar' không trả về null. Tại sao nó được cập nhật thành công? –

+0

2) Rất dễ dàng. Sự ủng hộ của bạn 'List ' thực hiện 'ICollection', vì vậy' DefaultModelBinder' có thể cập nhật nó. Không chắc chắn về (1) - mô hình ràng buộc là kỳ quặc và nó có thể không chủ ý. –

Các vấn đề liên quan