ASP.NET MVC学习三-数据传递之模型绑定

一:简单的模型绑定

在ASP.NET MVC中是模型绑定来解析客户端传过来的数据的,简单的来说就更近一步来封装了获得数据的手段,让用户更方便的来获取数据了

我们来简单做一个例子

public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(string username)
        {
            ViewData["username"] = username;
            return View();
        }

这里我们定义了一个Action,[httpPost]限定Action只能被post请求访问,我们在其参数中定义一个名字username的字符串

view视图中的代码如下

@{
    ViewBag.Title = "Index";
}

<form method="post">
    <h1>简单模型绑定</h1>
    姓名:<input type="text" name="username" id="username" />
    <input type="submit" value="提交" />
</form>
@if (ViewData["username"] != null)
{
    <h1>@ViewData["username"]</h1>
}

这里注意模型绑定的name属性需要和view视图中的input标签工具name相同

image

 

 

二:使用FormCollection来进行模型绑定

在Asp.net中我们习惯了用request.Form来获取提交的表单的值,当然在MVC中我们可以很方便的获取其值

public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(FormCollection e)
        {
            ViewData["username"] = e["username"];
            ViewData["Age"] = e["Age"];
            return View();
        }

在视图中代码为

<form method="post">
    <h1>简单模型绑定</h1>
    姓名:<input type="text" name="username" id="username" />
    年龄:<input type="text" name="Age" id="Age" /><br />
    <input type="submit" value="提交" />
</form>
@if (ViewData["username"] != null)
{
    <label>姓名:</label><h1>@ViewData["username"]</h1>
    <label>年龄:</label><h1>@ViewData["Age"]</h1>
}

我们可以通过这种方法清晰的看到一样是能获取数据

image

 

三:模型绑定之传递对象

之前我们讨论的都是通过表单传递一些简单的值,现在如果要传递一个model对象能不能呢?我们先在Model层添加一个People得吧

public class People
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }

在Controller将代码修改

public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(People people)
        {
            ViewData["Id"] = people.Id;
            ViewData["Name"] = people.Name;
            ViewData["Age"] = people.Age;
            return View();
        }

此时我们将模型绑定为People类型

View中视图修改为

<form method="post">
    Id:<input type="text" name="Id" id="Id" />
    姓名:<input type="text" name="Name" id="Name" />
    年龄:<input type="text" name="Age" id="Age" /><br />
    <input type="submit" value="提交" />
</form>
@if (ViewData["Id"] != null)
{
    <label>Id:</label><h1>@ViewData["Id"]</h1>
    <label>姓名:</label><h1>@ViewData["Name"]</h1>
    <label>年龄:</label><h1>@ViewData["Age"]</h1>
}

当然这里我们将定义的Input标签的name属性与类people名字一样

image

 

到这里你可能又会觉得那能不能传递多个对象呢?

我们将Controller的代码改动一下

public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(People people1, People people2)
        {
            ViewData["Id1"] = people1.Id;
            ViewData["Name1"] = people1.Name;
            ViewData["Age1"] = people1.Age;

            ViewData["Id2"] = people2.Id;
            ViewData["Name2"] = people2.Name;
            ViewData["Age2"] = people2.Age;
            return View();
        }
<form method="post">
    Id1:<input type="text" name="people1.Id" id="people1.Id" />
    姓名1:<input type="text" name="people1.Name" id="people1.Name" />
    年龄1:<input type="text" name="people1.Age" id="people1.Age" /><br />
     Id2:<input type="text" name="people2.Id" id="people2.Id" />
    姓名2:<input type="text" name="people2.Name" id="people2.Name" />
    年龄2:<input type="text" name="people2.Age" id="people2.Age" /><br />
    <input type="submit" value="提交" />
</form>
@if (ViewData["Id1"] != null)
{
    <label>Id1:</label><h1>@ViewData["Id1"]</h1>
    <label>姓名1:</label><h1>@ViewData["Name1"]</h1>
    <label>年龄1:</label><h1>@ViewData["Age1"]</h1>
    
      <label>Id2:</label><h1>@ViewData["Id2"]</h1>
    <label>姓名2:</label><h1>@ViewData["Name2"]</h1>
    <label>年龄2:</label><h1>@ViewData["Age2"]</h1>
}

 

