ASP.NETMVC——模型绑定
这篇⽂章我们来讲讲模型绑定(Model Binding),其实在初步了解ASP.NET MVC之后,⼤家可能都会产⽣⼀个疑问,为什么URL⽚段最后会转换为例如int型或者其他类型的参数呢?这⾥就不得不说模型绑定了。模型绑定是指,⽤浏览器以HTTP请求⽅式发送的数据来创建.NET对象的过程。每当定义具有参数的动作⽅法时,⼀直是在依赖着这种模型绑定过程。
准备项⽬
我们先来创建⼀个MVC项⽬,名叫MVCModels,并在Models⽂件夹中创建⼀个新的类⽂件Person。
1 using System; 2
3 namespace MVCModels.Models 4 {
5 public class Person 6 {
7 public int PersonId { get; set; } 8 public string FirstName { get; set; } 9 public string LastName { get; set; }10 public DateTime BirthDate { get; set; }11 public Address HomeAddress { get; set; }12 public Role Role { get; set; }13 }14
15 public class Address16 {
17 public string Line { get; set; }18 public string City { get; set; }
19 public string PostalCode { get; set; }20 public string Country { get; set; }21 }22
23 public enum Role24 {
25 Admin,26 User,27 Guest28 }29 }
另外定义⼀个Home控制器。
1 using MVCModels.Models; 2 using System.Linq;
3 using System.Web.Mvc; 4
5 namespace MVCModels.Controllers 6 {
7 public class HomeController : Controller 8 {
9 private Person[] personDate = {
10 new Person { PersonId = 1, FirstName = \"Adam\", LastName = \"Freeman\", Role = Role.Admin },11 new Person { PersonId = 2, FirstName = \"Jacqui\", LastName = \"Griffyth\", Role = Role.User },12 new Person { PersonId = 1, FirstName = \"John\", LastName = \"Smith\", Role = Role.Guest },13 };
14 public ActionResult Index(int id)15 {
16 Person dataItem = personDate.Where(p => p.PersonId == id).First();17 return View(dataItem);18 }19 }20 }
接下来再创建⼀个视图⽂件Index。
@model MVCModels.Models.Person@{
ViewBag.Title = \"Index\";
Layout = \"~/Views/Shared/_Layout.cshtml\";}
Person ID:
@Html.DisplayFor(m => m.PersonId)
First Name:
@Html.DisplayFor(m => m.FirstName)
Last Name:
@Html.DisplayFor(m => m.LastName)
Role:
@Html.DisplayFor(m => m.Role)
理解模型绑定
模型绑定是HTTP请求与C#⽅法之间的⼀个桥梁,它根据 Action ⽅法中的 Model 类型创建 .NET 对象,并将 HTTP 请求数据经过转换赋给该对象。当我们启
动项⽬,并导航到/Home/Index/1,我们会看见图下:
当我们请求 /Home/Index/1 URL 时,路由系统便将最后⼀个⽚段值 1 赋给了 id 变量。action invoker 通过路由信息知道当前的请求需要 Index action ⽅法来
处理,但它调⽤ Index action ⽅法之前必须先拿到该⽅法参数的值。 默认的动作调⽤器ControllerActionInvoker,要依靠模型绑定器来⽣成调⽤动作所需要的的数据对象,模型绑定器由IModelBinder接⼝所定义。在本例中,动作调⽤器会检查Index⽅法,并发现它具有⼀个int型参数,于是会查找负责int值绑定的绑定器,并调⽤它的BindModel⽅法。
使⽤默认模型绑定器
虽然应⽤程序可以⾃定义模型绑定器,但是⼤多数情况下还是依靠内建的绑定器类DefaultModelBinder。当动作调⽤器找不到绑定某个类型的⾃定义绑定器
时,就会使⽤这个默认的模型绑定器,默认情况下,这个模型绑定器会搜索四个位置。如下表所⽰:
DefaultModelBinder类查找参数数据的顺序
源
Request.FromRouteData.ValuesRequest.QueryStringRequest.Files
描述
由⽤户在HTML的表单元素中提供值⽤应⽤程序路由获得的值
包含在请求URL中的查询字符串部分的数据请求中上传的⽂件
这些位置将被依次搜索,在本例中,DefaultModelBinder会为id参数查找以下的⼀个值: 1.Request.Form[\"id\"] 2.RouteData.Values[\"id\"] 3.Request.QueryString[\"id\"] 4.Request.Files[\"id\"]
绑定简单类型
处理简单参数类型时,DefaultModelBinder会使⽤System.ComponentModel.TypeDescriptor类,讲请求数据获得的字符参数值转换为参数类型,如果⽆法转换,那么DefaultModelBinder便不能绑定到Model。⽐如访问/Home/Index/apple,便会出现如图所⽰:
默认模型绑定器看到需要的是int值,如果视图将URL中提供的apple值转换为int型,就会出错,此时我们可以修改代码,为参数提供⼀个默认值,这样当默认绑定器⽆法转换时也不会出错了。
1 ...
2 public ActionResult Index(int id = 1)3 {
4 Person dataItem = personDate.Where(p => p.PersonId == id).First();5 return View(dataItem);6 }
绑定复杂类型
当动作⽅法参数是符合类型时(即不能⽤TypeConverter类进⾏转换的属性),DefaultModelBinder类将⽤反射类获取public属性集。好的,我们现在来修改代码。
1 using MVCModels.Models; 2 using System.Linq;
3 using System.Web.Mvc; 4
5 namespace MVCModels.Controllers 6 {
7 public class HomeController : Controller 8 {
9 private Person[] personDate = {
10 new Person { PersonId = 1, FirstName = \"Adam\", LastName = \"Freeman\", Role = Role.Admin },11 new Person { PersonId = 2, FirstName = \"Jacqui\", LastName = \"Griffyth\", Role = Role.User },12 new Person { PersonId = 1, FirstName = \"John\", LastName = \"Smith\", Role = Role.Guest },13 };
14 public ActionResult Index(int id = 1)15 {
16 Person dataItem = personDate.Where(p => p.PersonId == id).First();17 return View(dataItem);18 }
19 public ActionResult CreatePerson()20 {
21 return View(new Person());22 }
23 [HttpPost]
24 public ActionResult CreatePerson(Person model)25 {
26 return View(\"Index\", model);27 }28 }29 }
另外创建⼀个CreatePerson视图。
1 @model MVCModels.Models.Person 2 @{
3 ViewBag.Title = \"CreatePerson\";
4 Layout = \"~/Views/Shared/_Layout.cshtml\"; 5 } 6
7
CreatePerson 8 @using (Html.BeginForm()) 9 {
10
11 12 @Html.LabelFor(m => m.PersonId)13 @Html.EditorFor(m => m.PersonId)14
15
16
17 18 @Html.LabelFor(m => m.FirstName)19 @Html.EditorFor(m => m.FirstName)20
21
22
23 24 @Html.LabelFor(m => m.LastName)25 @Html.EditorFor(m => m.LastName)26
27
28
29 30 @Html.LabelFor(m => m.Role)31 @Html.EditorFor(m => m.Role)32
33
34 Submit 35 }
在表单回递给CreatePerson⽅法时,默认绑定器发现动作⽅法需要⼀个Person对象,便会依次处理每个属性。绑定器会从请求中找到每⼀个值。如果⼀个属性需要另⼀个复合类型时,那么该过程会重复执⾏。例如本例中,Person类的HomeAddress属性是Address类型,在为Line属性查找值时,模型绑定器查找的是HomeAddress.Line的值,即模型对象的属性名(HomeAddress)与属性类型(Address)的属性名(Line)的组合。
Bind特性
我们还可以⽤bind特性为 Address 类型的参数绑定 Person 对象中的 HomeAddress 属性值,例如这样:
1 public ActionResult DisplayAddress([Bind(Prefix=\"HomeAddress\")]Address address) 2 {
3 return View(address);4 }
DisplayAddress action ⽅法的参数类型 Address 不⼀定必须是 Person 的 HomeAddress 属性的类型,它可以是其他类型,只要该类型中含有City
或 Country 同名的属性就都会被绑定到。不过,要注意的是,使⽤ Bind 特性指定了前缀后,需要提交的表单元素的 name 属性必须有该前缀才能被绑定。Bind特性还有两个属性,Exclude 和 Include。它们可以指定在 Mdoel 的属性中,Binder 不查找或只查找某个属性,即在查找时要么只包含这个属性要么不包含这个属性。如下⾯的 action ⽅法:
1 public ActionResult DisplayAddress([Bind(Prefix = \"HomeAddress\2 {
3 return View(address);4 }
这时 Binder 在绑定时不会对 Address 这个 Model 的 Country 属性绑定值。