ASP.NET Core通過特性實現參數驗證


  微軟在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     protected override ValidationResult IsValid(object value, ValidationContext validationContext)
12     {
13         //這里是驗證的參數的邏輯 value是需要驗證的值  而validationContext中包含了驗證相關的上下文信息 這里我是有一個自己封裝的驗證格式的FormatValidation類
14         if (FormatValidation.IsCombinationOfEnglishNumber(value as string))
15             //驗證成功返回 success
16             return ValidationResult.Success;
17         //不成功 提示驗證錯誤的信息
18         else return new ValidationResult(ErrorMessage ?? error);
19     }
20 }

  這里是實現一個英文字母數字組合的驗證特性,這樣我們就可以把它附在在我們請求的參數上,可以是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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM