zoukankan      html  css  js  c++  java
  • .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
    本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。
  • 相关阅读:
    团队项目-第一阶段冲刺2
    团队项目-第一阶段冲刺1
    第九周总结
    梦断代码阅读笔记02
    第八周总结
    团队会议个人领取任务
    梦断代码阅读笔记01
    Spring Boot 揭秘与实战(二) 数据缓存篇
    Spring Boot 揭秘与实战(二) 数据缓存篇
    Spring Boot 揭秘与实战(二) 数据缓存篇
  • 原文地址:https://www.cnblogs.com/peterzhang123/p/13994852.html
Copyright © 2011-2022 走看看