首页前端开发HTML使用 DataAnnotations(数据注解)实现模型的通用数据校验

使用 DataAnnotations(数据注解)实现模型的通用数据校验

时间2023-04-04 10:02:01发布访客分类HTML浏览1652
导读:参数校验的意义在实际项目开发中,无论任何方式、任何规模的开发模式,项目中都离不开对接入数据模型参数的合法性校验,目前普片的开发模式基本是前后端分离,当用户在前端页面中输入一些表单数据时,点击提交按钮,触发请求目标服务器的一系列后续操作,在这...

参数校验的意义

在实际项目开发中,无论任何方式、任何规模的开发模式,项目中都离不开对接入数据模型参数的合法性校验,目前普片的开发模式基本是前后端分离,当用户在前端页面中输入一些表单数据时,点击提交按钮,触发请求目标服务器的一系列后续操作,在这中间的执行过程中(标准做法推荐)无论是前端代码部分,还是服务端代码部分都应该有针对用户输入数据的合法性校验,典型做法如下:

  • 前端部分:当用户在页面输入表单数据时,前端监听页面表单事件触发相应的数据合法性校验规则,当数据非法时,合理的提示用户数据错误,只有当所有表单数据都校验通过后,才继续提交数据给目标后端对应的接口;
  • 后端部分:当前端数据合法校验通过后,向目标服务器提交表单数据时,服务端接收到相应的提交数据,在入口源头出就应该触发相关的合法性校验规则,当数据都校验通过后,继续执行后续的相关业务逻辑处理,反之则响应相关非法数据的提示信息;
特别说明:在实际的项目中,无论前端部分还是服务端部分,参数的校验都是很有必要性的。无效的参数,可能会导致应用程序的异常和一些不可预知的错误行为。

常用参数的校验

这里例举一些项目中比较常用的参数模型校验项,如下所示:

  • Name:姓名校验,比如需要是纯汉字的姓名;
  • Password:密码强度验证,比如要求用户输入必须包含大小写字母、数字和特殊符号的强密码;
  • QQ号:QQ 号码验证,是否是有效合法的 QQ 号码;
  • China Postal Code:中国邮政编码;
  • IP Address:IPV4 或者 IPV6 地址验证;
  • Phone:手机号码或者座机号码合法性验证;
  • ID Card:身份证号码验证,比如:15 位和 18 位数身份证号码;
  • Email Address:邮箱地址的合法性校验;
  • String:字符串验证,比如字段是否不为 null、长度是否超限;
  • URL:验证属性是否具有 URL 格式;
  • Number:数值型参数校验,数值范围校验,比如非负数,非负整数,正整数等;
  • File:文件路径及扩展名校验;

对于参数校验,常见的方式有正则匹配校验,通过对目标参数编写合法的正则表达式,实现对参数合法性的校验。

.NET 中内置 DataAnnotations 提供的特性校验

上面我们介绍了一些常用的参数验证项,接下来我们来了解下在 .NET 中内置提供的 DataAnnotations 数据注解,该类提供了一些常用的验证参数特性。

官方解释:

  • 提供用于为 ASP.NET MVCASP.NET 数据控件定义元数据的特性类。
  • 该类位于 System.ComponentModel.DataAnnotations 命名空间。

关于 DataAnnotations 中的特性介绍

让我们可以通过这些特性对 API 请求中的参数进行验证,常用的特性一般有:

  • [ValidateNever]: 指示应从验证中排除属性或参数。
  • [CreditCard]:验证属性是否具有信用卡格式。
  • [Compare]:验证模型中的两个属性是否匹配。
  • [EmailAddress]:验证属性是否具有电子邮件格式。
  • [Phone]:验证属性是否具有电话号码格式。
  • [Range]:验证属性值是否位于指定范围内。
  • [RegularExpression]:验证属性值是否与指定的正则表达式匹配。
  • [Required]:验证字段是否不为 null。
  • [StringLength]:验证字符串属性值是否不超过指定的长度限制。
  • [Url]:验证属性是否具有 URL 格式。

其中 RegularExpression 特性,基于正则表达式可以扩展实现很多常用的验证类型,下面的( 基于 DataAnnotations 的通用模型校验封装 )环节举例说明;

