• 深入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;
            }
  • 相关阅读:
    3、二进制的秘闻和不同进制间的转换
    Hello World!
    HDU5883 The Best Path(欧拉回路 | 通路下求XOR的最大值)
    Codeforces 722C(并查集 + 思维)
    Floyd 算法求多源最短路径
    vim 配置
    STL容器 -- Vector
    STL容器 -- Bitset
    HDU 5707 Combine String(动态规划)
    HDU 5876 Sparse Graph(补图上BFS)
  • 原文地址:https://www.cnblogs.com/yinzixin/p/2783763.html
Copyright © 2020-2023  润新知