模型绑定(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