关于该类更多详细信息请查看,https://learn.microsoft.com/zh-cn/dotnet/api/system.componentmodel.dataannotations?view=net-7.0

基于 DataAnnotations 的通用模型校验封装

此处主要是使用了 Validator.TryValidateObject() 方法:

Validator.TryValidateObject(object instance, ValidationContext validationContext, ICollectionValidationResult>
    ? validationResults, bool validateAllProperties);
    

Validator 类提供如下校验方法:

基于 DataAnnotations 的特性校验助手实现步骤

  • 错误成员对象类 ErrorMember
namespace Jeff.Common.Validatetion;
    

/// summary>
    
/// 错误成员对象
/// /summary>

public class ErrorMember
{
    
    /// summary>
    
    /// 错误信息
    /// /summary>

    public string? ErrorMessage {
     get;
     set;
 }
    
    /// summary>
    
    /// 错误成员名称
    /// /summary>

    public string? ErrorMemberName {
     get;
     set;
 }

}
    
  • 验证结果类 ValidResult
namespace Jeff.Common.Validatetion;
    

/// summary>
    
/// 验证结果类
/// /summary>

public class ValidResult
{

    public ValidResult()
    {
    
        ErrorMembers = new ListErrorMember>
    ();

    }
    
    /// summary>
    
    /// 错误成员列表
    /// /summary>
    
    public ListErrorMember>
 ErrorMembers {
     get;
     set;
 }
    
    /// summary>
    
    /// 验证结果
    /// /summary>

    public bool IsVaild {
     get;
     set;
 }

}
    
  • 定义操作正则表达式的公共类 RegexHelper(基于 RegularExpression 特性扩展)
using System;
    
using System.Net;
    
using System.Text.RegularExpressions;
    

namespace Jeff.Common.Validatetion;
    

/// summary>
    
/// 操作正则表达式的公共类
/// Regex 用法参考:https://learn.microsoft.com/zh-cn/dotnet/api/system.text.regularexpressions.regex.-ctor?redirectedfrom=MSDN&
    view=net-7.0
/// /summary>
   
public class RegexHelper
{

    #region 常用正则验证模式字符串
    public enum ValidateType
    {

        Email,                 // 邮箱
        TelePhoneNumber,       // 固定电话(座机)
        MobilePhoneNumber,     // 移动电话
        Age,                   // 年龄(1-120 之间有效)
        Birthday,              // 出生日期
        Timespan,              // 时间戳
        IdentityCardNumber,    // 身份证
        IpV4,                  // IPv4 地址
        IpV6,                  // IPV6 地址
        Domain,                // 域名
        English,               // 英文字母
        Chinese,               // 汉字
        MacAddress,            // MAC 地址
        Url,                   // URL 
    }
    

    private static readonly DictionaryValidateType, string>
     keyValuePairs = new DictionaryValidateType, string>

    {

       {
 ValidateType.Email, _Email }
,
       {
 ValidateType.TelePhoneNumber,_TelephoneNumber }
,  
       {
 ValidateType.MobilePhoneNumber,_MobilePhoneNumber }
, 
       {
 ValidateType.Age,_Age }
, 
       {
 ValidateType.Birthday,_Birthday }
, 
       {
 ValidateType.Timespan,_Timespan }
, 
       {
 ValidateType.IdentityCardNumber,_IdentityCardNumber }
, 
       {
 ValidateType.IpV4,_IpV4 }
, 
       {
 ValidateType.IpV6,_IpV6 }
, 
       {
 ValidateType.Domain,_Domain }
, 
       {
 ValidateType.English,_English }
, 
       {
 ValidateType.Chinese,_Chinese }
, 
       {
 ValidateType.MacAddress,_MacAddress }
, 
       {
 ValidateType.Url,_Url }
, 
    }
    ;
    

    public const string _Email = @"^(\w)+(\.\w)*@(\w)+((\.\w+)+)$";
 // ^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$ , [A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{
2,4}

    public const string _TelephoneNumber = @"(d+-)?(d{
4}
-?d{
7}
|d{
3}
-?d{
8}
|^d{
7,8}
    )(-d+)?";
 //座机号码(中国大陆)
    public const string _MobilePhoneNumber = @"^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{
