深入ASP.NET MVC之五:Model Binding


在上文中,談到在action方法被執行的過程中,調用了ControllerActionInvoker的GetParameterValues方法來獲得action的參數,上文沒有細談,在這個方法里面,實現了ASP.NET MVC的Model Binding功能。ASP.NET的Model Binding主要有兩個接口組成,分別是:

    public interface IModelBinder {
        object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext);
    }
    public interface IValueProvider {
        bool ContainsPrefix(string prefix);
        ValueProviderResult GetValue(string key);
    }

這兩個接口都非常簡單,BindModel是真正實現數據綁定的地方,ModelBindingContext有個屬性是ValueProvider用來給BindModel提供數據。ASP.NET MVC的Model Binding的“骨架”其實也不復雜,比較繁瑣的是這兩個接口的實現,這兩個接口的實現才是真正實現綁定功能的地方。ASP.NET MVC有一些默認實現,DefaultModelBinder和一系列的ValueProvider:FormValueProvider,QueryStringProvider等,IValueProvider可以將form中的表單,querystring中的數據等抽象為鍵值對,對於ModelBinder來說,他並不知道這些數據是通過什么地方來的。Model Binding有兩方面的功能,一是將提交上去的數據綁定到action方法的參數中,另一方面是將對象的值顯示到view中,本文先側重前一個方面。先看DefaultModelBinder的功能,簡單說,這個Model Binder主要是根據Action方法的參數的名字和通過http request提交上去的Key-Value pair中的key進行比對從而進行綁定。具體來說,又分為很多情況。作為例子,如下定義幾個類型:

    public class Person
    {
        public string Name { get; set; }

        public int Age { get; set; }

        public Address Add { get; set; }

        public List<Course> Courses { get; set; }
    }

    public class Address
    {
        public string City { get; set; }
        public string Street { get; set; }
    }

    public class Course
    {
        public string Name { get; set; }
        public int Id { get; set; }
    }

(1)簡單類型,比如 Action(string abc),這種情況會將 key=abc的值直接賦值給abc這個參數。

(2)復雜類型,采用遞歸的手法進行綁定。

  1.          (2.1)如果是數組或者IEumerable<T>的,例如 Action(List<Course> courses),內部創建一個List,進行數組綁定。數組綁定的時候,對key有如下要求,
  2.                    (2.1.1) 是以數字為index的,比如 [0].Name, [0].Id, [1].Name, [1].Id。這里的數字必須是從0開始,連續。
  3.                     (2.1.2)  有時候上面這個條件比較難以滿足,比如需要通過javascript動態增刪表單的時候,這時候可以自定義Index。下面針對兩種情況看兩個例子:
  4. 新建一個View:
<form action="@Url.Action("SeqIndex")" method="post">
    <p>
        Id:
        <input type="text" name="[0].Id" />
        &nbsp;
       Name: 
        <input type="text" name="[0].Name" />
    </p>
    <p>
        Id:
        <input type="text" name="[2].Id" />
        &nbsp;
       Name: 
        <input type="text" name="[2].Name" />
    </p>
    <p>
        Id:
        <input type="text" name="[1].Id" />
        &nbsp;
       Name: 
        <input type="text" name="[1].Name" />
    </p>
    <input type="submit" value="OK" />
</form>

和一個Action方法:

        [HttpPost]
        public ActionResult SeqIndex(Course[] courses)
        {
            return Json(courses);
        }

 

image

點擊Ok之后的輸出:

[{"Name":null,"Id":1},{"Name":"c","Id":3},{"Name":"b","Id":2}]

注意1: course變量的中的順序是和form中的Index一致的。

注意2:大多時候使用MVC的輔助方法,比如TextBox,Editor等生成表單的字段更好,在這里為了更清楚說明原理,采用了原始的html寫法。

注意3:對每一個類型為T進行綁定的時候,T依然可能是一個復雜類型,自然需要遞歸的執行這個綁定流程,以下皆如此,不再重復指出。

第二個例子,

<form action="@Url.Action("SeqIndex")" method="post">
    <p><input type="hidden" name="index" value="Monday" />
         Monday Id:<input type="text" name="[Monday].Id" />
         Monday Name:<input type="text" name="[Monday].Name" />
    </p>

    <p><input type="hidden" name="index" value="Tue" />
        Tuesday Id:<input type="text" name="[Tue].Id" />
        Tuesday Name:<input type="text" name="[Tue].Name" />
    </p>
 
      <input type="submit" value="OK" />
</form>

點擊Ok之后輸出:

