本文介绍 ASP.NET 中内置的验证功能,并介绍如何自定义验证返回信息,最后以统一社会信用代码为例,实现自定义的数据验证。
DataAnnotations 命名空间提供常用的内置验证特性,可通过声明方式应用于类或属性。我们不需要编写复杂的逻辑,仅需要指定一次,即可应用到整个项目中。代码量的减少,意味着更少的出错,也更易于测试和维护。指定了验证特性的模型会进行强制执行这些验证,有助于提升应用的可靠性,同时保证你在忘记编写某些验证逻辑时,防止你通过应用提交错误的数据到数据库。下面我们来实际使用一下:.
项目演示
创建项目
首先我们创建一个 ASP.NET Core Web API 项目,记得不要使用最小 API,因为最小 API 没有对验证的内置支持,参见《最小 API 与具有控制器的 API 之间的差异》。
创建用户注册信息接收类
接着我们创建一个 OrgRegInfo 类,用于接收用户的注册信息,并把基本验证规则通过内置验证功能进行声明。更多内置特性和使用可参考官方文档《模型验证》。
public class OrgRegInfo{/// <summary>/// 用户名/// </summary>[StringLength(50, MinimumLength = 3, ErrorMessage = "用户名长度为3-50个字符")][Required(ErrorMessage = "请填写用户名")]public string? name { get; set; }/// <summary>/// 邮箱/// </summary>[StringLength(100, ErrorMessage = "邮箱最大支持100个字符,请更换邮箱")][EmailAddress(ErrorMessage = "邮箱格式不正常")][Required(ErrorMessage = "请填写公司邮箱")]public string? email { get; set; }/// <summary>/// 密码/// </summary>[StringLength(50, MinimumLength = 8, ErrorMessage = "密码长度为8-50个字符")][Required(ErrorMessage = "请填写密码")]public string? pwd { get; set; }/// <summary>/// 信用代码/// </summary>[Required(ErrorMessage = "请填写统一社会信用代码")]public string? orgid { get; set; }/// <summary>/// 企业名称/// </summary>[StringLength(80, MinimumLength = 2, ErrorMessage = "企业名称2-80个字符")][Required(ErrorMessage = "请填写工商注册的企业名称")]public string? orgname { get; set; }/// <summary>/// 联系人信息/// </summary>[StringLength(20, MinimumLength = 2, ErrorMessage = "企业联系人2-20个字符")][Required(ErrorMessage = "请填写企业联系人")]public string? orguser { get; set; }/// <summary>/// 联系人电话/// </summary>[Phone(ErrorMessage ="联系人电话格式有误")][Required(ErrorMessage = "请填写联系人电话")]public string? orgphone { get; set; }}
创建测试 Controller
在Controllers文件夹下新建一个 HomeController.cs,内容如下,该方法可以通过 POST 方法获取到我们的输入,简单的验证我们的前面的声明是否有效,如果有效则返回我们的输入信息。
using Microsoft.AspNetCore.Mvc;namespace ValidationDemo.Controllers{[Route("api")][ApiController]public class HomeController : ControllerBase{[HttpPost]public OrgRegInfo Index([FromBody] OrgRegInfo orgreginfo){return orgreginfo;}}}
执行测试
准备完成,我们先启动项目,在 Swagger UI 中,我们直接调用刚刚写好的 API,先不修改传入的参数。
默认直接发送的话,请求的数据如下:
{"name": "string","email": "user@example.com","pwd": "stringst","orgid": "string","orgname": "string","orguser": "string","orgphone": "string"}
不出意外的话,我们就可以看到执行数据验证的返回效果了,接口直接报 400:
错误返回内容如下:
{"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1","title": "One or more validation errors occurred.","status": 400,"traceId": "00-d2cd5da922b883bdf60a464ba305d722-d5ce4691eb28a064-00","errors": {"orgphone": ["联系人电话格式有误"]}}
统一错误返回
一般来说我们都会要求一定的接口返回格式,便于前端的统一处理,比如:
{"success" : false,"code" : 20000,"msg" : "str","data": "Any"}
对应错误返回使用 200 还是 400 的 HTTP 状态码,项目中协商一致按照统一标准即可。我个人在写前端的时候,一般是 200 派的。不过,错误的状态码,可以通一进入错误的处理逻辑进行处理,也是比较好的方案,主要还是看个人喜好,有的前端框架,比如使用前端低代码的时候,会要求你后端出错要怎么返回。
如何统一错误返回呢?
首先我们需要创建一个自定义的过滤器,来处理验证出错后的返回,关于筛选器的更详细的介绍,可查阅官网的文档《ASP.NET Core 中的筛选器》。
新建一个 ModelValidateActionFilterAttribute.cs 文件,继承 ActionFilterAttribute 重写 OnActionExecuting :
using Microsoft.AspNetCore.Mvc.Filters;using Microsoft.AspNetCore.Mvc;namespace ValidationDemo{public class ModelValidateActionFilterAttribute : ActionFilterAttribute{public override void OnActionExecuting(ActionExecutingContext context){if (!context.ModelState.IsValid){//获取验证失败的模型字段var errors = context.ModelState.Where(e => e.Value.Errors.Count > 0).Select(e => e.Value.Errors.First().ErrorMessage).ToList();// | 简单组合一下多处错误var str = string.Join("|", errors);//设置返回内容var result = new{success = false,code = 20000,msg = str,data = ""};context.Result = new BadRequestObjectResult(result);}}}}
接下来我们需要在 Program.cs 文件中进行一些配置,使其生效。
我们需要先关闭默认的筛选器 BadRequestObjectResult, 并添加上我们自己刚刚写好的过滤器。
//关闭默认模型验证builder.Services.Configure<ApiBehaviorOptions>(opt => opt.SuppressModelStateInvalidFilter = true);builder.Services.AddControllers(opt =>{//添加过滤器opt.Filters.Add<ModelValidateActionFilterAttribute>();});
最后,我们再次进行刚刚的测试,可以看到,返回已经是我们刚刚在过滤器里面设置好的格式了。
自定义验证规则
内置的验证虽然满足了基本的使用需求,但如何自定义验证规则呢?下面我们就以统一社会信用代码为例,介绍如何自定义内置验证规则。
验证规则介绍
我们要验证统一社会信用代码,首先就需要了解它。统一社会信用代码是一组长度为18位的用于法人和其他组织身份识别的代码。相当于我们自己的身份证号,是推动社会信用体系建设的一项重要改革措施。在设计公司信息的相关数据存储时,我们可能需要验证统一社会信用代码的真实性。
统一社会信用代码由18位数字或者大写字母组成,但是字母不包括 I、O、Z、S、V一共由五部分组成,下表就是其详细的组成。
|
代码序号 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
|
代码 |
x |
x |
x |
x |
x |
x |
x |
x |
x |
x |
x |
x |
x |
x |
x |
x |
x |
x |
|
说明 |
登记管理部门代码1位 |
机构类别代码1位 |
登记管理机关行政区划码6位 |
主体标识码(组织机构代码)9位 |
校验码1位 |
|||||||||||||
如何验证,我们只需要按照官网的《统一社会信用代码数据错误类型及其标准提法(错码)》[4] 处理即可,校验码的验证也可以在官网搜索到 GB 32100-2015 的规范性文件。
编码实现
我们创建一个 SocialCreditCodeAttribute.cs 文件,继承 Attribute, IModelValidator 实现对统一社会信用代码的数据验证,具体内容如下:
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;namespace ValidationDemo{public class SocialCreditCodeAttribute : Attribute, IModelValidator{// 自定义一个异常信息属性public string ErrorMessage { get; set; }public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context){var code = context.Model as string;// 长度不为18if (code == null || code.Length != 18){// 抛出异常信息集合return new List<ModelValidationResult>{new ModelValidationResult(string.Empty,ErrorMessage)};}code = code.ToUpper();int[] factor = { 1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28 };string str = "0123456789ABCDEFGHJKLMNPQRTUWXY";int total = factor.Select((p, i) => p * str.IndexOf(code[i])).Sum();int index = total % 31 == 0 ? 0 : (31 - total % 31);if (str[index] != code.Last()){// 抛出异常信息集合return new List<ModelValidationResult>{new ModelValidationResult(string.Empty,ErrorMessage)};}// 如果没有异常,就抛出一个空集合return Enumerable.Empty<ModelValidationResult>();}}}
然后我们就可以在 OrgRegInfo 类中对 orgid 属性进行声明。
/// <summary>/// 信用代码/// </summary>[SocialCreditCode(ErrorMessage = "统一社会信用代码效验有误")][Required(ErrorMessage = "请填写统一社会信用代码")]public string? orgid { get; set; }
接下来进行测试,测试可用机构代码 91350100M000100Y43 :
最后
完整代码见:https://github.com/sangyuxiaowu/ValidationDemo