8}
    $";
     //移动电话
    public const string _Age = @"^(?:[1-9][0-9]?|1[01][0-9]|120)$";
 // 年龄 1-120 之间有效
    public const string _Birthday = @"^((?:19[2-9]\d{
1}
    )|(?:20(?:(?:0[0-9])|(?:1[0-8]))))((?:0?[1-9])|(?:1[0-2]))((?:0?[1-9])|(?:[1-2][0-9])|30|31)$";

    public const string _Timespan = @"^15|16|17\d{
8,11}
    $";
 // 目前时间戳是15开头,以后16、17等开头,长度 10 位是秒级时间戳的正则,13 位时间戳是到毫秒级的。
    public const string _IdentityCardNumber = @"^[1-9]\d{
7}
((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{
3}
$|^[1-9]\d{
5}
[1-9]\d{
3}
((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{
3}
    ([0-9]|X)$";

    public const string _IpV4 = @"^((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}
    $";

    public const string _IpV6 = @"^\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*$";

    public const string _Domain = @"^[a-zA-Z0-9][-a-zA-Z0-9]{
0,62}
(\.[a-zA-Z0-9][-a-zA-Z0-9]{
0,62}
    )+\.?$";
    
    public const string _English = @"^[A-Za-z]+$";

    public const string _Chinese = @"^[\u4e00-\u9fa5]{
0,}
    $";

    public const string _MacAddress = @"^([0-9A-F]{
2}
)(-[0-9A-F]{
2}
){
5}
    $";
    
    public const string _Url = @"^[a-zA-z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\S*)?$";
    
    #endregion

    /// summary>
    
    /// 获取验证模式字符串
    /// /summary>
    
    /// param name="validateType">
    /param>
    
    /// returns>
    /returns>

    public static (bool hasPattern, string pattern) GetValidatePattern(ValidateType validateType) 
    {
    
        bool hasPattern = keyValuePairs.TryGetValue(validateType, out string? pattern);
    
        return (hasPattern, pattern ?? string.Empty);

    }
    

    #region 验证输入字符串是否与模式字符串匹配
    /// summary>
    
    /// 验证输入字符串是否与模式字符串匹配
    /// /summary>
    
    /// param name="input">
    输入的字符串/param>
    
    /// param name="validateType">
    模式字符串类型/param>
    
    /// param name="matchTimeout">
    超时间隔/param>
    
    /// param name="options">
    筛选条件/param>
    
    /// returns>
    /returns>

    public static (bool isMatch, string info) IsMatch(string input, ValidateType validateType, TimeSpan matchTimeout, RegexOptions options = RegexOptions.None)
    {
    
        var (hasPattern, pattern) = GetValidatePattern(validateType);
    
        if (hasPattern &
    &
 !string.IsNullOrWhiteSpace(pattern))
        {
    
            bool isMatch = IsMatch(input, pattern, matchTimeout, options);
    
            if (isMatch) return (true, "Format validation passed.");
     // 格式验证通过。
            else return (false, "Format validation failed.");
 // 格式验证未通过。
        }
    

        return (false, "Unknown ValidatePattern.");
 // 未知验证模式
    }
    

    /// summary>
    
    /// 验证输入字符串是否与模式字符串匹配,匹配返回true
    /// /summary>
    
    /// param name="input">
    输入字符串/param>
    
    /// param name="pattern">
    模式字符串/param>
        
    /// returns>
    /returns>

    public static bool IsMatch(string input, string pattern)
    {
    
        return IsMatch(input, pattern, TimeSpan.Zero, RegexOptions.IgnoreCase);

    }
    

    /// summary>
    
    /// 验证输入字符串是否与模式字符串匹配,匹配返回true
    /// /summary>
    
    /// param name="input">
    输入的字符串/param>
    
    /// param name="pattern">
    模式字符串/param>
    
    /// param name="matchTimeout">
    超时间隔/param>
    
    /// param name="options">
    筛选条件/param>
    
    /// returns>
    /returns>

    public static bool IsMatch(string input, string pattern, TimeSpan matchTimeout, RegexOptions options = RegexOptions.None)
    {
    
        return Regex.IsMatch(input, pattern, options, matchTimeout);

    }

    #endregion
}
    
  • 定义验证结果统一模型格式类 ResponseInfo(此类通常也是通用的数据响应模型类)
