在开发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的行为。

#cnblogs_post_body.cnblogs-markdown p img
{
max-width:95%
}

ASP.NET Core MVC中的 [Required]与[BindRequired]的更多相关文章

  1. 008.Adding a model to an ASP.NET Core MVC app --【在 asp.net core mvc 中添加一个model (模型)】

    Adding a model to an ASP.NET Core MVC app在 asp.net core mvc 中添加一个model (模型)2017-3-30 8 分钟阅读时长 本文内容1. ...

  2. 007.Adding a view to an ASP.NET Core MVC app -- 【在asp.net core mvc中添加视图】

    Adding a view to an ASP.NET Core MVC app 在asp.net core mvc中添加视图 2017-3-4 7 分钟阅读时长 本文内容 1.Changing vi ...

  3. ASP.NET Core MVC 中的 [Controller] 和 [NonController]

    前言 我们知道,在 MVC 应用程序中,有一部分约定的内容.其中关于 Controller 的约定是这样的. 每个 Controller 类的名字以 Controller 结尾,并且放置在 Contr ...

  4. ASP.NET Core MVC 中设置全局异常处理方式

    在asp.net core mvc中,如果有未处理的异常发生后,会返回http500错误,对于最终用户来说,显然不是特别友好.那如何对于这些未处理的异常显示统一的错误提示页面呢? 在asp.net c ...

  5. 006.Adding a controller to a ASP.NET Core MVC app with Visual Studio -- 【在asp.net core mvc 中添加一个控制器】

    Adding a controller to a ASP.NET Core MVC app with Visual Studio 在asp.net core mvc 中添加一个控制器 2017-2-2 ...

  6. ASP.NET Core MVC中构建Web API

    在ASP.NET CORE MVC中,Web API是其中一个功能子集,可以直接使用MVC的特性及路由等功能. 在成功构建 ASP.NET CORE MVC项目之后,选中解决方案,先填加一个API的文 ...

  7. 在ASP.NET Core MVC中子类Controller拦截器要先于父类Controller拦截器执行

    我们知道在ASP.NET Core MVC中Controller上的Filter拦截器是有执行顺序的,那么如果我们在有继承关系的两个Controller类上,声明同一种类型的Filter拦截器,那么是 ...

  8. ASP.NET MVC和ASP.NET Core MVC中获取当前URL/Controller/Action (转载)

    ASP.NET MVC 一.获取URL(ASP.NET通用): [1]获取完整url(协议名+域名+虚拟目录名+文件名+参数) string url=Request.Url.ToString(); [ ...

  9. ASP.NET Core MVC中URL和数据模型的匹配

    Http GET方法 首先我们来看看GET方法的Http请求,URL参数和ASP.NET Core MVC中Controller的Action方法参数匹配情况. 我定义一个UserController ...

随机推荐

  1. 51Nod 1284 2 3 5 7的倍数 容斥原理

    1284 2 3 5 7的倍数基准时间限制:1 秒 空间限制:131072 KB 分值: 5 难度:1级算法题 收藏 关注给出一个数N,求1至N中,有多少个数不是2 3 5 7的倍数. 例如N = 1 ...

  2. phython编写图形界面

    Python支持多种图形界面的第三方库,包括: Tk wxWidgets Qt GTK 等等. 但是Python自带的库是支持Tk的Tkinter,使用Tkinter,无需安装任何包,就可以直接使用. ...

  3. Hexo + github 打造个人博客

    前两年开始用 wordpress 搭了一个网站,但服务器是在 Linode 上,之所以要放在 Linode 上,要从买的域名说起,因为我买的域名是 fengzheng.pub ,.pub 是不允许备案 ...

  4. gem install报错

    [root@app-test ruby-2.2.3]# gem install capistrano -v 2.15 ERROR:  While executing gem ... (Gem::Rem ...

  5. S2b只适合于电商吗?

    万物互联时代,任何产业蓬勃发展都离不开互联网,从B2M.B2B.B2C.C2C.M2M,层出不穷的商业模式都让人眼花缭乱,最近还推出了s2b这个全新的模式. S代表着大的供应平台,它将更好地赋能给更多 ...

  6. CS Round#53 E Maxor

    题意:给你N个数,你可以从中选出两个数将它们or起来得到M,求M的最大值及得到最大值的方案数. 刚了半个小时得到了一个貌似时O(N log max(Ai)^2)的方法,想了想发现貌似只能做出第一问,但 ...

  7. Solr7.1---数据库导入并建立中文分词器

     这里只是告诉你如何导入,生产环境不要这样部署你的solr服务. 首先修改solrConfig.xml文件 备份_default文件夹 修改solrconfig.xml 加入如下内容 官方示例:< ...

  8. arguments.callee的临时指向特性

    function r(){ alert('BBB'); } var a = { f: function(){ alert('AAA'); arguments.callee = r; } }; 弹出的都 ...

  9. python爬虫(四)_urllib2库的基本使用

    本篇我们将开始学习如何进行网页抓取,更多内容请参考:python学习指南 urllib2库的基本使用 所谓网页抓取,就是把URL地址中指定的网络资源从网络流中读取出来,保存到本地.在Python中有很 ...

  10. WINDOWS java 不能正常卸载 问题, (其他系统问题 也可以试试)

    1.JAVA 原安装包无法卸载  不知道 有没有通知 碰到过这种情况的 自己碰到过3次这种情况了,    卸载不掉, 在网上 找了N多中 方法, 注册表什么的都被翻烂了, 单还是没用,其中有一次还把 ...