天天看點

精通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; }
    }