[{"Name":"a","Id":1},{"Name":"b","Id":2}]

 

注意這里的技巧是用一個hidden的input標記Monday,Tue這些是Index。下文分析源代碼的時候會看到是如何實現這種綁定的。

 

        (2.2)如果參數是IDictionary的,則新建一個Dictionary對象,將Key和從valueProvider中獲得的值轉換成相應類型的對象之后放入Dictionary。此時的表單應該是如下樣子的:

<form action="@Url.Action("Dictionary")" method="post">
     <p><input type="hidden" name="[0].Key" value="Monday" />
         Monday Id:<input type="text" name="[0].value.Id" />
         Monday Name:<input type="text" name="[0].value.Name" />
    </p>
         <p><input type="hidden" name="[1].Key" value="Tue" />
         Tuesday Id:<input type="text" name="[1].value.Id" />
         Tuesday Name:<input type="text" name="[1].value.Name" />
    </p>
    <input type="submit" value="OK" />
</form>

Action方法為:

        [HttpPost]
        public ActionResult Dictionary(Dictionary<string, Course> courses)
        {
            return Json(courses);
        }

image

結果為:

{"Monday":{"Name":"a","Id":2},"Tue":{"Name":"b","Id":1}}

        (2.3) 除此之外,綁定的參數將是“單個”的對象。這時候遍歷此對象的所有公共屬性,遞歸的進行數據綁定。也看一個例子:

<form action="@Url.Action("Complex")" method="post">
     <p>        
         Name:<input type="text" name="Name" /> 
         Age:<input type="text" name="Age" />
    </p>
    <fieldset >
        <legend>Address</legend>
        <p>City:<input type="text" name="Add.City" /> </p>
        <p>Street:<input type="text" name="Add.Street" /> </p>
    </fieldset>
    <fieldset>
        <legend>Courses</legend>
        <p>Course 1 Name:<input type="text" name="Courses[0].Name" />
           Course 1 Id:<input type="text" name="Courses[0].Id" />
        </p>
                <p>Course 2 Name:<input type="text" name="Courses[1].Name" />
           Course 2 Id:<input type="text" name="Courses[1].Id" />
        </p>
                <p>Course 3 Name:<input type="text" name="Courses[2].Name" />
           Course 3 Id:<input type="text" name="Courses[2].Id" />
        </p>
    </fieldset>
    <input type="submit" value="OK" />
</form>
        public ActionResult Complex(Person p)
        {
            return Json(p);
        }

 

image

點擊Ok之后輸出為:

{"Name":"Zixin Yin","Age":28,"Add":{"City":"bellevue","Street":"15058 NE 8th PL"},"Courses":[{"Name":"math","Id":1},{"Name":"physics","Id":2},{"Name":"english","Id":3}]}

上面介紹了asp.net mvc的默認的model binder支持的綁定形式,應該說是比較強大的,基本能夠應付各種需求,這個過程其實很像反序列化的過程,當然它的數據源是“扁平”的,只有Key-Value對,這是由html的表單所能提交的數據決定的,因此在綁定復雜對象的時候,需要能夠區分這個key對應的是哪個對象上的屬性值。比如上面的例子中,name="Add.City" ,就表明,這是Person類型(因為這是Action方法中唯一的參數類型),Add屬性(Address類型)的City屬性(string類型)。默認的model binder是通過點號和中括號來區分的,點號分隔開的一段段稱為prefix,prefix在綁定過程中起到了十分重要的作用,這就是IValueProvider接口中ContainsPrefix方法存在的意義。這個方法的含義乍看並不明確,只有詳細分析了其實現之后才能比較透徹的明白。Prefix除了分隔復雜類型的屬性之外,還有一個重要的作用,就是當Action有多個參數的時候,可以指定其中一個參數的前綴,從而區分開兩個參數的值,例如下面的表單:

<form action="@Url.Action("Multiple")" method="post">
    <fieldset>
        <legend>Address A</legend>
        City: <input type="text" name="City" />
        Street: <input type="text" name="Street" />
    </fieldset>
    <fieldset>
        <legend>Address B</legend>
        City: <input type="text" name="B.City" />
        Street: <input type="text" name="B.Street" />
    </fieldset>
     <input type="submit" value="OK" />
</form>

 

在表單中通過前綴B來區分,對應的在action方法中:

        public string Multiple(Address addr1,[Bind(Prefix="B")]Address addr2)
        {
            return addr1.City + " " + addr1.Street + " B:" + addr2.City + addr2.Street;
        }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM