.Net Core 3.x Api开发笔记 -- 输入参数模型验证(六)

参数模型验证  一般是对传入的参数按照制定规则校验,该章节主要演示在服务端对传入参数进行校验

校验主要包括3点:

1,定义验证规则

2,按照规则进行检查

3,错误报告

 

1,定义验证规则

这里介绍3中验证方式:

方式一:使用 Data Annotations程序集,通过属性注解方式,例如  [Required]、[MaxLength] 等

方式二:自定义属性 Attribute 验证

方式三:使用 FluentValidation 方式验证 (推荐)

 

方式一 和 方式二 都要引入下边的程序集

引入程序集:System.ComponentModel.Annotations  项目没有的需要安装一下该程序包

方式一:属性注解验证

优点:简单

缺点:只能作用在属性上、存在代码侵入、校验方式简单、验证只能在Controller的Action中使用,不支持非Controller中或者控制台程序的验证

 1 public class ProductsDto
 2 {
 3         [Display(Name = "商品编号")]
 4         [Required(ErrorMessage = "{0}是必填项")]
 5         [StringLength(maximumLength: 10, MinimumLength = 5, ErrorMessage = "{0}的长度范围是{2}到{1}")]
 6         public string ProductCode { get; set; }
 7 
 8         [Display(Name = "商品名称")]
 9         [Required(ErrorMessage = "{0}是必填项")]
10         [MinLength(1, ErrorMessage = "{0}的最小长度是1")]
11         public string ProductName { get; set; }
12 
13         [Display(Name = "商品价格")]
14         [Required(ErrorMessage = "{0}是必填项")]
15         [RegularExpression(@"^(?!.{12,}$)\d{1,18}(\.\d{1,2})?$", ErrorMessage = "{0}格式不规范,{0}要保留小数点后1到2位")]
16         public decimal? Price { get; set; }
17 
18         [Display(Name = "会员价")]
19         [Compare("Price", ErrorMessage = "{0}必须和{1}相同")]
20         public decimal? VipPrice { get; set; }
21 
22         [Display(Name = "状态")]
23         [Range(0, 1, ErrorMessage = "{0}必须是{1}或{2}")]
24         public int? Status { get; set; }
25 
26 }

简单解释: 

Display  定义别名

Required  必填项

StringLength   字符串长度验证   

   maximumLength: 10   最大长度

   MinimumLength = 5    最小长度

   ErrorMessage = "{0}的长度范围是{2}到{1}"      -- 错误提示内容 

   {0}、{1}、{2} 是占位符,{0}表示当前属性 ,{1}第一个参数 maximumLength , {2}是第二个参数MinimumLength  

RegularExpression   定义正则表达式

Compare  和其他属性进行比较

 

通过接口进行测试,返回错误报告如下:

AddProducts(ProductsDto products)

方式二:自定义属性 Attribute 验证

优点:可以将验证声明到类级别上、在方式一的基础上进一步封装、可以进行复杂的规则校验

缺点:依然存在代码侵入(小到可以忽略不计),验证只能在Controller的Action中使用,不支持非Controller中或者控制台程序的验证

定义一个类:LoginFilterValidationAttribute,继承 ValidationAttribute 属性,重写IsValid()方法

public class LoginFilterValidationAttribute : ValidationAttribute
    {
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var userDto = (UsersDto)validationContext.ObjectInstance;   //获取类的实例对象

            //验证用户名不能为空
            if (string.IsNullOrWhiteSpace(userDto.Username))
            {
                return new ValidationResult("用户名不能为空", new[] { nameof(userDto.Username) });
            }

            //验证密码不能为空
            if (string.IsNullOrWhiteSpace(userDto.Password))
            {
                return new ValidationResult("密码不能为空", new[] { nameof(userDto.Password) });
            }

            //验证手机号不能为空
            if (string.IsNullOrWhiteSpace(userDto.Mobile))
            {
                return new ValidationResult("手机号不能为空", new[] { nameof(userDto.Mobile) });
            }

            //手机号输入规则验证
            if (!string.IsNullOrWhiteSpace(userDto.Mobile))
            {
                var regex = new Regex(@"^1[3456789]\d{9}$");
                if (!regex.IsMatch(userDto.Mobile))
                    return new ValidationResult("手机号不符合规则", new[] { nameof(userDto.Mobile) });
            }

            //验证密码强度
            if (!string.IsNullOrWhiteSpace(userDto.Password))
            {
                //正则
                var regex = new Regex(@"
                                      (?=.*[0-9])                     #必须包含数字
                                      (?=.*[a-zA-Z])                  #必须包含小写或大写字母
                                      (?=([\x21-\x7e]+)[^a-zA-Z0-9])  #必须包含特殊符号
                                      .{6,16}                         #至少6个字符,最多16个字符
                                      ", RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);
                //是否匹配,如果不匹配则返回
                if (!regex.IsMatch(userDto.Password))
                {
                    return new ValidationResult("密码不符合规则,请重新输入", new[] { nameof(userDto.Password) });
                }
            }

            //验证两次输入密码
            if (!string.IsNullOrWhiteSpace(userDto.ConfirmPassword))
            {
                if (!userDto.Password.Equals(userDto.ConfirmPassword))
                {
                    return new ValidationResult("两次输入密码不同,请重新输入", new[] {
                            nameof(userDto.Password),
                            nameof(userDto.ConfirmPassword)
                        });
                }
            }

            return ValidationResult.Success;
        }

    }