注意:View视图我们必须将标签的name属性设置和模型绑定名字一样

 

Model Binding(模型绑定)是Http请求和Action方法之间的桥梁,它根据Action方法中的Model类型创建.NET对象,并将Http请求数据经过转换赋给该对象

模型绑定原理

以HomeController下Index的简单例子来查看

public ActionResult Index(int id = 0) {
    viewdata["Id"]=id;
     return view();
}

Action方法是由默认的Action Invoke(即ControllerActionInvoker类)来调用,Action方法是由默认的Action Invoker(即ControllerActionInvker类)来调用,Action Invoker 依靠 Model Binder(模型绑定器) 来创建调用 Action 方法需要的数据对象。我们可以通过 Model Binder 实现的接口来了解它的功能,该接口是 IModelBinder

namespace System.Web.Mvc { 
    public interface IModelBinder { 
        object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext); 
    } 
}

当 action invoker 需要调用一个 action 方法时,它先看这个 action 方法需要的参数,然后为每个参数找到和参数的类型对应的 Model Binder。对于我们以上示例,Action Invoker 会先检查action 方法,发现它有一个int的参数,然后它会定位到负责给其类型提供值的 Binder,并调用该 Binder 的 BindModel 方法。该方法再根据 Action 方法参数名称从路由信息中获取其类型的值,最后把该值提供给 Action Invoker。

 

MVC 框架内置默认的 Model Binder 是 DefaultModelBinder

当Action Invoker没有找到自定义的Binder时,则默认使用DefaultModelBinder,默认情况下DefaultModelBinder从如下4中途径找到绑定的值

1.Request.Form,HTML form 元素提供的值。

2.RouteData.Values,通过应用程序路由提供的值。

3.Request.QueryString,所请求 URL 的 query string 值。

4.Request.Files,客户端上传的文件。

 

DefaultModelBinder会按照上面的顺序来查找对应的值

1.Request.Form["id"]

2.RouteData.Values["id"]

3.Request.QueryString["id"]

4.Request.Files["id"]

复合类型的绑定

复合类型:任何不能被 TypeConverter 类转换的类型(大多指自定义类型),否则称为简单类型,对于复合类型,DefaultModelBinder 类通过反射获取该类型的所有公开属性,然后依次进行绑定。

依然看上面的例子

public class People
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }

在Action方法中

public ActionResult CreatePerson(People model) { 
      return View(model);  
}
默认的 model binder 发现 action 方法需要一个 People对象的参数,会依次处理 People的每个属性。对于每个简单类型的属性,它和前面的例子一样去请求的数据中查找需要的值,例如Request.Form["Id"]去绑定Action参数model中的Id的值

但是如果一个复合类型的属性也是复合类型

public class People
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public Address HomeAddress { get; set; } 
    }

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

表单提交后,model binder 会在 Request.Form["HomeAddress.Country"] 中查找到 People.HomeAddress 的 Country 属性的值。当Model binder 检查到 People类型参数的 HomeAddress 属性是一个复合类型,它会重复之前的查找工作,为 HomeAddress 的每个属性查找值,唯一不同的是,查找的时候用的名称不一样。

应用 Bind 特性

有时候我们还会遇到这样的情况,某个 action 方法的参数类型是某个对象的属性的类型,如下面这个 DisplayAddress action 方法:

public ActionResult DisplayAddress(Address address) { 
    return View(address); 
}
@model MvcApplication1.Models.Person 
...
@using(Html.BeginForm("DisplayAddress", "Home")) { 
    <div>@Html.LabelFor(m => m.PersonId)@Html.EditorFor(m=>m.PersonId)</div> 
    <div> 
        @Html.LabelFor(m => m.HomeAddress.City) 
        @Html.EditorFor(m=> m.HomeAddress.City)
    </div> 
    <div> 
        @Html.LabelFor(m => m.HomeAddress.Country) 
        @Html.EditorFor(m=> m.HomeAddress.Country)
    </div> 
    <button type="submit">Submit</button> 
}

