天天看点

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

模型绑定(Model Binding)是指,用浏览器以Http请求方式发送的数据来创建.Net对象的过程。

准备示例项目 

新建一个空的MVC项目,名叫MvcModels,接下去会以此项目来演示各种功能。

在Models文件夹中创建一个Person.cs类文件,代码如下图所示:

namespace MvcModels.Models
{
    public class Person
    {
        public int PersonId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime BirthDate { get; set; }
        public Address HomeAddress { get; set; }
        public bool IsApproved { get; set; }
        public Role Role { get; set; }
    }

    public class Address
    {
        public string Line1 { get; set; }
        public string Line2 { get; set; }
        public string City { get; set; }
        public string PostalCode { get; set; }
        public string Country { get; set; }
    }

    public enum Role
    {
        Admin,
        User,
        Guest
    }
}
           

定义一个Home控制器,代码如下图所示:

public class HomeController : Controller
    {
        private Person[] personData = {
            new Person { PersonId = 1,FirstName = "Adam",LastName = "Freeman" },
            new Person { PersonId = 2,FirstName = "Jacqui",LastName = "Griffyth"},
            new Person { PersonId = 3,FirstName = "John",LastName = "Smith" },
            new Person { PersonId = 4,FirstName = "Anne",LastName = "Jones"}
        };
        // GET: Home
        public ActionResult Index(int id)
        {
            Person dataItem = personData.Where(p => p.PersonId == id).First();
            return View(dataItem);
        }
    }
           

新增Index控制器对应的Index.cshtml页面,代码如下: 

@model MvcModels.Models.Person

@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Person</h2>
<div><label>ID:</label> @Html.DisplayFor(m => m.PersonId)</div>
<div><label>First Name:</label>@Html.DisplayFor(m => m.FirstName)</div>
<div><label>Last Name:</label>@Html.DisplayFor(m => m.LastName)</div>
<div><label>Roles:</label>@Html.DisplayFor(m => m.Role)</div>
           

新增_layout.cshtml布局页面,代码如下: 

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <style>
        label {
            display:inline-block; width:100px;font-weight:bold;margin:5px;
        }
        form label {
            float:left;
        }
        input.text-box {
            float:left;margin:5px
        }
        button[type=submit] {
            margin-top:5px;
            float:left;
            clear:left;
        }
        form div {
            clear:both;
        }
    </style>
</head>
<body>
    <div>
        @RenderBody()
    </div>
</body>
</html>
           

运行程序,并导航到/Home/Index/1,结果如下图所示: 

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

默认的动作绑定器ControllerActionInvoker要依靠模型绑定器来生成调用动作所需的数据对象。模型绑定器由IModelBinder接口所定义,接口如下图所示:

public interface IModelBinder
{
    object BindModel(ControllerContext controllerContext,ModelBindingContext bindingContext)
}
           

在一个MVC应用程序中,可以有多个模型绑定器,而每个绑定器可以负责绑定一个或者多个模型类型。它会考察该方法所定义的参数,并查找各个参数类型所依赖的模型绑定器。

在上述示例中,动作调用器会检查Index方法,并发现它具有一个int型的参数。于是会查找负责int值绑定的绑定器,并调用BindModel方法。

使用默认的模型绑定器

虽然程序可以定义自定义的模型绑定器,大多数程序都是依靠内建的模型绑定器DefaultModelBinder.当动作调用器找不到绑定某个类型的自定义绑定器时,这个默认的模型绑定器便是由动作调用器所使用的一个绑定器。默认情况下,这个模型绑定器会搜索四个位置:

DefaultModelBinder类查找参数数据的顺序

描述
Request.Form 由用户在HTML的form(表单)元素中提供的值
RouteData.Values 用应用程序路由获得的值
Request.QueryString 包含在请求URL中的查询字符串部分的数据
Request.Files 请求中上传的文件

这些位置被依序搜索。例如,在上述简单示例中,DefaultModelBinder会为id参数查找以下的一个值:

1、Request.Form["id"]

2、RouteData.Values["id"]

3、Request.QueryString["id"]

4、Request.Files["id"]

只要找到值,便会停止搜索。在上述例子中,搜索到第二步就停了,不会到第三步。