在Model类中直接如下声明即可:

    [LoginFilterValidation]      //将参数验证声明到类上
    public class UsersDto
    {
        public int Userid { get; set; }
        public string Username { get; set; }
        。。。
    }

通过接口测试,返回错误内容如下:

AddUsers(UsersDto users)

方式三:使用 FluentValidation 方式验证 (推荐) 

优点:支持任何场景下的模型验证(Controller层和Service层都能用),且不侵入代码,支持复制规则验证,规则定义类似方式二

缺点:适合大型项目(个人感觉),小项目用上边两种方式够用了

使用该方式需要引入下边程序包:FluentValidation.AspNetCore

创建自定义类:RegisterValidationAttribute 、继承 AbstractValidator<UsersDto>

 1 public class RegisterValidationAttribute : AbstractValidator<UsersDto>, IModelValidator
 2 {
 3     public RegisterValidationAttribute()
 4     {
 5         //如果设置为Stop,则检测到失败的验证,则立即终止,不会继续执行剩余属性的验证。
 6         //默认值为 Continue
 7         CascadeMode = CascadeMode.Stop;
 8 
 9         RuleFor(x => x.Username).NotEmpty().WithMessage("用户名不能为空")
10                              .Length(2, 12).WithMessage("用户名至少2个字符,最多12个字符");
11 
12         RuleFor(x => x.Password).NotEmpty().WithMessage("密码不能为空")
13                              .Length(6, 16).WithMessage("密码长度至少6个字符,最多16个字符")
14                   .Must(EncryptionPassword).WithMessage("密码不符合规则,必须包含数字、小写或大写字母、特殊符号");
15 
16         RuleFor(x => x.ConfirmPassword).NotEmpty().WithMessage("确认密码不能为空")
17                             .Must(ComparePassword).WithMessage("确认密码必须跟密码一样");
18 
19         RuleFor(x => x.Mobile).NotEmpty().WithMessage("手机号不能为空")
20                           .Must(IsMobile).WithMessage("手机号格式不正确");
21     }
22 
23     /// <summary>
24     /// 密码强度验证
25     /// </summary>
26     /// <returns></returns>
27     private bool EncryptionPassword(string password)
28     {
29         //正则
30         var regex = new Regex(@"
31                               (?=.*[0-9])                     #必须包含数字
32                               (?=.*[a-zA-Z])                  #必须包含小写或大写字母
33                               (?=([\x21-\x7e]+)[^a-zA-Z0-9])  #必须包含特殊符号
34                               .{6,16}                         #至少6个字符,最多16个字符
35                               ", RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace);
36         return regex.IsMatch(password);
37     }
38 
39     /// <summary>
40     /// 比较两次密码是否一样
41     /// </summary>
42     /// <param name="confirmpwd">这里传的是:ConfirmPassword</param>
43     /// <returns></returns>
44     private bool ComparePassword(UsersDto model, string confirmpwd)
45     {
46         return string.Equals(model.Password, confirmpwd, StringComparison.OrdinalIgnoreCase);  //比较字符串并忽略大小写
47     }
48 
49     //验证手机号
50     private bool IsMobile(string arg)
51     {
52         return Regex.IsMatch(arg, @"^[1][3-8]\d{9}$");
53     }
55 }

使用方式很简单,如下:

 1 [HttpPost]
 2 public async Task<ActionResult> RegisterUsers(UsersDto usersDto)
 3 {
 4     var result = new CommonResult();
 5     
 6     //使用如下两行代码即可
 7     RegisterValidationAttribute validationRules = new RegisterValidationAttribute();
 8     ValidationResult validaResult = validationRules.Validate(usersDto);
 9 
10     if (validaResult.IsValid)   //校验通过
11     {
12         //执行正常的业务逻辑
13         result.ResultCode = 1;
14         result.ResultMsg = "成功";
15     }
16     else   //验证没通过,返回错误信息
17     {
18         result.ResultCode = 0;
19         result.ResultMsg = validaResult.ToString("||");
20     }
21     return Ok(result);
22 }

测试结果,分别返回如下错误提示:

   

   

如果将 Stop  替换成 Continue 会发生什么?

1 CascadeMode = CascadeMode.Stop;
2 替换成:
3 CascadeMode = CascadeMode.Continue;

测试结果如下:错误信息会全部返回  

1 {
2   "resultCode": 0,
3   "resultMsg": "'用户名' 不能为空。||用户名至少2个字符,最多12个字符||'密码' 不能为空。||密码长度至少6个字符,最多16个字符||密码不符合规则,必须包含数字、小写或大写字母、特殊符号||'验证码' 不能为空。||请输入4位验证码"
4 }

如果你嫌每次都要实例化一次对象进行注册,你也可以使用全局注册,直接在 Staup 中注册即可

 1 services.AddControllers()
 2 //记得引入 using FluentValidation.AspNetCore
 3 .AddFluentValidation(option =>
 4  {
 5      //所有验证类继承该接口,使用接口标识 IModelValidator 批量注册
 6      //option.RegisterValidatorsFromAssemblyContaining<IModelValidator>();
 7 
 8      //单个类注册
 9      option.RegisterValidatorsFromAssemblyContaining<LoginValidationAttribute>();
10  });

参考文档:

https://blog.csdn.net/fuluadmin/article/details/114619301

https://docs.microsoft.com/zh-cn/aspnet/core/mvc/models/validation?view=aspnetcore-5.0

作者:PeterZhang
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
posted @ 2021-05-12 23:56  找.net工作(北京)  阅读(173)  评论(0编辑  收藏  举报