那么我们如何把 Person 类型的对象传递给 DisplayAddress(Address address) 方法呢?点提交按钮后,Binder 能为 Address 类型的参数绑定 Person 对象中的 HomeAddress 属性值吗?

经过验证视图中的值确实不能传递给DisplayAddress中的参数address,不能模型绑定的原因是问题在于生成 form 表单的 name 属性有 HomeAddress 前缀(name="HomeAddress.Country"),model binder 会通过request.form[“Country”]去进行绑定,因为提交上来的name有HoneAddress.Country有了HomeAddress所以无法绑定

这时我们利用Bind绑定特性就可以解决问题

public ActionResult DisplayAddress([Bind(Prefix="HomeAddress")]Address address) {
    return View(address);
}

注意:使用 Bind 特性指定了前缀后,需要提交的表单元素的 name 属性必须有该前缀才能被绑定。

Bind 特性还有两个属性,Exclude 和 Include

Exclude:在绑定时不会对 Address 这个 Model 的 Country 属性绑定值

public ActionResult DisplayAddress([Bind(Prefix = "HomeAddress", Exclude = "Country")]Address address) {
    return View(address);
}

Include:Binder 在给 Address 模型绑定时只会给 Country 属性绑定值

[Bind(Include = "Country")]
public class Address {
    public string City { get; set; }
    public string Country { get; set; }
}

绑定到数组

public ActionResult Names(string[] names) {
        names = names ?? new string[0];
        return View(names);
    }
Names action方法有一个名为 names 的数组参数,Model Binder 将查找所有名称为 names 的条目的值,并创建一个 Array 对象存储它们。
@model string[]
@{
    ViewBag.Title = "Names";
}

<h2>Names</h2>
@if (Model.Length == 0) {
    using (Html.BeginForm()) {
        for (int i = 0; i < 3; i++) {
            <div><label>@(i + 1):</label>@Html.TextBox("names")</div>
        }
        <button type="submit">Submit</button>
    }
}
else {
    foreach (string str in Model) {
        <p>@str</p>
    }
    @Html.ActionLink("Back", "Names");
}

重要:绑定到集合

先创建一个带有 IList<Address> 参数的 action 方法:

public ActionResult Address(IList<Address> addresses) {
    addresses = addresses ?? new List<Address>();
    return View(addresses);
}

在 View 中表单元素的 name 属性应该怎样命名才能被 Model Binder 识别为集合呢?

@using MvcApplication1.Models
@model IList<Address>
@{
    ViewBag.Title = "Address";
}

<h2>Addresses</h2>
@if (Model.Count() == 0) {
    using (Html.BeginForm()) {
        for (int i = 0; i < 2; i++) {
            <fieldset>
                <legend>Address @(i + 1)</legend>
                <div><label>City:</label>@Html.Editor("[" + i + "].City")</div>
                <div><label>Country:</label>@Html.Editor("[" + i + "].Country")</div>
            </fieldset>
        }
        <button type="submit">Submit</button>
    }
}
else {
    foreach (Address str in Model) {
        <p>@str.City, @str.Country</p>
    }
    @Html.ActionLink("Back", "Address");
}

当 Model Binder 发现 Address action 方法需要一个 Address 集合作为参数时,它便从提交的数据中从索引 [0] 开始查找和 Address 的属性名称相同的数据值.

手动调用model Bind方法

使用UpdateModel来指定调用model Bind的方法来指定只从某一数据源来啊获取数据

public ActionResult Address() {
    IList<Address> addresses = new List<Address>();
    UpdateModel(addresses, new FormValueProvider(ControllerContext));
    return View(addresses);
}
UpdateModel 方法指定了第二个参数是一个 FormValueProvider 的实例,它将使用 Model Binder 从只从 Request.Form 中查找需要的数据.FormValueProvider 类是 IValueProvider 接口的实现,是 Value Provider 中的一种,相应的,RouteData.Values、Request.QueryString 和 Request.Files 的 Value Provider 分别是 RouteDataValueProvider、QueryStringValueProvider和HttpFileCollectionValueProvider。