namespace Jeff.Common.Model;
    

public sealed class ResponseInfoT>
 where T : class
{
    
    /*
     Microsoft.AspNetCore.Http.StatusCodes
     System.Net.HttpStatusCode
     */

    /// summary>
    
    /// 响应代码(自定义)
    /// /summary>

    public int Code {
     get;
     set;
 }
    

    /// summary>
    
    /// 接口状态
    /// /summary>

    public bool Success {
     get;
     set;
 }
    

    #region 此处可以考虑多语言国际化设计(语言提示代号对照表)
    /// summary>
    
    /// 语言对照码,参考:https://blog.csdn.net/shenenhua/article/details/79150053
    /// /summary>

    public string Lang {
     get;
     set;
 }
     = "zh-cn";
    

    /// summary>
    
    /// 提示信息
    /// /summary>

    public string Message {
     get;
     set;
 }
     = string.Empty;
    
    #endregion

    /// summary>
    
    /// 数据体
    /// /summary>

    public T? Data {
     get;
     set;
 }

}
    
  • 实现验证助手类 ValidatetionHelper,配合 System.ComponentModel.DataAnnotations 类使用
// 数据注解,https://learn.microsoft.com/zh-cn/dotnet/api/system.componentmodel.dataannotations?view=net-7.0
using System.ComponentModel.DataAnnotations;
    
using Jeff.Common.Model;
    

namespace Jeff.Common.Validatetion;
    

/// summary>
    
/// 验证助手类
/// /summary>

public sealed class ValidatetionHelper
{
    
    /// summary>
    
    /// DTO 模型校验
    /// /summary>
    
    /// param name="value">
    /param>
    
    /// returns>
    /returns>

    public static ValidResult IsValid(object value)
    {
    
        var result = new ValidResult();

        try
        {
    
            var validationContext = new ValidationContext(value);
    
            var results = new ListValidationResult>
    ();
    
            bool isValid = Validator.TryValidateObject(value, validationContext, results, true);
    
            result.IsVaild = isValid;


            if (!isValid)
            {

                foreach (ValidationResult? item in results)
                {

                    result.ErrorMembers.Add(new ErrorMember()
                    {

                        ErrorMessage = item.ErrorMessage,
                        ErrorMemberName = item.MemberNames.FirstOrDefault()
                    }
    );

                }

            }

        }

        catch (ValidationException ex)
        {
    
            result.IsVaild = false;
    
            result.ErrorMembers = new ListErrorMember>

            {

                new ErrorMember()
                {

                    ErrorMessage = ex.Message,
                    ErrorMemberName = "Internal error"
                }

            }
    ;

        }
    

        return result;

    }
    

    /// summary>
    
    /// DTO 模型校验统一响应信息
    /// /summary>
    
    /// typeparam name="T">
    /typeparam>
    
    /// param name="model">
    /param>
    
    /// returns>
    /returns>
    
    public static ResponseInfoValidResult>
     GetValidInfoT>
(T model) where T : class
    {
    
        var result = new ResponseInfoValidResult>
    ();
    
        
        var validResult = IsValid(model);

        if (!validResult.IsVaild)
        {
    
            result.Code = 420;
    
            result.Message = "DTO 模型参数值异常";
    
            result.Success = false;
    
            result.Data = validResult;

        }

        else
        {
    
            result.Code = 200;
    
            result.Success = true;
    
            result.Message = "DTO 模型参数值合法";

        }
    

        return result;

    }

}
    

如何使用 DataAnnotations 封装的特性校验助手?

  • 首先定义一个数据模型类(DTO),添加校验特性 ValidationAttribute
using System.ComponentModel.DataAnnotations;
    
using Jeff.Common.Validatetion;
    

namespace Jeff.Comm.Test;


public class Person
{

