ASP.NET Core模型绑定

ASP.NET Core模型绑定

什么是模型绑定

控制器和Razor页面可以处理来自HTTP请求的数据。 例如,路由数据,而表单字段。 编写代码来检索这些值中的每一个并将它们从字符串转换为.NET类型,将是乏味且容易出错的。 模型绑定使该过程自动化。

事实上模型绑定系统能做的还很多:

  1. 从各种来源检索数据,例如路由数据,表单字段和查询字符串。
  2. 将字符串数据转换为.NET类型。
  3. 在方法参数和公共属性中将数据提供给控制器和Razor页面。
  4. 可以将值绑定到复杂类型的属性上。

一个简单的例子

假设你有一个action如下所示:

[HttpGet("{id}")]
public ActionResult<product> GetById(int id, bool dogsOnly)

应用接收到如下请求:

http://yoursite.com/api/products/2?IsOnSale=true

模型绑定在路由选中action方法后开始工作

  1. 找到GetByID方法,它接收一个名为id的整型参数。
  2. 在当前请求所有的所有来源中查找,找到id=2的路由数据。
  3. 将字符串型的“2”转化为整形的2。
  4. 找到GetByID的下一个参数,布尔型的isOnSale
  5. 将字符串"true"转化成布尔型的true

然后,框架将调用GetByID方法,并给参数id传递2,给参数isOnSale传递true

数据来源

默认情况下,模型绑定从HTTP请求中的以下来源获取数据:

  1. 表单字段
  2. 请求body(例如具有[ApiController]标签的控制器。)
  3. 路由数据
  4. 查询字符串
  5. 上传的文件

模型绑定时,将按照前面列表中指示的顺序扫描源。有一些例外:

路由数据和查询字符串值仅用于简单类型。 上传的文件仅绑定到实现IFormFileIEnumerable<IFormFile>的目标类型。

如果默认来源不正确,请使用以下属性之一来指定来源:

  • [FromQuery] - 从查询字符串中获取值。
  • [FromRoute] - 从路由数据中获取值。
  • [FromForm] - 从发布的表单字段中获取值。
  • [FromBody] - 从请求正文中获取值。
  • [FromHeader] - 从HTTP标头中获取值。

上面的这些属性:

  • 添加到每个模型的属性上(而不是模型类上)。
  • 当请求中的值与属性名不匹配时,可以在上述属性的构造函数中传递一下名称:
public void OnGet([FromHeader(Name = "Accept-Language")] string language)

那些类型可以执行模型绑定

  1. 请求路由到的控制器的Action方法的参数上。
  2. 请求路由到的Razor Page的处理方法的参数上。
  3. 使用attribute标记的控制器或Razor Page的公共属性上。

BindProperty和BindProperties Attributte

前面的例子我们已经演示了如何在控制器的Action参数上执行模型绑定,下面我们将讲解如何将值绑定到控制器或Razor Page的公共属性上。

public class ProductModel : PageModel
{
    [BindProperty(SupportsGet =true)]
    public int Id { get; set; }

如上代码,当请求地址为product?id=9时,Razor Page的Id属性会被赋值为9。这得益于BindProperty属性使使用。它应用在控制器或PageModel类的公共属性上,使其能接受模型绑定的值。

默认情况下,HTTP GET请求的属性是不会被绑定的,设置SupportsGetture可以使控制器或PageModel类的属性接受模型绑定的值。

当您需要绑定控制器或PageModel类的多个公共属性时,可以使用BindProperties

[BindProperties(SupportsGet = true)]
public class ProductModel : PageModel
{
    public int Id { get; set; }

    public string Name { get; set; }

复杂类型

模型绑定可以从将字符串直接转化为简单类型:

