.Net Core 5.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