    [Display(Name = "姓名"), Required(ErrorMessage = "{
0}
必须填写")]
    public string Name {
     get;
     set;
 }


    [Display(Name = "邮箱")]
    [Required(ErrorMessage = "{
0}
必须填写")]
    [RegularExpression(RegexHelper._Email, ErrorMessage = "RegularExpression: {
0}
格式非法")]
    [EmailAddress(ErrorMessage = "EmailAddress: {
0}
格式非法")]
    public string Email {
     get;
     set;
 }


    [Display(Name = "Age年龄")]
    [Required(ErrorMessage = "{
0}
必须填写")]
    [Range(1, 120, ErrorMessage = "超出范围")]
    [RegularExpression(RegexHelper._Age, ErrorMessage = "{
0}
超出合理范围")]
    public int Age {
     get;
     set;
 }


    [Display(Name = "Birthday出生日期")]
    [Required(ErrorMessage = "{
0}
必须填写")]
    [RegularExpression(RegexHelper._Timespan, ErrorMessage = "{
0}
超出合理范围")]
    public TimeSpan Birthday {
     get;
     set;
 }


    [Display(Name = "Address住址")]
    [Required(ErrorMessage = "{
0}
必须填写")]
    [StringLength(200, MinimumLength = 10, ErrorMessage = "{
0}
输入长度不正确")]
    public string Address {
     get;
     set;
 }


    [Display(Name = "Mobile手机号码")]
    [Required(ErrorMessage = "{
0}
必须填写")]
    [RegularExpression(RegexHelper._MobilePhoneNumber, ErrorMessage = "{
0}
格式非法")]
    public string Mobile {
     get;
     set;
 }


    [Display(Name = "Salary薪水")]
    [Required(ErrorMessage = "{
0}
必须填写")]
    [Range(typeof(decimal), "1000.00", "3000.99")]
    public decimal Salary {
     get;
     set;
 }


    [Display(Name = "MyUrl连接")]
    [Required(ErrorMessage = "{
0}
必须填写")]
    [Url(ErrorMessage = "Url:{
0}
格式非法")]
    [RegularExpression(RegexHelper._Url, ErrorMessage = "RegularExpression:{
0}
格式非法")]
    public string MyUrl {
     get;
     set;
 }

}
  • 控制台调用通用校验助手验证方法 ValidatetionHelper.IsValid()ValidatetionHelper.GetValidInfo()
// 通用模型数据验证测试
static void ValidatetionTest() 
{

    var p = new Person
    {

        Name = "",
        Age = -10,
        Email = "www.baidu.com",
        MobilePhoneNumber = "12345",
        Salary = 4000,
        MyUrl = "aaa"
    }
    ;
    

    // 调用通用模型校验
    var result = ValidatetionHelper.IsValid(p);

    if (!result.IsVaild)
    {

        foreach (ErrorMember errorMember in result.ErrorMembers)
        {

            // 控制台打印字段验证信息
            Console.WriteLine($"{
errorMember.ErrorMemberName}
:{
errorMember.ErrorMessage}
    ");

        }

    }
    
    Console.WriteLine();
    

    // 调用通用模型校验,返回统一数据格式
    var validInfo = ValidatetionHelper.GetValidInfo(p);

    var options = new JsonSerializerOptions
    {

        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, // 设置中文编码乱码
        WriteIndented = false
    }
    ;
    
    string jsonStr = JsonSerializer.Serialize(validInfo, options);

    Console.WriteLine($"校验结果返回统一数据格式:{
jsonStr}
    ");

}

在控制台Program.Main 方法中调用 ValidatetionTest() 方法:

internal class Program
{

    static void Main(string[] args)
    {
    
        Console.WriteLine("Hello, World!");

        {
    
            #region 数据注解(DataAnnotations)模型验证
            ValidatetionTest();
 
            #endregion
        }
    
        Console.ReadKey();

    }
    

启动控制台,输出如下信息:

如何实现自定义的验证特性?

当我们碰到这些参数需要验证的时候,而上面内置类提供的特性不能满足需求时,此时我们可以实现自定义的验证特性来满足校验需求,按照微软给出的编码规则,我们只需继承 ValidationAttribute 类,并重写 IsValid() 方法即可。

自定义校验特性案例

比如实现一个密码强度的验证,实现步骤如下:

  • 定义密码强度规则,只包含英文字母、数字和特殊字符的组合,并且组合长度至少 8 位数
/// summary>
    
/// 只包含英文字母、数字和特殊字符的组合
/// /summary>
    
/// returns>
    /returns>

public static bool IsCombinationOfEnglishNumberSymbol(string input, int? minLength = null, int? maxLength = null)
{
    
    var pattern = @"(?=.*\d)(?=.*[a-zA-Z])(?=.*[^a-zA-Z\d]).";
    
    if (minLength is null &
    &
 maxLength is null)
        pattern = $@"^{
pattern}
    +$";
    
    else if (minLength is not null &
    &
 maxLength is null)
        pattern = $@"^{
pattern}
{
{
{
minLength}
,}
}
    $";
    
    else if (minLength is null &
    &
 maxLength is not null)
        pattern = $@"^{
pattern}
{
{
1,{
maxLength}
}
}
    $";

    else
        pattern = $@"^{
pattern}
{
{
{
minLength}
,{
maxLength}
}
}
    $";
    
    return Regex.IsMatch(input, pattern);

}
    
  • 实现自定义特性 EnglishNumberSymbolCombinationAttribute,继承自 ValidationAttribute
using System.ComponentModel.DataAnnotations;
    

namespace Jeff.Common.Validatetion.CustomAttributes;
    

/// summary>
    
/// 是否是英文字母、数字和特殊字符的组合
/// /summary>

public class EnglishNumberSymbolCombinationAttribute : ValidationAttribute
{
    
    /// summary>
    
    /// 默认的错误提示信息
    /// /summary>
    
    private const string error = "无效的英文字母、数字和特殊字符的组合";


    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
    
        if (value is null) return new ValidationResult("参数值为 null");


        //if (value is null)
        //{
    
        //    throw new ArgumentNullException(nameof(attribute));

        //}
    

        // 验证参数逻辑 value 是需要验证的值,而 validationContext 中包含了验证相关的上下文信息,这里可自己封装一个验证格式的 FormatValidation 类
        if (FormatValidation.IsCombinationOfEnglishNumberSymbol(value as string, 8))
            //验证成功返回 success
            return ValidationResult.Success;
    
        //不成功 提示验证错误的信息
        else return new ValidationResult(ErrorMessage ?? error);

    }

}

以上就实现了一个自定义规则的 自定义验证特性,使用方式很简单,可以把它附属在我们 请求的参数 上或者 DTO 里的属性,也可以是 Action 上的形参,如下所示:

public class CreateDTO
{

   [Required]
   public string StoreName {
     get;
     init;
 }

   [Required]
   // 附属在 DTO 里的属性
   [EnglishNumberSymbolCombination(ErrorMessage = "UserId 必须是英文字母、数字和特殊符号的组合")]
   public string UserId {
     get;
     init;
 }

}
    
...
// 附属在 Action 上的形参
[HttpGet]
public async ValueTaskActionResult>
     Delete([EnglishNumberSymbolCombination]string userId, string storeName)

该自定义验证特性还可以结合 DataAnnotations 内置的 [Compare] 特性,可以实现账号注册的密码确认验证(输入密码和确认密码是否一致性)。关于更多自定义参数校验特性,感兴趣的小伙伴可参照上面案例的实现思路,自行扩展实现哟。

总结

对于模型参数的校验,在实际项目系统中是非常有必要性的(通常在数据源头提供验证),利用 .NET 内置的 DataAnnotations(数据注解)提供的特性校验,可以很方便的实现通用的模型校验助手,关于其他特性的用法,请自行参考微软官方文档,这里注意下RegularExpressionAttribute(指定 ASP.NET 动态数据中的数据字段值必须与指定的正则表达式匹配),该特性可以方便的接入正则匹配验证,当遇到复杂的参数校验时,可以快速方便的扩展自定义校验特性,从此告别传统编码中各种 if(xxx != yyyy) 判断的验证,让整体代码编写更佳简练干净。

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!

开发框架前端开发网络协议.NETAPI数据安全/隐私保护

若转载请注明出处: 使用 DataAnnotations(数据注解)实现模型的通用数据校验
本文地址: https://pptw.com/jishu/954.html
651651(651651开头是什么银行) ASP.NET大型医院LIS系统管理源码(医院医疗管理系统源码)

游客 回复需填写必要信息