zoukankan      html  css  js  c++  java
  • ASP.NET Core 高级系列(一)【上】:模型绑定

    此为系列文章,对MSDN ASP.NET Core 的官方文档进行系统学习与翻译。其中或许会添加本人对 ASP.NET Core 的浅显理解。

           这篇文章解释了模型绑定是什么,它是如何工作的,以及如何自定义它的行为。

    什么是模型绑定

           控制器以及Razor 页面与来自于HTTP请求的数据一起工作。举个例子,路由数据或许会提供一个记录键,post 表单字段可能会为模型的属性提供值。如果编写代码来取出这些值并将它们从字符串类型转换为.NET类型将会是枯燥乏味并且是容易出错的。而模型绑定将这个过程自动化。模型绑定系统:

    • 从各种数据源中取出数据,比如路由数据,表单字段,以及查询字符串。
    • 以方法参数以及公共属性的方式为控制器及Razor页面提供数据。
    • 将字符串类型的数据转换为.NET类型。
    • 更新复杂类型的属性。

    示例

          假设你有如下的动作(Action)方法:

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

          而且app接受到了一个带有如下URL的请求:

    http://contoso.com/api/pets/2?DogsOnly=true

          在路由系统选择合适的动作方法之后,模型绑定会经理如下的步骤:

    • 找到GetByID方法的第一个参数,一个名为id的整型。
    • 在HTTP请求的可用的数据源进行查找,并在路由数据中找到 id = "2"。
    • 将字符串类型“2”转换为整数2。
    • 找到GetByID方法的下一个参数,一个名为 dogsOnly 的布尔值。
    • 查找数据源并在查询字符串中找到 “DogsOnly=true”。名称匹配是大小写不敏感的。
    • 将字符串类型的 “true” 转换为布尔类型的 true。

         框架然后会调用 GetById 方法,然后会给id参数传2,给dogsOnly参数传递 dogsOnly。

         在之前的示例中,模型参数目标是简单类型的方法参数。模型绑定的目标也可能是复杂类型的属性。在每个属性都被成功绑定后,那个属性会发生model validation。什么数据被绑定给模型的记录,以及任何绑定或者验证错误,都会存储在ControllerBase.ModelState 或 PageModel.ModelState中。为了证实这个过程是否成功,可以检查ModelState.IsValid标记。

    目标

           模型绑定尝试为如下类型的目标查找数值:

    • 请求路由到的一个控制器的动作方法。
    • 请求被路由到的Razor 页面的处理方法。
    • 一个控制器或者PageModel 类的公共属性,如果被属性指定。

    [BindProperty] 属性

          这个属性可被应用于一个控制器或者是PageModel 类的 public 属性上,以在那个属性上实现模型绑定。

    public class EditModel : InstructorsPageModel
    {
        [BindProperty]
        public Instructor Instructor { get; set; }

    [BindProperties] 属性

          在ASP.NET Core 2.1及后续版本可用。其可用应用到控制器或者一个PageModel 类来告诉模型绑定,其目标为这个类的所有的public 属性。

    [BindProperties(SupportsGet = true)]
    public class CreateModel : InstructorsPageModel
    {
        public Instructor Instructor { get; set; }
    }

    HTTP GET 请求的模型绑定

           默认情况下,对于HTTP GET 请求,属性不会被绑定。经典的,对于一个HTTP GET 请求,所有你需要的只是一个记录 ID 参数。这个记录ID 会被使用来在数据库中查询这个条目。因此,没有必要将其绑定到持有模型实例的一个属性上。在你确实想要将属性绑定到来自于HTTP GET 请求的数据上时,可以将 SupportsGet 属性设置为 true。

    [BindProperty(Name = "ai_user", SupportsGet = true)]
    public string ApplicationInsightsCookie { get; set; }

    数据源

           默认情况下,模型绑定以键值对的形式从一个HTTP 请求的如下数据源中获取数据:

    1. 表单字段。
    2. 请求体(对于实现了[ApiController] 属性的控制器)。
    3. 路由数据。
    4. 查询字符串参数。
    5. 已上传的文件。

           对于每一个目标参数及属性,数据源都会以如上的顺序被扫描。但是还有一些例外:

    1. 路由数据以及查询字符串值仅被用作简单类型。
    2. 已上传的文件仅仅被绑定到实现了 IFormFile 或IEnumerable<IFormFile>接口的目标类型。

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

           这些属性:  

    • 被添加到各个模型属性上,而不是模型类上,如同如下示例:
    public class Instructor
    {
        public int ID { get; set; }
    
        [FromQuery(Name = "Note")]
        public string NoteFromQueryString { get; set; }
    • 在构造函数中可选的接受一个模型名称值。在属性名称不匹配请求中的值的情形,可以使用这个选项。比如,请求中的值或许是一个其名字中带有连字的头信息,如同如下示例:
    public void OnGet([FromHeader(Name = "Accept-Language")] string language)

    [FromBody] 属性 

           将[FromBody] 属性应用到一个属性上以从一个HTTP 请求体中填充这个属性值。ASP.NET Core 运行时代理了将读取请求体的数据到一个输入格式化器的职责。输入格式化器将在本章的后续进行解释。

           当[FromBody]属性被应用到一个复杂类型参数上的时候,任何应用到这个属性的绑定源属性都会被忽略。如下的Create方法指定了它的pet 会被从请求体中进行填充。

    public ActionResult<Pet> Create([FromBody] Pet pet)

           Pet 类指定了其 Breed 属性将会从查询字符串中的值进行填充:

    public class Pet
    {
        public string Name { get; set; }
    
        [FromQuery] // Attribute is ignored.
        public string Breed { get; set; }
    }

          在上述示例中:

    • [FromQuery] 属性会被忽略。
    • Breed 属性不会从查询字符串中进行填充。

          输入格式化器仅仅读取请求体,其并不理解绑定源属性。如果一个合适的值在请求体中被发现,这个值将会被用来填充Breed 属性。

          对于一个Action 方法,请不要应用[FromBody]属性给多于一个的属性。一旦请求流被一个输入格式化器读取,它便不再可用被再次读取以绑定到其他 [FromBody]属性。

    额外的源 

           源数据通过值提供器被提供给模型绑定系统。你可以编写并注册自定义的值提供器,其为模型绑定从其他源中获取数据。举个例子,你或许想从Cookies 或者会话状态中获取数据。为了从一个新数据源获取数据,你可以:  

    • 创建一个实现了IValueProvider 的类。
    • 创建一个实现了IValueProviderFactory 的类。
    • 将工厂类注册到Startup.ConfigureServices。

          示例app包括了一个 value provider 和 factory 示例,其从cookies 中获取值。如下是 Startup.ConfigureServices中的注册代码:

    services.AddRazorPages()
        .AddMvcOptions(options =>
    {
        options.ValueProviderFactories.Add(new CookieValueProviderFactory());
        options.ModelMetadataDetailsProviders.Add(
            new ExcludeBindingMetadataProvider(typeof(System.Version)));
        options.ModelMetadataDetailsProviders.Add(
            new SuppressChildValidationMetadataProvider(typeof(System.Guid)));
    })
    .AddXmlSerializerFormatters();

           演示的代码将自定义的值提供器放置在内置的值提供器之后。为了使其成为列表中的第一个,调用 Insert(0, new CookieValueProviderFactory()) 来代替 Add()。

    没有数据源的模型属性

          默认情况下,如果一个模型属性没有找到对应的值,那么不会创建模型状态错误的。这个时候,属性会被设置为 null 或者默认值:

    • 可空简单类型被设置为 null。
    • 不可空值类型被设置为 default(T),举个例子,一个 int 型的参数 id 被设置为 0。
    • 对于复杂类型,模型绑定通过默认构造函数创建了一个实例,而不会设置其属性。
    • 数组被设置为 Array.Empty<T>(),而字节数组被设置为 null。

         当在一个表单字段中为某个属性没有找到对应的绑定值,而需要模型状态为 非验证通过时,可以使用 [BindRequired] 属性。

         请注意[BindRequired] 行为应用到模型绑定是从 posted 表单数据,而不是在请求体中的 JSON 或者 XML 数据,请求体数据通过 input formatters 进行处理。

    类型转换错误

          如果一个数据源被找到但是没有转换为目标类型,模型状态便会被标记为 invalid。目标参数或者属性会被设置为 null 或者默认值,就如同以上章节所提到的。

          在一个具有 [ApiController] 属性的API 控制器中,invalid 模型状态导致了一个自动的 HTTP 400 响应。

          在一个Razor 页面,会重新显示这个页面,并显示了一个错误信息。

    public IActionResult OnPost()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }
    
        _instructorsInMemoryStore.Add(Instructor);
        return RedirectToPage("./Index");
    }

          客户端验证会捕获大部分的异常数据,否则它们便会被提交到Razor 页面表单中。这些验证使得我们很难触发如上高亮显示的代码。示例app 包含了一个  Submit with Invalid Date 按钮,其将异常数据放在 Hire Date 字段中并提交数据。这个按钮演示了当转换错误发生时, 用来重新显示页面的代码是如何工作的。

          当页面被上述的代码重新显示时,无效的输入不会显示在表单字段中,这是因为模型属性被设置为 null 或者默认值。无效的输入会出现在一个错误信息中,如果你想将异常数据显示在表单字段中,考虑将模型字段设置为字符串并手动进行数据转换。

         如果你不想类型转换错误导致模型状态错误,我们推荐同样的策略。在那种情形下,将模型属性设置为一个字符串。

    To be continued...

  • 相关阅读:
    8.17 纯css画一个着重号图标
    8.16 val()和html()的问题
    8.14 git??sourceTree??
    7.27-8.10 Problems
    To be a better me
    【LeetCode刷题】Set and bitset
    【LeetCode刷题】求平方根
    【LeetCode刷题】爬楼梯问题
    大学四年就这样,么了~
    硬件综合实习——51单片机四则运算带括号计算器
  • 原文地址:https://www.cnblogs.com/qianxingmu/p/12594842.html
Copyright © 2011-2022 走看看