自定义Value Provider

通过自定义 Value Provider 我们可以为 Model Binding 添加自己的数据源。前面我们讲到了四种内置 Value Provider 实现的接口是 IValueProvider,我们可以实现这个接口来自定义一个 Value Provider。先来看这个接口的定义:

namespace System.Web.Mvc { 
    public interface IValueProvider { 
        bool ContainsPrefix(string prefix); 
        ValueProviderResult GetValue(string key); 
    } 
}

ContainsPrefix 方法是 Model Binder 根据给定的前缀用来判断是否要解析所给数据。GetValue 方法根据数据的key返回所需要值。下面我们添加一个 Infrastructure 文件夹,创建一个名为 CountryValueProvider 的类来实现这个接口,代码如下:

public class CountryValueProvider : IValueProvider {
    public bool ContainsPrefix(string prefix) {
        return prefix.ToLower().IndexOf("country") > -1;
    }
    public ValueProviderResult GetValue(string key) {
        if (ContainsPrefix(key))
            return new ValueProviderResult("China", "China", CultureInfo.InvariantCulture);
        else
            return null;
    }
}

这就自定义好了一个 Value Provider,当需要一个 Country 的值时,它始终返回"China",其它返回 null。ValueProviderResult 类的构造器有三个参数,第一个参数是原始值对象,第二个参数是原始对象的字符串表示,最后一个是转换这个值所关联的 culture 信息。

为了让 Model Binder 调用这个 Value Provider,我们需要创建一个能实现化它的类,这个类需要继承  ValueProviderFactory 抽象类。如下我们创建一个这样的类,名为 CustomValueProviderFactory:

 

public class CustomValueProviderFactory : ValueProviderFactory {
    public override IValueProvider GetValueProvider(ControllerContext controllerContext) {
        return new CountryValueProvider();
    }
}

当 model binder 在绑定的过程中需要获取值时会调用这里的 GetValueProvider 方法。这里我们没有做别的处理,直接返回了一个 CountryValueProvider 实例。

最后我们需要在 Global.asax 文件中的 Application_Start 方法中进行注册,如下:

 

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    ValueProviderFactories.Factories.Insert(0, new CustomValueProviderFactory());
...

自定义 Model Binder

 

我们也可以为特定的 Model 自定义 Model Binder。前面讲了默认的 Model Binder 实现的接口是 IModelBinder(前文列出了它的定义),自定义的 Binder 自然也需要实现该接口。下面我们在 Infrastructure 文件夹中添加一个实现了该接口的名为  AddressBinder 类,代码如下:

public class AddressBinder : IModelBinder {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
        Address model = (Address)bindingContext.Model ?? new Address();
        model.City = GetValue(bindingContext, "City");
        model.Country = GetValue(bindingContext, "Country");
        return model;
    }

    private string GetValue(ModelBindingContext context, string name) {
        name = (context.ModelName == "" ? "" : context.ModelName + ".") + name;
        ValueProviderResult result = context.ValueProvider.GetValue(name);
        if (result == null || result.AttemptedValue == "") 
            return "<Not Specified>";
        else 
            return (string)result.AttemptedValue;
    }
}

当 MVC 框架需要一个 model 类型的实现时,则调用 BindModel 方法。它的 ControllerContext 类型参数提供请求相关的上下文信息,ModelBindingContext 类型参数提供 model 对象相关的上下文信息。ModelBindingContext 常用的属性有Model、ModelName、ModelType 和 ValueProvider。这里的 GetValue 方法用到的 context.ModelName 属性可以告诉我们,如果有前缀(一般指复合类型名),则需要把它加在属性名的前面,这样 MVC 才能获取到以 [0].City、[0].Country 名称传递的值。

然后我们需要在 Global.asax 的 Application_Start 方法中对自定义的 Model Binder 进行注册,如下所示:

 

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    //ValueProviderFactories.Factories.Insert(0, new CustomValueProviderFactory());
    ModelBinders.Binders.Add(typeof(Address), new AddressBinder());
...
posted @ 2014-11-11 16:58  草旅虫  阅读(586)  评论(0编辑  收藏  举报