模型綁定(Model Binding)是使用瀏覽器發起Http請求時的數據建立.NET對象的過程。咱們每一次定義帶參數的action方法時就已經依靠了模型綁定——這些參數對象是經過模型綁定建立的。這一章會介紹模型綁定的原理以及針對高級使用必要的定製模型綁定的技術。css
理解模型綁定(Understanding Model Binding)html
想象下咱們建立了一個控制器以下:數組
using System; using System.Web.Mvc; using MvcApp.Models; namespace MvcApp.Controllers { public class HomeController : Controller { public ViewResult Person(int id) { // 獲取一條person記錄 Person myPerson = null; //檢索數據的邏輯... return View(myPerson); } } }
action方法定義在HomeController類裏面,VS默認建立的路由就是調用這裏的action方法。當咱們請求一個如/Home/Person/23的URL,MVC框架會將請求的詳細信息映射經過一種傳遞合適的值或對象做爲參數的方式映射到action方法。action調用者負責在調用action以前獲取這些值,默認的action調用者ControllerActionInvoker依賴於Model Binders,它們是經過IModelBinder接口定義的,以下:瀏覽器
namespace System.Web.Mvc { public interface IModelBinder { object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext); } }
在MVC程序裏面能夠有多個model binders,每個binder能夠綁定一個或多個model類型。當action調用者須要調用一個action方法,它會尋找定義在方法裏面的參數而且找到對應負責每個參數類型的model binder。在最開始的例子裏面,action調用者會發現咱們的action方法具備一個int型的參數,因此它會定位到負責綁定int值的binder並調用本身的BindModel方法,若是沒有可以處理int值的binder,那麼默認的model binder會被使用。mvc
model binder是用來生成匹配action方法的參數值,這一般意味着傳遞一些請求元素的數據(例如form或query string值),可是MVC框架不會對如何獲取這些值有任何限制。框架
使用默認的Model Binder(Using the Default Model Binder)ide
儘管一個應用程序有多個binders,大多數都是依賴於內置的binder類——DefaultModelBinder。這也是當action調用者找不到自定義的binder時使用的binder。默認狀況下,這個model binder搜索了4個路徑,以下所示: Request.Form:HTML表單提供的值 RouteData.Values:使用應用程序路由獲取的值 Request.QueryString:包含在URL的請求字符串裏面的數據 Request.Files:做爲請求部分被上傳的文件測試
上面四個路徑是按順序搜索的,例如在上面的例子中,action方法須要一個參數id,DefaultModelBinder會檢查action方法並尋找名爲id的參數。它會按下面的順序來尋找: 1. Request.Form["id"] 2. RouteData.Values["id"] 3. Request.QueryString["id"] 4. Request.Files["id"] 只要有一個值找到,搜索就會中止。this
綁定簡單類型(Binding to Simple Types)spa
當處理簡單的參數類型時,DefaultModelBinder會試圖使用System.ComponentModel.TypeDescriptor類將request數據(字符串型)轉換爲對應action方法參數的類型。若是這個值不能轉換,那麼DefaultModelBinder將不可以綁定到model。若是要避免這個問題,能夠修改下參數,如:public ViewResult RegisterPerson(int? id) {...},這樣修改之後,若是不能匹配,參數的值會爲null。還能夠提供一個默認值如:public ViewResult RegisterPerson(int id = 23) {...}
綁定複雜類型(Binding to Complex Types)
若是action方法參數是一個複雜類型(就是不能使用TypeConverter轉換的類型),那麼DefaultModelBinder會使用反射獲取公共的屬性並輪流綁定每個屬性。使用前面的Person.cs來舉例,以下:
public class Person { [HiddenInput(DisplayValue=false)] public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [DataType(DataType.Date)] 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; } }
默認的model binder會檢查這個類的屬性是否都是簡單類型,若是是,binder就會在請求裏面具備相同的名稱的數據項。對應例子來講就是FirstName屬性會引發binder尋找一個名爲FirstName的數據項。若是這個類的屬性(如Address)仍然是個複雜類型,那麼對這個類型重複上面的處理過程。在尋找Line1屬性的值時,model binder會尋找HomeAddress.Line1的值。
指定自定義的前綴(Specifying Custom Prefixes)
當默認的model binder尋找對應的數據項時,咱們能夠指定一個自定義的前綴。這對於在HTML裏包含了額外的model對象時很是有用。舉例以下:
@using MvcApp.Models;
@model MvcApp.Models.Person
@{
Person myPerson = new Person() { FirstName = "Jane", LastName = "Doe" }; } @using (Html.BeginForm()) { @Html.EditorFor(m => myPerson) @Html.EditorForModel() <input type="submit" value="Submit" /> }
咱們使用了EditorFor helper方法來對Person對象生成HTML,lambda表達式的輸入是一個model對象(用m代替),當使用這種方式之後,生成的HTML元素的屬性名會有一個前綴,這個前綴來源於咱們在EditorFor裏面的變量名myPerson。運行之後能夠看到頁面源代碼以下:
public ActionResult Index(Person firstPerson,Person myPerson){...},第一個參數對象使用沒有前綴的數據綁定,第二個參數尋找以參數名開頭的數據綁定。 若是咱們不想用這種方式,可使用Bind特性來指定,以下: public ActionResult Register(Person firstPerson, [Bind(Prefix="myPerson")] Person secondPerson) 這樣就設置了Prefix屬性的值爲myPerson,這意味着默認的model binder將使用myPerson做爲數據項的前綴,即便這裏第二個參數的名爲secondPerson。
有選擇的綁定屬性(Selectively Binding Properties)
想象一下若是Person類的IsApproved屬性是很是敏感的信息,咱們可以經過模版綁定來不呈現該屬性,可是一些惡意的用戶能夠簡單的在一個URL裏附加?/IsAdmin=true後來提交表單。若是這種狀況發生,model binder在綁定的過程會識別並使用這個數據的值。幸運的是,咱們可使用"Bind"特性來從綁定過程包含或排除model的屬性。具體的示例以下:
public ActionResult Register([Bind(Include="FirstName, LastName")] Person person) {...}//僅僅包含Person屬性裏面的FirstName和LastName屬性 public ActionResult Register([Bind(Exclude="IsApproved, Role")] Person person) {...}//排除了IsApproved屬性
上面這樣使用Bind僅僅是針對單個的action方法,若是想將這種策略應用到全部控制器的全部action方法,能夠在model類自己使用該特性,以下:
[Bind(Exclude = "IsApproved")] public class Person { [HiddenInput(DisplayValue = false)] public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [DataType(DataType.Date)] public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } public bool IsApproved { get; set; } public Role Role { get; set; } public DateTime CurrentTime { get; set; } }
這樣就會在全部的用到給model的action方法生效。
注:若是Bind特性被應用到model類而且也在action方法的參數中使用,在沒有其餘的應用程序特性排除它時會被包含在綁定過來裏。這意味着應用到model的類的策略不能經過應用一個較小限制策略到action方法參數來重寫。下面用示例說明:
首先添加一個Model Person以下:
using System.Web.Mvc; using System.ComponentModel.DataAnnotations; namespace ModelBinding.Models { [Bind(Exclude = "IsApproved")] public class Person { [HiddenInput(DisplayValue = false)] public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [DataType(DataType.Date)] public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } public bool IsApproved { get; set; } public Role Role { get; set; } public DateTime CurrentTime { 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 } }
對Person類添加了Bind特性,排除了IsApproved屬性,而後添加Controller以下:
public class HomeController : Controller { public ActionResult Index() { Person myPerson = new Person { PersonId = 1, FirstName = "Joe", LastName = "Smith", BirthDate = DateTime.Parse("1988/12/01"), HomeAddress = new Address { Line1 = "123 North Street", Line2 = "West Bridge", City = "London", Country = "UK", PostalCode = "WC2R 1SS" }, IsApproved = true, Role = Role.User }; return View("PersonEdit", myPerson); } [HttpPost] public ActionResult Index(Person person, Person myPerson) { return View("PersonDisplay", person); } }
最後添加兩個涉及的視圖PersonEdit和PersonDisplay,以下:
//PersonEdit.cshtml @using ModelBinding.Models; @model ModelBinding.Models.Person <style type="text/css"> .check-box { margin: 0.5em 0 0 0; } </style> @{ Person myPerson = new Person() { FirstName = "xuefei", LastName = "zhang" }; } @using (Html.BeginForm()) { @Html.EditorFor(m => myPerson) @Html.EditorForModel() <input type="submit" value="Submit" /> } //PersonDisplay.cshtml @model ModelBinding.Models.Person <div class="column"> @Html.DisplayForModel() </div> <div class="column"> @Html.DisplayFor(m => m.HomeAddress) </div>
運行程序以下:
另外,咱們在URL裏面添加?IsApproved=true試試看有什麼效果:
接着繼續測試,剛纔不是有說到關於策略重寫的問題嗎,這裏咱們對【HttpPost】的Index action的參數添加一個Bing特性以下:
[HttpPost]
public ActionResult Index([Bind(Include = "IsApproved")]Person person, Person myPerson) { return View("PersonDisplay", person); }
理論上這裏的是沒有辦法對Person上應用的策略進行重寫的,有圖爲證:
綁定到數組和集合(Binding to Arrays and Collections)
處理具備相通名字的多條數據項是默認的model binder的一個很是優雅的功能,示例說明以下: 建立兩個視圖Movies和MoviesDisplay,以下:
@*Movies*@
@{
ViewBag.Title = "Movies"; } 輸入三部你最喜好的影片名: @using (Html.BeginForm()) { @Html.TextBox("movies") @Html.TextBox("movies") @Html.TextBox("movies") <input type="submit" /> } @*MoviesDisplay*@ @model List<string> @{ ViewBag.Title = "MoviesDisplay"; } 你最喜好的電影: @foreach (string movie in Model) { <p>@movie</p> }
添加對應的action,以下:
public ViewResult Movies() { return View(); } [HttpPost] public ViewResult Movies(List<string> movies) { return View("MoviesDisplay", movies); }
model binder會尋找用戶提交的全部值並把它們經過List<string>集合傳遞到Movies action方法,binder是足夠的聰明的識別不一樣的參數類型,例如咱們能夠將List<string>改爲IList<string>或是string[]。
綁定到自定義類型的集合(Binding to Collections of Custom Types)
上面的多個值的綁定技巧很是好用,但若是咱們想應用到自定義的類型,就必須用一種合適的格式來生成HTML。添加MPerson視圖和MPersonDisplay視圖以下:
@*MPerson.cshtml*@
@model List<ModelBinding.Models.Person> @{ ViewBag.Title = "MPerson"; } @using (Html.BeginForm()) { <h4> First Person</h4> <input type="hidden" name="[0].key" value="firstPerson" /> @:First Name:@Html.TextBox("[0].value.FirstName") @:Last Name:@Html.TextBox("[0].value.LastName") <h4> Second Person</h4> <input type="hidden" name="[1].key" value="secondPerson" /> @:First Name:@Html.TextBox("[1].value.FirstName") @:Last Name:@Html.TextBox("[1].value.LastName") <input type="submit" /> } @*MPersonDisplay*@ @using ModelBinding.Models; @model IDictionary<string, ModelBinding.Models.Person> @foreach (string key in Model.Keys) { @Html.DisplayFor(m => m[key]); }
添加Controller,以下:
public ViewResult MPerson() { List<Person> people = new List<Person> { new Person{FirstName="xuefei",LastName="zhang"}, new Person{FirstName="si",LastName="Li"} }; return View(people); } [HttpPost] public ViewResult MPerson(IDictionary<string, Person> people) { return View("MPersonDisplay", people); }
運行程序能夠看到效果,要綁定這些數據,咱們僅僅定義了一個action並接收一個視圖model類型的集合參數,如:
[HttpPost] public ViewResult Register(List<Person> people) {...} 由於咱們綁定到一個集合,默認的model binder會搜索用一個索引作前綴的Person類的屬性。固然,咱們沒必要使用模版化的helper方法來生成HTML,能夠顯示地在視圖裏面作,以下:
<h4>First Person</h4>
First Name: @Html.TextBox("[0].FirstName") Last Name: @Html.TextBox("[0].LastName") <h4>Second Person</h4> First Name: @Html.TextBox("[1].FirstName") Last Name: @Html.TextBox("[1].LastName")
只要咱們保證了索引值被恰當的建立,model binder會找到並綁定全部定義的數據元素。
使用非線性的索引綁定到集合(Binding to Collections with Nonsequential Indices)
除了上面使用數字序列的索引值外,還可使用字符串來做爲鍵值,這在當咱們想要使用js在客戶端動態的添加或移除控件時很是有用,並且不用去維護索引的順序。採用這種方式須要定義一個hidden input元素name爲指定key的index。以下:
<h4>First Person</h4> <input type="hidden" name="index" value="firstPerson"/> First Name: @Html.TextBox("[firstPerson].FirstName") Last Name: @Html.TextBox("[firstPerson].LastName") <h4>Second Person</h4> <input type="hidden" name="index" value="secondPerson"/> First Name: @Html.TextBox("[secondPerson].FirstName") Last Name: @Html.TextBox("[secondPerson].LastName")
咱們用input元素的前綴來匹配index隱藏域的值,model binder會檢測到index並使用它在綁定過程當中關聯數據的值。
綁定到一個Dictionary(Binding to a Dictionary)
默認的model binder是可以綁定到一個Dictionary的,可是隻有當咱們遵循一個很是具體的命名序列時才行。以下:
<h4>First Person</h4>
<input type="hidden" name="[0].key" value="firstPerson"/> First Name: @Html.TextBox("[0].value.FirstName") Last Name: @Html.TextBox("[0].value.LastName") <h4>Second Person</h4> <input type="hidden" name="[1].key" value="secondPerson"/> First Name: @Html.TextBox("[1].value.FirstName") Last Name: @Html.TextBox("[1].value.LastName")
此時可使用以下的action來獲取值 [HttpPost] public ViewResult Register(IDictionary<string, Person> people) {...}
手動調用模型綁定(Manually Invoking Model Binding)
模型綁定的過程是在一個action方法定義了參數時自動執行的,可是能夠直接控制這個過程。這給了咱們對於model對象如何實例化,數據的值從哪裏獲取,以及數據強制轉換錯誤如何處理等更多明確的控制權。示例以下:
//Controller裏添加action [HttpPost] public ActionResult RegisterMember() { Person myPerson = new Person(); UpdateModel(myPerson); return View(myPerson); } //添加Register視圖 @using ModelBinding.Models; @model ModelBinding.Models.Person <style type="text/css"> .check-box { margin: 0.5em 0 0 0; } </style> @using (Html.BeginForm("RegisterMember", "Home")) { @Html.EditorForModel() <input type="submit" value="Submit" /> }
UpdateModel方法獲取一個model對象做爲參數並試圖使用標準綁定過程獲取model對象裏面公共屬性的值。手動調用model綁定的其中一個緣由是爲了支持DI。例如,若是咱們使用了一個應用程序範圍的依賴解析器,那麼咱們可以添加DI到這裏的Person對象的建立,以下:
[HttpPost]
public ActionResult RegisterMember() { Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person)); UpdateModel(myPerson); return View(myPerson); }
正如咱們闡釋的,這不是在綁定過程引入DI的惟一方式,後面還會介紹其餘的方式。
將綁定限制到指定的數據源(Restricting Binding to a Specific Data Source)
當咱們手動的調用綁定時,能夠限制綁定到指定的數據源。默認狀況下,bingder會尋找四個地方:表單數據,路由數據,querystring,以及上傳的文體。下面例子說明如何限制綁定到單個數據源——表單數據。修改action方法以下:
[HttpPost]
public ActionResult RegisterMember() { //Person myPerson = new Person(); //UpdateModel(myPerson); Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person)); UpdateModel(myPerson, new FormValueProvider(ControllerContext)); return View(myPerson); }
這裏的UpdateModel是重載的版本接收一個IValueProvider接口實現做爲參數,從而指定了綁定過程的數據源。每個默認的數據源都對應了一個對該接口的實現,以下: 1.Request.Form——>FormValueProvider 2.RouteData.Values——>RouteDataValueProvider 3.Request.QueryString——>QueryStringValueProvider 4.Request.Files——>HttpFileCollectionValueProvider
最經常使用的如今數據源的方式就是隻在尋找Form裏面的值,有一個很是靈巧的綁定技巧,以致於咱們不用建立一個FormValueProvider的實例,以下:
[HttpPost]
public ActionResult RegisterMember(FormCollection formData) { Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person)); UpdateModel(myPerson, formData); return View(myPerson); }
FormCollection類實現了IValueProvider接口,而且若是咱們定義的action方法接收一個該類型的參數,model binder會提供一個能夠直接傳遞給UpdateModel方法的對象。
處理綁定錯誤(Dealing with Binding Errors)
用戶不免會提交一些不能綁定到相應的model屬性的值,如未驗證的日期或文本當成數值。下一章會介紹相關的綁定驗證的內容,這裏在使用UpdateModel方法時,咱們必須準備捕獲處理相關的異常,並使用ModelState向用戶提示錯誤的信息,以下:
[HttpPost]
public ActionResult RegisterMember(FormCollection formData) { Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person)); try { UpdateModel(myPerson, formData); } catch (InvalidOperationException ex) { //這裏根據ModelState提供UI反饋 throw ex; } return View("PersonDisplay", myPerson); }
除了try...catch以外,還可使用TryUpdateModel()方法,它的返回值是bool值,以下:
[HttpPost]
public ActionResult RegisterMember(FormCollection formData) { Person myPerson = (Person)DependencyResolver.Current.GetService(typeof(Person)); //try //{ // UpdateModel(myPerson, formData); //} //catch (InvalidOperationException ex) //{ // //這裏根據ModelState提供UI反饋 // throw ex; //} if (TryUpdateModel(myPerson, formData)) { //... } else { //這裏根據ModelState提供UI反饋 } return View("PersonDisplay", myPerson); }
使用模型綁定接收文件上傳(Using Model Binding to Receive File Uploads)
爲了接收上傳的文件,須要定義一個action方法並接收一個HttpPostedFileBase類型的參數。而後,model binder將會使用跟上傳的文件一致的數據填充這個參數。以下:
這裏的關鍵是要設定enctype屬性的值爲"multipart/form-data".若是不這樣作,瀏覽器只會發送文件名而不是文件自己(這是瀏覽器的運行原理決定的).
自定義模型綁定系統(Customizing the Model Binding System)
前面介紹都是默認的模型綁定系統,咱們一樣能夠定製本身的模型綁定系統,下面會展現一些例子:
建立一個自定義的Value Provider
經過定義一個value provider,咱們能夠在模型綁定過程添加本身的數據源。value providers實現IValueProvider接口,以下:
using System.Web.Mvc; using System.Globalization; namespace ModelBinding.Infrastructure { public class CurrentTimeValueProvider : IValueProvider { public bool ContainsPrefix(string prefix) { return string.Compare("CurrentTime", prefix, true) == 0; } public ValueProviderResult GetValue(string key) { return ContainsPrefix(key) ? new ValueProviderResult(DateTime.Now, null, CultureInfo.InvariantCulture) : null; } } public class CurrentTimeValueProviderFactory : ValueProviderFactory { public override IValueProvider GetValueProvider(ControllerContext controllerContext) { return new CurrentTimeValueProvider(); } } }
咱們只響應針對CurrentTime的請求,並當接收到這樣的請求時,返回DateTime.Now屬性的值,對其餘的請求,返回null,表示不能提供數據。咱們必須將數據做爲ValueProviderResult類型返回。爲了註冊自定義的Value Provider,咱們須要建立一個用來產生Provider實例的工廠,這個類從ValueProviderFactory派生,以下:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); ValueProviderFactories.Factories.Add(0, new CurrentTimeValueProviderFactory()); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); }
經過向ValueProviderFactories.Factories集合裏面添加一個實例來註冊咱們本身的工廠,model binder 會按順序尋找value provider,若是想讓咱們的value provider優先,能夠插入序號0,就像上面的代碼中寫的。若是想放在最後能夠直接這樣添加:ValueProviderFactories.Factories.Add(new CurrentTimeValueProviderFactory()); 能夠測下咱們本身的Value Provider,添加一個Action方法以下:
public ActionResult Clock(DateTime currentTime) { return Content("The time is " + currentTime.ToLongTimeString()); }
建立一個依賴感知的Model Binder(Creating a Dependency-Aware Model Binder)
前面有介紹過使用手動模型綁定引入依賴注入到綁定過程,可是還有一種更加優雅的方式,就是經過從DefaultModelBinder派生來建立一個DI敏感的binder而且重寫CreateModel方法,以下所示:
using System.Web.Mvc; namespace ModelBinding.Infrastructure { public class DIModelBinder : DefaultModelBinder { protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) { return DependencyResolver.Current.GetService(modelType) ?? base.CreateModel(controllerContext, bindingContext, modelType); } } }
接着須要註冊該binder,以下:
protected void Application_Start() { AreaRegistration.RegisterAllAreas(); ModelBinders.Binders.DefaultBinder = new DIModelBinder(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); }
建立一個自定義的Model Binder
咱們可以經過建立一個針對具體類型的自定義model binder來重寫默認的binder行爲,以下:
using System.Web.Mvc; using ModelBinding.Models; namespace ModelBinding.Infrastructure { public class PersonModelBinder : IModelBinder { public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { //判斷,若是存在一個model則更新,不然建立 Person model = (Person)bindingContext.Model ?? (Person)DependencyResolver.Current.GetService(typeof(Person)); //檢查下這個Value Provider是否具備必須的前綴 bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName); string searchPrefix = (hasPrefix) ? bindingContext.ModelName + "." : ""; //填充model對象的字段 model.PersonId = int.Parse(GetValue(bindingContext, searchPrefix, "PersonId")); model.FirstName = GetValue(bindingContext, searchPrefix, "FirstName"); model.LastName = GetValue(bindingContext, searchPrefix, "LastName"); model.BirthDate = DateTime.Parse(GetValue(bindingContext, searchPrefix, "BirthDate")); model.IsApproved = GetCheckedValue(bindingContext, searchPrefix, "IsApproved"); model.Role = (Role)Enum.Parse(typeof(Role), GetValue(bindingContext, searchPrefix, "Role")); return model; } private string GetValue(ModelBindingContext context, string prefix, string key) { ValueProviderResult vpr = context.ValueProvider.GetValue(prefix + key); return vpr == null ? null : vpr.AttemptedValue; } private bool GetCheckedValue(ModelBindingContext context, string prefix, string key) { bool result = false; ValueProviderResult vpr = context.ValueProvider.GetValue(prefix + key); if (vpr != null) { result = (bool)vpr.ConvertTo(typeof(bool)); } return result; } } }
下面我一步步來解析這段代碼,首先咱們獲取將要綁定的model對象以下: Person model = (Person)bindingContext.Model ?? (Person)DependencyResolver.Current.GetService(typeof(Person));
當model binding過程被手動調用時,咱們傳遞一個model對象到UpdateModel方法;該對象經過BindingContext類的Model屬性是可用的,一個好的model binder會檢查一個model 對象是不是可用的而且只有當它是能夠的時候纔會被用於綁定過程,不然咱們就須要負責建立一個model對象,並使用應用程序範圍級別的依賴解析器(第10章有介紹)
接着看咱們是否須要使用一個前綴請求來自value provider的數據: bool hasPrefix = bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName); string searchPrefix = (hasPrefix) ? bindingContext.ModelName + "." : "";
BindingContext.ModelName屬性返回綁定的model的名稱,若是咱們在視圖裏呈現這個model對象,生成的HTML不會有前綴,可是ModelName都要返回Action方法的參數名,因此咱們檢查value provider的值前綴是否存在。我經過BindingContext.ValueProvider屬性訪問value providers,這給了咱們一個統一的方式來訪問全部可用的value providers,而且請求按順序傳遞給它們。若是value data裏面存在前綴則使用。
接着咱們使用value providers獲取Person對象的屬性值,以下: model.FirstName = GetValue(bindingContext, searchPrefix, "FirstName");
咱們定義了一個GetValue的方法從統一的value provider獲取ValueProviderResult對象而且經過AttemptedValue屬性提取一個字符串值。 在前面有提到過當呈現一個CheckBox時,HTML helper方法建立一個hidden input元素來保證咱們可以獲取一個沒有選中的值,這會稍微對Model綁定有一些影響,由於value provider將會把兩個值做爲字符串數組提供給咱們。
爲了解決這個問題,咱們使用ValueProviderResult.ConvertTo方法來協調並給出正確的值: result = (bool)vpr.ConvertTo(typeof(bool)); 接着註冊model binder: ModelBinders.Binders.Add(typeof(Person), new PersonModelBinder());
建立Model Binder提供程序(Creating Model Binder Providers)
一種註冊自定義的model binders替代的方式就是經過實現IModelBinderProvider接口來建立一個model binder provider,以下:
using System.Web.Mvc; using ModelBinding.Models; namespace ModelBinding.Infrastructure { public class CustomModelBinderProvider : IModelBinderProvider { public IModelBinder GetBinder(Type modelType) { return modelType == typeof(Person) ? new PersonModelBinder() : null; } } }
這種方式更加靈活,特別是在咱們有多個自定義的binders或多個providers維護時。接着註冊剛建立的provider: ModelBinderProviders.BinderProviders.Add(new CustomModelBinderProvider());
使用ModelBinder屬性(Using the ModelBinder Attribute)
還有最後一種註冊自定義model binder的方式就是使用ModelBinder特性到model類,以下:
[ModelBinder(typeof(PersonModelBinder))] public class Person { [HiddenInput(DisplayValue=false)] public int PersonId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [DataType(DataType.Date)] public DateTime BirthDate { get; set; } public Address HomeAddress { get; set; } public bool IsApproved { get; set; } public Role Role { get; set; } }
ModelBinder特性具備的單個參數讓咱們指定綁定對象的類型,在這個三種方式中,咱們傾向於實現IModelBinderProvider接口來處理負責的需求,固然這三種方式最終實現的效果都同樣,因此選擇哪個均可以。
好了,今天的筆記就到這裏,下一次是關於模型驗證(Model Validation)的內容,由於最近比較忙,因此隨筆的時間間隔比較大了,我儘可能抓緊時間寫吧,:-)
轉自http://www.cnblogs.com/mszhangxuefei/archive/2012/05/15/mvcnotes_30.html