ASP.NET MVC - 模型绑定

创建时间:
2015-10-27 16:17
最近更新:
2018-09-18 11:01

Brief

  • 模型绑定 本质上就是 从 HTTP 请求中获取数据 并赋值给 Action 参数的过程。
  • 模型绑定器 在请求中查找 与操作参数同名的数据。例如 在路由数据中 找到并赋值给 操作参数 id

Keywords

  • Model Binding (模型绑定)
  • DefaultModelBinder
  • IModelBinder

Resource - MSDN

  1. System.Web.Mvc 命名空间
  2. ASP.NET MVC 模型绑定的功能和问题 副本
  3. ASP.NET Core 中的模型绑定 - MSDN - 2018/01/22

Resource

  1. ASP.NET MVC 模型绑定的 6 个建议
  2. MVC3 模型绑定
  3. MVC 模型绑定
  4. ASP.NET Core MVC 模型绑定用法及原理 - 本文最后一部分是 实现原理
  5. ASP.NET MVC Model Binding (模型绑定) - 简单类型绑定、复合类型绑定、集合/数组绑定、[BindAttribute(Prefix="p")]
  6. ASP.NET MVC Model Binding (模型绑定) - 绑定到复合类型、绑定到数组、绑定到集合、应用 BindAttribute
  7. ASP.NET MVC 数组模型绑定
  1. 通过实例模拟 ASP.NET MVC 的 Model 绑定机制: 简单类型 + 复杂类型
  2. 通过实例模拟 ASP.NET MVC 的 Model 绑定机制: 数组
  3. 通过实例模拟 ASP.NET MVC 的 Model 绑定的机制: 集合 + 字典

Resource - JSON + ASP.NET MVC 模型绑定

  1. MVC3 JSON 绑定模型
  2. MVC MVVM Knockout viewmodel 提交完整过程,包含序列化 JSON 和 字典模型绑定
  3. 通过 AJAX POST JSON 类型的数据到 Controller
  4. 如何使用 jQuery 向 ASP.NET MVC 传递复杂 JSON 数据
  5. 一个共通的 ViewModel 搞定所有的编辑页面 (EasyUI + KnockoutJS + MVC4)

显式调用 模型绑定

  • UpdateModel()
  • TryUpdateModel()

表单验证

ASP.NET MVC 进行模型绑定的同时还会进行 数据验证 (例如 表单验证)。
模型绑定 完成后,会得到一个完整的 ModelState 对象,该对象含有模型绑定过程中收集到的各种信息,包括 验证状态、绑定异常。例如 ModelState.IsValid。

异常

模型绑定时,请求中的数据都是字符串类型,DefaultModelBinder 会其转换成 Action 参数对应的类型。如果转换失败,将抛出异常。

简单模型绑定

通过 DefaultModelBinder 在请求中查找与 Action 的参数 同名的 数据,并将其值赋给该参数。
对于简单类型,DefaultModelBinder 会尝试使用 System.ComponentModel.TypeDescriptor 类将 请求中的数据 (字符串类型) 转换成 与操作参数相同的类型。如果转换失败则抛出异常。为了避免异常,可将操作参数定义为可以为空的类型 (例如 int?) 或者 为操作参数定义一个默认值 (例如 int id = 123)。

复杂模型绑定

复杂类型 指任何不能被 TypeConverter 类转换的类型 (大多为 自定义类型 或者 集合),否则称为简单类型。
复杂模型绑定 指 通过 DefaultModelBinder 将 请求中的数据 绑定到 复杂类型的 Action 参数。
对于复杂类型,DefaultModelBinder 类 通过反射获取并遍历该类型的所有公共属性,遇到简单类型的属性则进行简单模型绑定,遇到复杂类型的属性则重复本过程。
DefaultModelBinder 在请求中查找与 复杂类型的属性 对应的数据时,用于查找的键 是 有前缀的/有层级的,例如以下示例中的 HomeAddress.City

示例

//模型类:
public class Person
{
    public int      PersonId    { get; set; }
    public string   FirstName   { get; set; }
    public string   LastName    { get; set; }
    public Address  HomeAddress { get; set; }
}
public class Address
{
    public string City      { get; set; }
    public string Country   { get; set; }
}
//控制器操作:
public ActionResult CreatePerson(Person model)
{
    return View(model);
}
//表单:
@using(Html.BeginForm()) {
    <div>
        @Html.LabelFor (m => m.PersonId)
        @Html.EditorFor(m => m.PersonId)
    </div>
    <div>
        @Html.LabelFor (m => m.HomeAddress.Country)
        @Html.EditorFor(m => m.HomeAddress.Country)
    </div>
    ...
}

当 DefaultModelBinder 发现 Action 参数 Person 是复合类型,就会为该类型的每个属性查找值。
当 DefaultModelBinder 发现该类型的属性 HomeAddress 是复合类型,也会为该类型的每个属性查找值。唯一不同的是,查找的时候用的名称有前缀。
对于诸如简单类型的 PersonId 属性,将会在 Request.Form["PersonId"] 中找到它需要的值。
对于诸如复杂类型的 HomeAddress 属性,会在 Request.Form["HomeAddress.Country"] 中找到 Person.HomeAddress.Country 的值。
因为上面的表单中的

@Html.EditorFor(m => m.HomeAddress.Country)

生成的 Html 代码是:

<input id="HomeAddress_Country" name="HomeAddress.Country" type="text" value="" />

复杂模型绑定 (多个)

表单 (有两组字段):

<form action="/Home/ComplexModelBinding" method="post">
    ## Form1:
    Type1: <input type="radio" name="form1.Type" value="1" />
    Type2: <input type="radio" name="form1.Type" value="2" />
    Name:  <input type="text"  name="form1.Name" />
    Email: <input type="text"  name="form1.Email" />
    Body:  <textarea name="form1.Body"></textarea>

    ## Form1:
    Type1: <input type="radio" name="form2.Type" value="1" />
    Type2: <input type="radio" name="form2.Type" value="2" />
    Name:  <input type="text"  name="form2.Name" />
    Email: <input type="text"  name="form2.Email" />
    Body:  <textarea name="form2.Body"></textarea>

    <input type="submit" />
</form>

以上表单对应的 Action:

public ActionResult ComplexModelBinding(CustomForm form1, CustomForm form2)
{
    InsertIntoDb(form1);
    InsertIntoDb(form2);
    return Redirect("/");
}

FormCollection

通过 FormCollection 可获得请求中的全部表单数据。
FormCollection 类似于 Request.Form。

public ActionResult TestForm(FormCollection form)
{
    ViewData.Model = form["Username"];
    return View();
}

测试记录: 同一参数名以不同数据类型重复出现 DefaultModelBinder 会抛异常

测试环境: VM0、Windows 10、Visual Studio 2013、CodeGen 项目、ASP.NET MVC

测试记录:
请求 http://localhost:3728/Home/Index/111 以下 Action 会抛出 System.ArgumentException,其 Message 为 "对于“Site.Controllers.HomeController”中方法“System.Web.Mvc.ActionResult Index(System.Nullable`1[System.Int32], System.String)”的参数“id”,参数字典包含无效项。字典包含一个类型“System.String”的值,但参数需要的是类型“System.Nullable`1[System.Int32]”的值。":

public ActionResult Index(int? id, string iD)
{
    return View();
}