微软在ASP.NET Core框架中内置了一些验证参数的特性,让我们可以通过这些特性对API请求中的参数进行验证,常用的特性一般有:
-
[ValidateNever]
: ValidateNeverAttribute 指示应从验证中排除属性或参数。 -
[CreditCard]
:验证属性是否具有信用卡格式。 -
[Compare]
:验证模型中的两个属性是否匹配。 -
[EmailAddress]
:验证属性是否具有电子邮件格式。 -
[Phone]
:验证属性是否具有电话号码格式。 -
[Range]
:验证属性值是否位于指定范围内。 -
[RegularExpression]
:验证 属性值是否与指定的正则表达式匹配。 -
[Required]
:验证字段是否不为 null。 -
[StringLength]
:验证字符串属性值是否不超过指定的长度限制。 -
[Url]
:验证属性是否具有 URL 格式。
但除了上面这些,还缺少一些我们平时在项目中会经常碰到的验证,例如:需要是纯汉字的姓名、必须包含大小写字母和数字的强密码、QQ号、IPV4或者IPV6地址,以及中国的手机号码和身份证号码等等。
当我们碰到这些参数需要验证的时候,我们需要如何实现自定义的验证特性呢?此时微软已经指出,让我们去继承ValidationAttribute类,并重写IsValid()即可。
1 /// <summary> 2 /// 是否是英文字母、数字组合 3 /// </summary> 4 public class EnglishNumberCombinationAttribute : ValidationAttribute 5 { 6 /// <summary> 7 /// 默认的错误提示信息 8 /// </summary> 9 private const string error = "无效的英文字母加数字组合"; 10 11 public EnglishNumberCombinationAttribute() 12 { 13 } 14 15 /// <param name="errorMessage">自定义的错误信息</param> 16 public EnglishNumberCombinationAttribute(string errorMessage) : base(errorMessage) 17 { 18 } 19 20 protected override ValidationResult IsValid(object value, ValidationContext validationContext) 21 { 22 //这里是验证的参数的逻辑 value是需要验证的值 而validationContext中包含了验证相关的上下文信息 这里我是有一个自己封装的验证格式的FormatValidation类 23 if (FormatValidation.IsCombinationOfEnglishNumber(value as string)) 24 //验证成功返回 success 25 return ValidationResult.Success; 26 //不成功 提示验证错误的信息 这里注意不要用ErrorMessage,要用ErrorMessageString 27 else return new ValidationResult(ErrorMessageString ?? error); 28 } 29 }
这里是实现一个英文字母数字组合的验证特性,这样我们就可以把它附在在我们请求的参数上,可以是DTO里的属性,也可以是Action上的形参。
1 public class CreateDTO 2 { 3 [Required] 4 public string StoreName { get; init; } 5 [Required] 6 [EnglishNumberCombination(errorMessage: "UserId必须是英文字母加数字的组合")] 7 public string UserId { get; init; } 8 } 9 10 ...
11
12 [HttpGet] 13 public async ValueTask<ActionResult> Delete([EnglishNumberCombination]string UserId, string StoreName)
Postman测试结果:
至于验证的过程,我看了下源码,具体的过程是当我们在startup中services.AddControllers()或者services.AddMvc()的时候,有一个默认的MvcOptions(这个我们是可以配置的),其中有一个ModelValidatorProviders属性,看名字就知道模型验证提供器。ASP.NET Core实现了默认的提供器:
options.ModelValidatorProviders.Add(new DataAnnotationsModelValidatorProvider( _validationAttributeAdapterProvider, _dataAnnotationLocalizationOptions, _stringLocalizerFactory));
其中_validationAttributeAdapterProvider,是已经依赖注入的IValidationAttributeAdapterProvider,下面是微软实现的代码,感兴趣的小伙伴可以去看一下,可以学到很多设计模式的运用:
1 namespace Microsoft.AspNetCore.Mvc.DataAnnotations 2 { 3 /// <summary> 4 /// Creates an <see cref="IAttributeAdapter"/> for the given attribute. 5 /// </summary> 6 public class ValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider 7 { 8 /// <summary> 9 /// Creates an <see cref="IAttributeAdapter"/> for the given attribute. 10 /// </summary> 11 /// <param name="attribute">The attribute to create an adapter for.</param> 12 /// <param name="stringLocalizer">The localizer to provide to the adapter.</param> 13 /// <returns>An <see cref="IAttributeAdapter"/> for the given attribute.</returns> 14 public IAttributeAdapter? GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer? stringLocalizer) 15 { 16 if (attribute == null) 17 { 18 throw new ArgumentNullException(nameof(attribute)); 19 } 20 21 var type = attribute.GetType(); 22 23 if (typeof(RegularExpressionAttribute).IsAssignableFrom(type)) 24 { 25 return new RegularExpressionAttributeAdapter((RegularExpressionAttribute)attribute, stringLocalizer); 26 } 27 else if (typeof(MaxLengthAttribute).IsAssignableFrom(type)) 28 { 29 return new MaxLengthAttributeAdapter((MaxLengthAttribute)attribute, stringLocalizer); 30 } 31 else if (typeof(RequiredAttribute).IsAssignableFrom(type)) 32 { 33 return new RequiredAttributeAdapter((RequiredAttribute)attribute, stringLocalizer); 34 } 35 else if (typeof(CompareAttribute).IsAssignableFrom(type)) 36 { 37 return new CompareAttributeAdapter((CompareAttribute)attribute, stringLocalizer); 38 } 39 else if (typeof(MinLengthAttribute).IsAssignableFrom(type)) 40 { 41 return new MinLengthAttributeAdapter((MinLengthAttribute)attribute, stringLocalizer); 42 } 43 else if (typeof(CreditCardAttribute).IsAssignableFrom(type)) 44 { 45 return new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "data-val-creditcard", stringLocalizer); 46 } 47 else if (typeof(StringLengthAttribute).IsAssignableFrom(type)) 48 { 49 return new StringLengthAttributeAdapter((StringLengthAttribute)attribute, stringLocalizer); 50 } 51 else if (typeof(RangeAttribute).IsAssignableFrom(type)) 52 { 53 return new RangeAttributeAdapter((RangeAttribute)attribute, stringLocalizer); 54 } 55 else if (typeof(EmailAddressAttribute).IsAssignableFrom(type)) 56 { 57 return new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "data-val-email", stringLocalizer); 58 } 59 else if (typeof(PhoneAttribute).IsAssignableFrom(type)) 60 { 61 return new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "data-val-phone", stringLocalizer); 62 } 63 else if (typeof(UrlAttribute).IsAssignableFrom(type)) 64 { 65 return new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "data-val-url", stringLocalizer); 66 } 67 else if (typeof(FileExtensionsAttribute).IsAssignableFrom(type)) 68 { 69 return new FileExtensionsAttributeAdapter((FileExtensionsAttribute)attribute, stringLocalizer); 70 } 71 else 72 { 73 return null; 74 } 75 } 76 }; 77 }源码
最后附上自己写的验证类,都是一些常用的验证:
1 /// <summary> 2 /// 格式验证 3 /// </summary> 4 public static class FormatValidation 5 { 6 private readonly static Regex IPV4Regex = new(@"^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$", RegexOptions.Compiled); 7 private readonly static Regex IPV6Regex = new(@"^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$", RegexOptions.Compiled); 8 private readonly static Regex DomainRegex = new(@"^[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+\.?$", RegexOptions.Compiled); 9 private readonly static Regex UrlRegex = new(@"^[a-zA-z]+://[^\s]*$", RegexOptions.Compiled); 10 private readonly static Regex PhoneNumberRegex = new(@"^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$", RegexOptions.Compiled); 11 private readonly static Regex EnglishRegex = new(@"^[A-Za-z]+$", RegexOptions.Compiled); 12 private readonly static Regex IdentityNumberRegex = new(@"(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)", RegexOptions.Compiled); 13 private readonly static Regex EmailRegex = new(@"^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$", RegexOptions.Compiled); 14 private readonly static Regex ChineseRegex = new(@"^[\u4e00-\u9fa5]{0,}$", RegexOptions.Compiled); 15 private readonly static Regex LandlineRegex = new(@"^\d{3}-\d{8}|\d{4}-\d{7}|\d{7}$", RegexOptions.Compiled); 16 17 /// <summary> 18 /// 是否是IPV4格式的IP 19 /// </summary> 20 /// <returns></returns> 21 public static bool IsIPV4(string input) 22 { 23 return IPV4Regex.IsMatch(input); 24 } 25 26 /// <summary> 27 /// 是否是IPV6格式的IP 28 /// </summary> 29 /// <returns></returns> 30 public static bool IsIPV6(string input) 31 { 32 return IPV6Regex.IsMatch(input); 33 } 34 35 /// <summary> 36 /// 是否是一个域名 37 /// </summary> 38 /// <returns></returns> 39 public static bool IsDomain(string input) 40 { 41 return DomainRegex.IsMatch(input); 42 } 43 44 /// <summary> 45 /// 是否是一个网址 46 /// </summary> 47 /// <returns></returns> 48 public static bool IsUrl(string input) 49 { 50 return UrlRegex.IsMatch(input); 51 } 52 53 /// <summary> 54 /// 是否是一个手机号码(*) 55 /// </summary> 56 /// <returns></returns> 57 public static bool IsPhoneNumber(string input) 58 { 59 return PhoneNumberRegex.IsMatch(input); 60 } 61 62 /// <summary> 63 /// 是否是纯英文字母 64 /// </summary> 65 /// <returns></returns> 66 public static bool IsEnglish(string input) 67 { 68 return EnglishRegex.IsMatch(input); 69 } 70 71 /// <summary> 72 /// 只包含英文字母和数字的组合 73 /// </summary> 74 /// <returns></returns> 75 public static bool IsCombinationOfEnglishNumber(string input, int? minLength = null, int? maxLength = null) 76 { 77 var pattern = @"(?=.*\d)(?=.*[a-zA-Z])[a-zA-Z0-9]"; 78 if (minLength is null && maxLength is null) 79 pattern = $@"^{pattern}+$"; 80 else if (minLength is not null && maxLength is null) 81 pattern = $@"^{pattern}{{{minLength},}}$"; 82 else if (minLength is null && maxLength is not null) 83 pattern = $@"^{pattern}{{1,{maxLength}}}$"; 84 else 85 pattern = $@"^{pattern}{{{minLength},{maxLength}}}$"; 86 return Regex.IsMatch(input, pattern); 87 } 88 89 /// <summary> 90 /// 只包含英文字母、数字和特殊字符的组合 91 /// </summary> 92 /// <returns></returns> 93 public static bool IsCombinationOfEnglishNumberSymbol(string input, int? minLength = null, int? maxLength = null) 94 { 95 var pattern = @"(?=.*\d)(?=.*[a-zA-Z])(?=.*[^a-zA-Z\d])."; 96 if (minLength is null && maxLength is null) 97 pattern = $@"^{pattern}+$"; 98 else if (minLength is not null && maxLength is null) 99 pattern = $@"^{pattern}{{{minLength},}}$"; 100 else if (minLength is null && maxLength is not null) 101 pattern = $@"^{pattern}{{1,{maxLength}}}$"; 102 else 103 pattern = $@"^{pattern}{{{minLength},{maxLength}}}$"; 104 return Regex.IsMatch(input, pattern); 105 } 106 107 /// <summary> 108 /// 是否是身份证号码(*) 109 /// </summary> 110 /// <returns></returns> 111 public static bool IsIdentityNumber(string input) 112 { 113 return IdentityNumberRegex.IsMatch(input); 114 } 115 116 /// <summary> 117 /// 是否是电子邮箱 118 /// </summary> 119 /// <returns></returns> 120 public static bool IsEmail(string input) 121 { 122 return EmailRegex.IsMatch(input); 123 } 124 125 /// <summary> 126 /// 是否是汉字 127 /// </summary> 128 /// <returns></returns> 129 public static bool IsChinese(string input) 130 { 131 return ChineseRegex.IsMatch(input); 132 } 133 134 /// <summary> 135 /// 是否是座机号码(*) 136 /// </summary> 137 /// <returns></returns> 138 public static bool IsLandline(string input) 139 { 140 return LandlineRegex.IsMatch(input); 141 } 142 }
每天了解多一点,日积月累,基础就会慢慢牢固。
author:https://www.cnblogs.com/abnerwong/