  • Boolean
  • Byte,SByte
  • Char
  • DateTime
  • DateTimeOffset
  • Decimal
  • Double
  • Enum
  • Guid
  • Int16,Int32,Int64
  • Uri
  • Version

模型绑定的目标也可以绑定到一个复杂类型的属性上。这对复杂类型是有要求的:1.复杂类型必须有公共的默认的构造函数;2.要绑定的属性必须有公共的可写的。

对于每个复杂类型的属性,模型绑定按prefix.property_name查找资原。如果没有找到任何内容,再查找property_name(没有prefix)。

  • 如果绑定的是参数,prefix是参数名。
  • 如果绑定的是PageModel的公共属性,prefix为公共属性名。

一些attribute有prefix属性,可以指定prefix

例如,复杂类型是如下所示的Product类:

public class Product
{
    public int ID { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

一个Prefix=参数名的例子:

如果模型绑定到一个名为product的参数上:

public IActionResult OnPost(Product product)

模型绑定首先会去找名为product.ID的资源,如果找不到,再找ID

一个Prefix=属性名的例子:

如果模型绑定到控制器或PageModel的一个名为Product的参数上:

[BindProperty]
public Product Product { get; set; }

模型绑定首先会去找名为Product.ID的资源,如果找不到,再找ID

自定义prefix

如果模型绑定到一个参数名为product,可以为其指定一个prefixpdt

public IActionResult OnPost([Bind(Prefix = "pdt")] Product product)

模型绑定首先会去找名为pdt.ID的资源,如果找不到,再找ID

用于复杂类型的Attribute

几个内置的控制复杂类型模型绑定的Attribute

  • [Bind] 指定对那些属性进行模型绑定

下面的例子中,仅绑定Product的指定属性。

[Bind("ID,Name,Price")]
public class Product
[HttpPost]
public IActionResult OnPost([Bind("ID,Name,Price")] Product product)
  • [ModelBinder]

ModelBinder attribute用于指定绑定器,它可以用于类型、属性或参数上。

[HttpPost]
public IActionResult OnPost([ModelBinder(typeof(MyProductModelBinder))] Product product)

ModelBinder attribute也可以用于更改绑定的属性或参数的名称。

  • [BindNever]

该标签只可用于属性,不可用于参数。它指示属性不需要模型绑定。

public class Product
{
    [BindNever]
    public int ID { get; set; }

集合

对于简单类型集合,模型绑定会查找与parameter_nameproperty_name的匹配项。 如果未找到匹配项,它将查找以下支持的格式。 例如:

假设要绑定的参数是一个名为selectedCourses的数组:

public IActionResult OnPost(int? id, int[] selectedCourses)

表单和查询字符串都支持下面的格式:

selectedCourses=1050&selectedCourses=2000

selectedCourses[0]=1050&selectedCourses[1]=2000

[0]=1050&[1]=2000

selectedCourses[a]=1050&selectedCourses[b]=2000&selectedCourses.index=a&selectedCourses.index=b

[a]=1050&[b]=2000&index=a&index=b

下面的格式仅表单支持:

selectedCourses[]=1050&selectedCourses[]=2000

上面的格式例子中,模型绑定传递一个数组的两个元素到selectedCourses参数。使用下标数字(...[0]... [1] ...)的数据格式必须确保它们从零开始按顺序编号。 如果下标编号有任何间隙,则间隙后的所有项目都将被忽略。 例如,如果下标是0和2而不是0和1,则忽略第二项。

字典

对于Dictionary类型参数,模型绑定会查找与parameter_nameproperty_name的匹配项。 如果未找到匹配项,它将查找以下支持格式之一。 例如:

假设PageModel的OnPost接受一个Dictionary<int, string>类型的参数selectedCourses

selectedCourses[1050]=Chemistry&selectedCourses[2000]=Economics

[1050]=Chemistry&selectedCourses[2000]=Economics

selectedCourses[0].Key=1050&selectedCourses[0].Value=Chemistry&
selectedCourses[1].Key=2000&selectedCourses[1].Value=Economics

[0].Key=1050&[0].Value=Chemistry&[1].Key=2000&[1].Value=Economics