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

时间:2022-12-27 11:56:05

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

参数校验的意义

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

  • 前端部分:当用户在页面输入表单数据时,前端监听页面表单事件触发相应的数据合法性校验规则,当数据非法时,合理的提示用户数据错误,只有当所有表单数据都校验通过后,才继续提交数据给目标后端对应的接口;
  • 后端部分:当前端数据合法校验通过后,向目标服务器提交表单数据时,服务端接收到相应的提交数据,在入口源头出就应该触发相关的合法性校验规则,当数据都校验通过后,继续执行后续的相关业务逻辑处理,反之则响应相关非法数据的提示信息;

特别说明:在实际的项目中,无论前端部分还是服务端部分,参数的校验都是很有必要性的。无效的参数,可能会导致应用程序的异常和一些不可预知的错误行为。

常用参数的校验

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

  • 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, ICollection<ValidationResult>? validationResults, bool validateAllProperties);

Validator 类提供如下校验方法:

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

基于 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 List<ErrorMember>();
    }
    /// <summary>
    /// 错误成员列表
    /// </summary>
    public List<ErrorMember> 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 Dictionary<ValidateType, string> keyValuePairs = new Dictionary<ValidateType, 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 ResponseInfo<T> 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 List<ValidationResult>();
            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 List<ErrorMember>
            {
                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 ResponseInfo<ValidResult> GetValidInfo<T>(T model) where T : class
    {
        var result = new ResponseInfo<ValidResult>();
        
        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();
    }

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

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

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

当我们碰到这些参数需要验证的时候,而上面内置类提供的特性不能满足需求时,此时我们可以实现自定义的验证特性来满足校验需求,按照微软给出的编码规则,我们只需继承 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 ValueTask<ActionResult> Delete([EnglishNumberSymbolCombination]string userId, string storeName)

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

总结

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