zoukankan      html  css  js  c++  java
  • ASP.NET Core MVC中的 [Required]与[BindRequired]

    在开发ASP.NET Core MVC应用程序时,需要对控制器中的模型校验数据有效性,元数据注释(Data Annotations)是一个完美的解决方案。

    元数据注释最典型例子是确保API的调用者提供了某个属性的值,在传统的ASP.NET MVC中使用的是RequiredAttribute特性类。该属性仍然可以在ASP.NET Core MVC中使用,但也提供了一个新的特性类BindRequiredAttribute

    今天让我们来看看它们之间的细微差别。

    RequiredAttribute的典型用法

    想象一下,您的模型是下面的BookOrder类。为了强制AuthorTitle属性的值始终出现在传入的请求中,并且值不能为空,我们使用RequiredAttribute修饰它们。

        public class BookOrder
        {
            [Required]
            public string Author { get; set; }
         
            [Required]
            public string Title { get; set; }
        }
    

    这在传统的ASP.NET MVC中是很常见方法。有了这个,您可以验证ModelState是否存在错误,因为对这些属性的验证失败会反映在这里。示例:

        [Route("bookorder")]
        public IActionResult PostBook([FromForm]BookOrder bookOrder)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
         
            // 正常逻辑代码
            return Content("OK");
        }
    

    如果传入的请求中缺少两个必需的属性(或者属性的值为String.Empty),则响应将是一个400状态码,并将模型状态错误序列化到响应中。

    使用Postman进行测试,请求中只提供了Title属性的值,而未提供Author属性的值:

    常有趣的问题,例如当我们添加类似数量的属性时(类型为int)。在这种情况下,RequiredAttribute将无法正常工作,因为默认值为0,并且该属性不可能为空。即使客户端在不包括属性值的情况下提交请求,模型实例的值都将为0。换句话说,RequiredAttribute不起作用。在我们的例子中,我们使用的是int,但是同样适用于所有值的类型,例如DateTimeGuid

    以前,在传统的ASP.NET MVC中,解决这个问题的方法是使用一个可为空类型,如下所示:

        public class BookOrder
        {
            [Required]
            public string Author { get; set; }
         
            [Required]
            public string Title { get; set; }
         
            [Required]
            public int? Quantity { get; set; }
        }
    

    虽然它解决了这个问题,但是这不是最优雅的方式。Quantity属性不能为null,如果API的调用者未提供该属性的值,则该值保持为null,配合RequiredAttribute特性可以达到我们的目的。

    这种方法的缺点是,在正常逻辑代码中,您需要直接访问可为空的Value属性。

        [Route("bookorder")]
        public IActionResult PostBook([FromForm]BookOrder bookOrder)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }
         
            // 正常逻辑代码
            var requestedQuantity = bookOrder.Quantity.Value;
            
            return Content("OK");
        }
    

    如果通过静态分析工具来检查代码,因为它们找不不到空值检测,所以会警告您可能出现空引用异常(即使在逻辑上,ModelState检测是足够的)。所以这并不是令人赏心悦目的方案。

    如果在VS中安装了Resharper工具,会显示如下警告:

    这就是BindRequiredAttribute产生的原因。它和RequiredAttribute的工作方式类似,它要求请求必须提示属性的值 。

    我们将模型改为如下结构,Quantity属性为int类型,并使用[BindRequired]修饰:

        public class BookOrder
        {
            [Required]
            public string Author { get; set; }
         
            [Required]
            public string Title { get; set; }
         
            [BindRequired]
            public int Quantity { get; set; }
        }
    

    使用Postman进行测试,在请求体不包括Quantity属性的值:

    测试结果为ModelState.IsValid返回的false,要求提供Quantity属性的值。

    RequiredAttribute与BindRequiredAttribute的差别

    BindRequiredAttribute特性类只要求API的调用者提供该属性的值,但不进行空值检测,允许提供的值为空,例如我们将模型改为如下示例,所有属性中都使用了BindRequiredAttribute特性类进行注释:

        public class BookOrder
        {
            [BindRequired]
            public string Author { get; set; }
         
            [BindRequired]
            public string Title { get; set; }
         
            [BindRequired]
            public int Quantity { get; set; }
        }
    

    使用Postman进行测试,在请求体包括这三个属性名称的参数,但是属性的值均为空或默认值:

    测试结果为正常,BindRequiredAttribute特性类只要求API的调用者提供该属性的值,但不进行空值检测,允许提供的值为空

    RequiredAttribute 与 BindRequiredAttribute 混用问题

    当然,在代码中混合使用[Required][BindRequired]并不是一个最令人满意的效果。值得庆幸的是,ASP.NET Core MVC为您提供了足够的灵活性来改变RequiredAttribute的行为,强制它包含BindRequiredAttribute的行为。

    您可以通过实现自己的IBindingMetadataProvider并在您的应用程序全局范围内注册来实现这一目标。

        public class RequiredBindingMetadataProvider : IBindingMetadataProvider
        {
            public void CreateBindingMetadata(BindingMetadataProviderContext context)
            {
                if (context.PropertyAttributes?.OfType<RequiredAttribute>().Any() ?? false) {
                    context.BindingMetadata.IsBindingRequired = true;
                }
            }
        }
    

    在全局范围内注册它:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(o =>
            {
                o.ModelMetadataDetailsProviders.Add(new RequiredBindingMetadataProvider());
            });
        }
    

    相关补充内容

    1、ASP.NET Core MVC还提供了BindNeverAttribute特性类用于指定该属性不进行模型绑定。例如:您拥有一个像IsAdmin这样的属性,这需要通过服务端来指定,而不是通过客户提交的数据来指定;
    2、如果客户端提交的是Json格式的数据,BindRequiredAttributeBindNeverAttribute特性类就不会起任何作用,这是因为模型直接通过Json.Net反序列化来创建,MVC框架并不知道属性的值从何而来。

    上面补充的两点,感兴趣的同学可以自己进行测试。

    总结

    • RequiredAttribute特性类的行为在传统的ASP.NET MVC与ASP.NET Core MVC是一致的,校验属性的值不能为null
    • RequiredAttribute特性类对于值类型的属性,就没有任何效果了,所以在编码的过程将属性的类型改为可为空类型,不过编码的过程就比较纠结了,需要访问可为空类型的Value属性,而且静态代码检测工具会警告需要进行非空的校验;
    • BindRequiredAttribute特性类可以解决值类型默认值的问题,强制要求客户端提交请求时,必须包含属性的值,不过属性的值可以为空,不进行非空的校验;
    • RequiredAttributeBindRequiredAttribute混合使用的情况下,我们也提供了优雅的方法,让RequiredAttribute包含BindRequiredAttribute的行为。
  • 相关阅读:
    JAI丢包掉帧处理
    FirstWriting
    搭建PHP开发环境 apache+MySQL+PHP 安装phpMyAdmin模块
    jQuery基本知识体系图
    WAMP不能启动MYSQL服务
    为图片添加内阴影效果的三种方法
    关于重装系统或还原系统
    初识jQuery,八字真言“选择元素,对其操作”
    JavaScript网站设计实践(七)编写最后一个页面 改进表单
    JavaScript网站设计实践(六)编写live.html页面 改进表格显示
  • 原文地址:https://www.cnblogs.com/tdfblog/p/required-and-bindrequired-in-asp-net-core-mvc.html
Copyright © 2011-2022 走看看