当处理简单参数类型时,DefaultModelBinder会尝试使用 System.ComponentModel.TypeDescriptor类。将已经从请求数据获得的字符串值转化成参数类型。如果无法转为这个值:例如给int值传一个“apple”,程序就会报错:

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

解决这个问题有两种办法:

一、在动作方法参数中设置可空类型(nullable),这为绑定器提供一个退路,一个可空的int参数可以不必为数字值,这让模型绑定器在调用动作时,这可以让动作方法参数设置为Null。

public ActionResult Index(int? id)
           

二、 在动作方法中运用默认值,当模型绑定器无法为id参数找到一个值时,将默认值1来代替,如下所示:

public ActionResult Index(int id  = 1)
           

 绑定复杂类型

当动作方法的参数是复合类型时,DefaultModelBinder类将用反射来获取public属性集。

在Home控制器中,新增如下两个动作方法: 

public class HomeController : Controller
    {
        private Person[] personData = {
            new Person { PersonId = 1,FirstName = "Adam",LastName = "Freeman" },
            new Person { PersonId = 2,FirstName = "Jacqui",LastName = "Griffyth"},
            new Person { PersonId = 3,FirstName = "John",LastName = "Smith" },
            new Person { PersonId = 4,FirstName = "Anne",LastName = "Jones"}
        };
        // GET: Home
        public ActionResult Index(int? id  = 1)
        {
            Person dataItem = personData.Where(p => p.PersonId == id).First();
            return View(dataItem);
        }

        public ActionResult CreatePerson()
        {
            return View(new Person());
        }

        [HttpPost]
        public ActionResult CreatePerson(Person model)
        {
            return View("Index",model);
        }
    }
           

为没有参数的CreatePerson控制器方法创建一个对应的视图:CreatePerson.cshtml ,代码如下图所示:

@model MvcModels.Models.Person
@{
    ViewBag.Title = "CreatePerson";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>CreatePerson</h2>
@using (Html.BeginForm())
{
    <div>@Html.LabelFor(m => m.PersonId) @Html.EditorFor(m => m.PersonId)</div>
    <div>@Html.LabelFor(m => m.FirstName) @Html.EditorFor(m => m.FirstName)</div>
    <div>@Html.LabelFor(m => m.LastName) @Html.EditorFor(m => m.LastName)</div>
    <div>@Html.LabelFor(m => m.Role) @Html.EditorFor(m => m.Role)</div>
    <button type="submit">Submit</button>
}
           

运行导航到/Home/CreatePerson,结果如下图所示:

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

点击submit按钮后,可以看到已经将输入的数据  传到 Index 界面了:

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

在表单传递给CreatePerson方法时,形成了一种不同的模型绑定情况。默认的模型绑定器发现,动作方法需要一个Person对象,于是会依次处理每个属性。 对于每个简单类型的属性,绑定器会视图查找请求中的一个值,就如同上一个示例所做的那样。因此,当遇到 PersonId属性时,绑定器会查找personId的数据值,它将在请求的表单中发现这个值。

如果一个属性需要另一个复合类型,那么,该过程会针对新类型重复执行。获取该类型的public属性集,而绑定器会视图找出这些属性的值。不同的是,这些属性是嵌套的。例如,Person类的HomeAddress 属性 是Address类型。

创建易于绑定的HTML

更新CreatePerson.cshtml中的代码,以便为Address类型捕获一些属性:

@model MvcModels.Models.Person
@{
    ViewBag.Title = "CreatePerson";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>CreatePerson</h2>
@using (Html.BeginForm())
{
    <div>@Html.LabelFor(m => m.PersonId) @Html.EditorFor(m => m.PersonId)</div>
    <div>@Html.LabelFor(m => m.FirstName) @Html.EditorFor(m => m.FirstName)</div>
    <div>@Html.LabelFor(m => m.LastName) @Html.EditorFor(m => m.LastName)</div>
    <div>@Html.LabelFor(m => m.Role) @Html.EditorFor(m => m.Role)</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>
}
           

更新Index.html中的代码,如下图所示: 

@model MvcModels.Models.Person

@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Person</h2>
<div><label>ID:</label> @Html.DisplayFor(m => m.PersonId)</div>
<div><label>First Name:</label>@Html.DisplayFor(m => m.FirstName)</div>
<div><label>Last Name:</label>@Html.DisplayFor(m => m.LastName)</div>
<div><label>Roles:</label>@Html.DisplayFor(m => m.Role)</div>

<div><label>City:</label>@Html.DisplayFor(m => m.HomeAddress.City)</div>
<div><label>County:</label>@Html.DisplayFor(m => m.HomeAddress.Country)</div>
           

运行导航到/Home/CreatePerson,如下图所示:

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

点击Submit按钮后,数据传递成功,情况如下:

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

简而言之,模型绑定器查找的是 HomeAddress.Country,即,模型对象的属性名(HomeAddress)与属性类型(Address)d的属性名(Country)的组合。 

指定自定义前缀 

偶尔有些时候,生成的HTML与一种类型的对象有关,但是希望绑定到另一个对象。这意味着包含的前缀与模型绑定器期望的结构不对应。这个时候需要用到属性注解了。

在Models文件夹中创建了一个新的类文件,名称为AddressSummary.cs,如下图所示:

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

在Home控制器中增加一个动作方法,如下图所示: 

public ActionResult DisplaySummary(AddressSummary summary)
        {
            return View(summary);
        }
           

修改CreatePerson.cshtml文件中表达提交的目标:

@model MvcModels.Models.Person
@{
    ViewBag.Title = "CreatePerson";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>CreatePerson</h2>
@using (Html.BeginForm("DisplaySummary","Home"))
{
    <div>@Html.LabelFor(m => m.PersonId) @Html.EditorFor(m => m.PersonId)</div>
    <div>@Html.LabelFor(m => m.FirstName) @Html.EditorFor(m => m.FirstName)</div>
    <div>@Html.LabelFor(m => m.LastName) @Html.EditorFor(m => m.LastName)</div>
    <div>@Html.LabelFor(m => m.Role) @Html.EditorFor(m => m.Role)</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>
}
           

导航到/Home/CreatePerson方法: 

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

点击submit方法后,结果如下图所示: 

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

 由于Country和City的前缀改变了,由HomeAddress变成了AddressSummary,故绑定器无法实现绑定。只需对动作方法的参数运用Bind注解属性即可,代码如下图所示:

public ActionResult DisplaySummary([Bind(Prefix ="HomeAddress")]AddressSummary summary)
{
     return View(summary);
}
           

重新运行代码,即可看到运行成功: 

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

有选择性的绑定属性 

如果希望对某一属性不需要模型绑定器进行绑定,可以使用如下代码:

public ActionResult DisplaySummary([Bind(Prefix ="HomeAddress",Exclude ="Country")]AddressSummary summary)
{
     return View(summary);
}
           

也可以设置模型绑定器只绑定某一属性,代码如下图所示:

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

绑定到数组

默认模型绑定器的一个雅致的特性是它支持动作方法参数作为数组。在Home控制器中添加一个方法,如下图所示:

public ActionResult Names(string[] names)
        {
            names = names ?? new string[0];
            return View(names);
        }
           

创建Names方法对应的视图Names.csthml,如下图所示: 

@model string[]
@{
    ViewBag.Title = "Names";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

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

导航到/Home/Names,如下图所示:

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

点击Submit后,如下图所示: 

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

递交表单时,默认的模型绑定器明白动作方法需要一个字符串数组。于是会查找与参数具有同样名称的数据项。在本例中,意味着会将所有input元素的内容聚集到一起用以填充数组。 

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

绑定到集合 

能绑定的不仅仅是数组,还可以使用.Net集合类。

修改names动作方法为强类型集合,如下图所示:

public ActionResult Names(IList<string> names)
        {
            names = names ?? new List<string>();
            return View(names);
        }
           

并修改Names.cshtml页面代码,如下所示:

@model IList<string>
@{
    ViewBag.Title = "Names";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Names</h2>
@if (Model.Count() == 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")
}
           

运行结果如下图所示:

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

点击提交后: 

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

绑定到自定义模型集合 

可以将一些单个的数据属性绑定成一个自定义类型的数组。如上述的AddressSummary模型类.

在控制器中增加一个新的动作方法,如下图所示:

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

添加Address动作方法对应的页面,代码如下图所示:

@using MvcModels.Models
@model IList<AddressSummary>
@{
    ViewBag.Title = "Address";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h2>Address</h2>
@if (Model.Count() == 0)
{
    using (Html.BeginForm())
    {
        for (int i = 0; i < 3; 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 (AddressSummary str in Model)
    {
        <p> @str.City, @str.Country</p>
    }
    @Html.ActionLink("Back","Address");
}
           

 导航到/Home/Address,并输入内容,页面如下图所示:

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

点击提交后,正确显示页面:

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

可以看到生成的HTML代码: 

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

当表单被提交时,默认的模型绑定器知道它需要创建的是一个AddressSummary对象集合,并利用 name 标签属性中的数组索引前缀获取对象的类型。以【0】为前缀的那些属性表示一个AddressSummary对象,以【1】为前缀表示第二个对象。以此类推。 

手工调用模型绑定

当动作方法定义了参数时,模型绑定过程是自动执行的,但是只要你愿意,也可以直接控制这一过程。如下将演示如何将Home控制器的Address动作方法修改成手动调用绑定过程。

修改Address方法,代码如下图所示:

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

UpdataModel方法以上一条语句定义的时候的模型对象为参数,并试图用标准的绑定该过程来获取其public属性的值。 

当手工调用绑定时,可以将绑定过程限制到单一的数据源。默认情况下,绑定器会搜索四个地方:表单数据、路由数据、查询字符串,以及上传文件。一下代码演示了如何将绑定器限制到搜索单一位置的数据——表单数据。

public ActionResult Address(IList<AddressSummary> addresses)
        {
            addresses = addresses ?? new List<AddressSummary>();
            UpdateModel(addresses,new FormValueProvider(ControllerContext));
            return View(addresses);
        }
           

UpdateModel方法的这一版本以IValueProvider接口的一个实现为参数,该实现也成为了绑定过程的唯一的数据源。四个默认的数据位置的每一个都由一个IValueProvider实现表示:

内建的IValueProvider

IValueProvider
Request.Form FormValueProvider
RouteData.Value RouteDataValueProvider
Request.QueryString QueryStringValueProvider
Request.Files HttpFileCollectionValueProvider

可以用另一种跟优雅的写法来表示:

public ActionResult Address(FormCollection formData)
        {
            List<AddressSummary> addresses = new List<AddressSummary>();
            UpdateModel(addresses,formData);
            return View(addresses);
        }
           

 处理绑定错误

用户难免会提供一些不能绑定到相应模型属性的值,需要对这些情况抛出些异常。特别是使用UpdateModel方法时,必须做好捕捉该异常的准备,代码如下图所示:

public ActionResult Address(FormCollection formData)
        {
            List<AddressSummary> addresses = new List<AddressSummary>();
            try
            {
                UpdateModel(addresses, formData);
            }
            catch (InvalidCastException ex)
            {
                //给用户提供反馈
            }
            return View(addresses);
        }
           

另一个可选的办法是,可以使用TryUpdateModel方法。如果模型绑定成功,返回ture;否则返回false;

public ActionResult Address(FormCollection formData)
        {
            List<AddressSummary> addresses = new List<AddressSummary>();

            if (TryUpdateModel(addresses,formData))
            {
                //正常处理
            }
            else
            {
                //给用户提供反馈
            }
            return View(addresses);
        }
           

这两种方式的唯一区别是,你是否喜欢捕捉并处理异常。 

定制模型绑定系统

还有一些不同的方式,可以对绑定系统进行定制。

通过定义一个自定义的值提供器,可以将自己的数据源添加到模型绑定过程。值提供器(Valueprovider)需要实现IValueProvider接口,如下图所示:

public interface IValueProvider
    {
        bool ContainsPrefix(string prefix);

        ValueProviderResult GetValue(string key);
    }
           

ContainsPrefix方法由模型绑定器调用,以确定这个值提供器是否可以解析给定前缀的数据。

GetValue方法返回给定数据键的值,或者在提供器无法得到合适的数据时返回null。

新建一个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("USA", "USA", CultureInfo.InvariantCulture);
            }
            else
            {
                return null;
            }
        }
    }
           

该值提供器只对请求Country属性的值进行响应,而且总是返回 USA 。对于其他请求,返回 NULL,表示无法提供数据。

返回值必须提供一个ValueProviderResult类来返回。这个类有三个构造器参数:第一个参数是与请求键关联的数据项,第二个参数是作为HTML页面一部分的该数据的安全显示形式,第三个参数是该值相关的文化信息。这里已经指定为了InvariantCulture。

为了在应用程序中对这个值进行注册,需要一个工厂类,以便在MVC框架需要时为这个提供器创建实例。这个工厂类必须派生于抽象类ValueProviderFactory。代码如下图所示:

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

 当模型绑定器要为绑定过程获取值时,会调用这个GetValueProvider方法。上述实现了简单的创建并返回了CurrentTimeProvider类的一个实例,但你可以使用ControllerContext参数提供的数据,以便创建不同的值提供器,对不同种类的请求进行响应。

然后在Global.asax的Application_Start方法中注册:

protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            ValueProviderFactories.Factories.Insert(0,new CustomValueProviderFactory());
        }
           

如果希望这一提供器在其他提供器不能提供数据值时作为一个备选,那么可以用Add方法把工厂追加到集合末尾: 

ValueProviderFactories.Factories.Add(new CustomValueProviderFactory());
           

运行程序,导航到/Home/Address,如下图所示:

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

点击Submit,如下图所示:

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

 创建自定义模板绑定器

通过创建一个特定类型的自定义模型绑定器,可以覆盖默认绑定器的行为。自定义模型绑定器需要实现IModelBinder接口。创建一个AddressSummaryBinder.cs类文件,如下图所示:

public class AddressSummaryBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            AddressSummary model = (AddressSummary)bindingContext.Model ?? new AddressSummary();
            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;
            }
        }
    }
           

BindModel方法的参数是一个ControllerContext对象,可以用它来访问当前请求的细节。另一个是ModelBindingContext对象,该对象提供了当前寻找的模型对象的细节,并能访问MVC应用程序中其他模型绑定工具。 

ModelBindingContext类所定义的最有用的属性

属性 描述
Model 如果手工调用了绑定,可返回传递给UpdataModel方法的模型对象
ModelName 返回被绑定模型的名称
ModelType 返回被创建模型的类型
ValueProvider 返回能用于请求中获得数据值的IValueProvider实现

在调用BindModel方法时,检查已经是否设置了 ModelBindingContext 对象 的Model属性,如果已经设置,则该模型便是将要为之生成数据值的对象,如没有设置,则创建AddressSummary类的一个实例。通过调用GetValue方法获取City和Country属性的值,然后返回已经过填充的AddressSummary对象。

在GetValue方法中,通过了ModelBindingContext.ValueProvider属性获得的IValueProvider实现,以获取模型对象属性的值。

ModelName属性能够告诉我们,对正在寻找的属性的名称,是否需要追加一个前缀。当无法为一个属性找到值,或者该属性为空字符串时,便提供一个默认值<Not Specified>.

然后在Global.asax的Application_Start方法中注册该模型绑定器:

protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);

            ModelBinders.Binders.Add(typeof(AddressSummary), new AddressSummaryBinder());
        }
           

导航到/Home/Address,并输入内容,如下图所示: 

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

提交后,结构如下图所示: 

精通ASP.NET MVC ——模型绑定准备示例项目 使用默认的模型绑定器 绑定复杂类型创建易于绑定的HTML指定自定义前缀 有选择性的绑定属性 绑定到数组绑定到集合 绑定到自定义模型集合 手工调用模型绑定 处理绑定错误定制模型绑定系统 创建自定义模板绑定器 用注解属性注册模型绑定器

 用注解属性注册模型绑定器

可以在模型类上使用ModelBinder注解属性进行修饰,来注册自定义模型绑定器。不必使用Global.asax文件。如下图所示:

[ModelBinder(typeof(AddressSummaryBinder))]
    public class AddressSummary
    {
        public string City { get; set; }
        public string Country { get; set; }
    }