找回密码
 立即注册
首页 业界区 业界 Util应用框架基础(四) - 验证

Util应用框架基础(四) - 验证

哈梨尔 2025-6-9 08:23:03
本节介绍Util应用框架如何进行验证.
概述

验证是业务健壮性的基础.
.Net 提供了一套称为 DataAnnotations 数据注解的方法,可以对属性进行一些基本验证,比如必填项验证,长度验证等.
Util应用框架使用标准的数据注解作为基础验证,并对自定义验证进行扩展.
基础用法

引用Nuget包

Nuget包名: Util.Validation.
通常不需要手工引用它.
数据注解

数据注解是一种.Net 特性 Attribute,可以在属性上应用它们.
常用数据注解

下面列出一些常用数据注解,如果还不能满足需求,可以创建自定义的数据注解.

  • RequiredAttribute 必填项验证
    [Required] 验证属性不能是空值.
    范例:
    1.   public class Test {
    2.       [Required]
    3.       public string Name { get; set; }
    4.   }
    复制代码
    [Required] 支持一些参数,可以设置验证失败的提示消息.
    1.   public class Test {
    2.       [Required(ErrorMessage = "名称不能为空")]
    3.       public string Name { get; set; }
    4.   }
    复制代码
  • StringLengthAttribute 字符串长度验证
    [StringLength] 可以对字符串长度进行验证.
    下面的例子验证 Name 属性的字符串最大长度为 5.
    1.   public class Test {
    2.       [StringLength(5)]
    3.       public string Name { get; set; }
    4.   }
    复制代码
    还可以同时设置最小长度.
    下面验证 Name 属性字符串最小长度为1,最大长度为 5.
    1.   public class Test {
    2.       [StringLength(5,MinimumLength = 1)]
    3.       public string Name { get; set; }
    4.   }
    复制代码
  • MaxLengthAttribute 字符串最大长度验证
    [MaxLength] 也可以用来验证字符串最大长度.
    验证 Name 属性的字符串最大长度为 5.
    1.   public class Test {
    2.       [MaxLength(5)]
    3.       public string Name { get; set; }
    4.   }
    复制代码
  • MinLengthAttribute 字符串最小长度验证
    [MinLength] 也可以用来验证字符串最小长度.
    验证 Name 属性的字符串最小长度为 1.
    1.   public class Test {
    2.       [MinLength(1)]
    3.       public string Name { get; set; }
    4.   }
    复制代码
  • RangeAttribute 数值范围验证
    [Range] 用于验证数值范围.
    下面验证 Money 属性的值必须在 1 到 5 之间的范围.
    1.   public class Test {
    2.       [Range( 1, 5 )]
    3.       public int Money { get; set; }
    4.   }
    复制代码
  • EmailAddressAttribute 电子邮件验证
    [EmailAddress] 用于验证电子邮件的格式.
    1.   public class Test {
    2.       [EmailAddress]
    3.       public int Email { get; set; }
    4.   }
    复制代码
  • PhoneAttribute 手机号验证
    [Phone] 用于验证手机号的格式.
    1.   public class Test {
    2.       [Phone]
    3.       public int Tel { get; set; }
    4.   }
    复制代码
  • IdCardAttribute 身份证验证
    [IdCard] 用于验证身份证的格式.
    它是一个Util应用框架自定义的数据注解.
    1.   public class Test {
    2.       [IdCard]
    3.       public int IdCard { get; set; }
    4.   }
    复制代码
  • UrlAttribute Url验证
    [Url] 用于验证网址格式.
    1.   public class Test {
    2.       [Url]
    3.       public int Url { get; set; }
    4.   }
    复制代码
  • RegularExpressionAttribute 正则表达式验证
    [RegularExpression] 可以使用正则表达式进行验证.
    由于正则表达式比较复杂,对于经常使用的场景,应封装成自定义数据注解.
    下面使用正则表达式验证身份证,可以封装到 [IdCard] 数据注解,从而避免正则表达式的复杂性.
    1.   public class Test {
    2.       [RegularExpression( @"(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)" )]
    3.       public string IdCard { get; set; }
    4.   }
    复制代码
验证数据注解

虽然在对象属性上添加了数据注解,但它们并不会自动触发验证.
你可以使用 Asp.Net Core 提供的方法验证对象上的数据注解.
Util 提供了一个辅助方法 Util.Validation.DataAnnotationValidation.Validate 用来验证数据注解.
DataAnnotationValidation.Validate 方法接收一个对象参数,只需将要验证的对象实例传入即可.
返回类型为验证结果集合,包含所有验证失败的消息.
  1.     public class Test {
  2.         [Required]
  3.         public string Name { get; set; }
  4.         public ValidationResultCollection Validate() {
  5.             return DataAnnotationValidation.Validate( this );
  6.         }
  7.     }
复制代码
大部分情况下,你并不需要调用 DataAnnotationValidation.Validate 方法验证数据注解.
实体,值对象,DTO等对象已经内置了 Validate 方法,它们会自动验证数据注解.
Util Angular UI 数据注解验证支持

Util Angular UI支持 Razor TagHelper服务端标签语法.
可以在表单组件使用 Lambda表达式绑定 DTO 对象属性.
TestDto参数对象 Name 属性使用 [Required] 设置必填项验证.
  1.     public class TestDto : DtoBase {
  2.         [Required]
  3.         [Display(Name = "name")]
  4.         public string Name { get; set; }
  5.     }
复制代码
Razor 页面声明 TestDto 模型, 定义输入框 util-input,使用 for 属性绑定到 TestDto 参数对象的 Name 属性.
  1. @page
  2. @model TestDto
  3. <util-form>
  4.     <util-input id="input_Name" for="Name" />
  5. </util-form>
复制代码
Razor页面最终会生成html,表单标签 nz-form-label 添加了 nzRequired 必填项属性, 输入框 input 添加了 required 必填项属性.
  1. <form nz-form>
  2.     <nz-form-item>
  3.         <nz-form-label [nzRequired]="true">name</nz-form-label>
  4.         <nz-form-control [nzErrorTip]="vt_input_Name">
  5.             <input #input_Name="" #v_input_Name="xValidationExtend" name="name" nz-input="" x-validation-extend="" [(ngModel)]="model.name" [required]="true" />
  6.             <ng-template #vt_input_Name="">{{v_input_Name.getErrorMessage()}}</ng-template>
  7.         </nz-form-control>
  8.     </nz-form-item>
  9. </form>
复制代码
通过将DTO数据注解转换成标签的验证属性,可以让 Web Api 和 UI 的验证同步.
自定义验证

数据注解可以解决一些常见的验证场景.
但业务上可能需要编写自定义代码以更灵活的方式验证.
Util应用框架定义了一个验证接口 Util.Validation.IValidation.
IValidation 接口定义了 Validate 方法,执行该方法返回验证结果集合.
  1. /// <summary>
  2. /// 验证操作
  3. /// </summary>
  4. public interface IValidation {
  5.     /// <summary>
  6.     /// 验证
  7.     /// </summary>
  8.     ValidationResultCollection Validate();
  9. }
复制代码
实体,值对象,DTO等对象类型实现了 IValidation 接口,意味着这些对象可以通过标准的 Validate 方法进行验证.
  1. var entity = new TestEntity();
  2. entity.Validate();
复制代码
不论对象内部多么复杂,要验证它只需调用 Validate 方法即可.
验证逻辑被完全封装到对象内部.
DTO自定义验证

DTO参数对象 Validate 方法默认仅验证数据注解,如果有错误将抛出 Warning 异常.
Warning 异常代表业务错误,它的错误消息会返回给客户端.
Validate 是一个虚方法,可以进行重写.
  1.     public class TestDto : DtoBase {
  2.         [Required]
  3.         public string Name { get; set; }
  4.         public override ValidationResultCollection Validate() {
  5.             base.Validate();
  6.             if ( Name.Contains( "test" ) )
  7.                 throw new Warning( "名称不能包含test" );
  8.             return ValidationResultCollection.Success;
  9.         }
  10.     }
复制代码
TestDto 重写了 Validate 方法.
首先调用 base.Validate(); ,保证数据注解得到验证.
如果数据注解验证通过, 判断 Name 属性是否包含 test 字符串,如果包含则抛出 Warning 异常.
由于DTO参数仅用来传递数据,不应包含复杂的验证逻辑,通过重写 Validate 方法添加简单自定义验证逻辑应能满足需求.
另外, DTO参数验证失败,可直接抛出 Warning 异常,让全局异常处理器进行处理.
领域对象自定义验证

领域对象包含实体和值对象等.
对于较复杂的业务场景,与DTO不同的是,领域对象可用于业务处理,而不是传递数据.
需要为领域对象提供更多的验证支持.
领域对象有多种方式进行自定义验证.

  • 重写 Validate 方法
    领域对象最简单的自定义验证方式是重写 Validate 方法,并提供额外的验证逻辑.
    1.     public class TestEntity : AggregateRoot<TestEntity> {
    2.         public TestEntity() : this( Guid.Empty ) {
    3.         }
    4.         public TestEntity( Guid id ) : base( id ) {
    5.         }
    6.         [Required]
    7.         public string Name { get; set; }
    8.         public override ValidationResultCollection Validate() {
    9.             base.Validate();
    10.             if( Name.Contains( "test" ) )
    11.                 throw new Warning( "名称不能包含test" );
    12.             return ValidationResultCollection.Success;
    13.         }
    14.     }
    复制代码
    不过重写 Validate 验证方式也存在一些问题.

    • Validate 方法逐渐变得臃肿,代码稳定性在降低.
    • 代码的清晰度很低,重要的验证条件属于业务规则,却被一堆杂乱的 if else 判断淹没了.

  • 验证规则
    验证规则  Util.Validation.IValidationRule 代表一个验证条件,接口定义如下.
    1.   /// <summary>
    2.   /// 验证规则
    3.   /// </summary>
    4.   public interface IValidationRule {
    5.       /// <summary>
    6.       /// 验证
    7.       /// </summary>
    8.       ValidationResult Validate();
    9.   }
    复制代码
    可以为较复杂和重要的验证条件创建验证规则对象,把复杂的验证逻辑封装起来,并从领域对象中分离出来.

    • 创建验证规则对象
      约定: 验证规则对象需要取一个符合业务验证规则的名称, 并以 ValidationRule 结尾,文件放到 ValidationRules 目录中.
      ValidationRule 结尾可能导致名称过长.
      这里演示就随便起一个 SampleValidationRule.
      验证规则依赖一些对象才能进行验证,如何才能获取依赖?
      通过验证规则对象的构造方法传入需要的依赖对象.
      验证规则不通过Ioc容器管理,在需要的地方通过 new 创建验证规则实例.
      SampleValidationRule 示例构造方法只接收一个参数,但可以根据需要接收更多依赖项.
      实现验证规则的 Validate 方法.
      如果验证成功返回 ValidationResult.Success.
      如果验证失败返回验证结果对象 ValidationResult, 并设置验证失败消息.
      1. public class SampleValidationRule : IValidationRule {
      2.     private readonly TestEntity _entity;
      3.     public SampleValidationRule( TestEntity entity ) {
      4.         _entity = entity;
      5.     }
      6.     public ValidationResult Validate() {
      7.         if( _entity.Name.Contains( "test" ) )
      8.             return new ValidationResult( "名称不能包含test" );
      9.         return ValidationResult.Success;
      10.     }
      11. }
      复制代码
    • 将验证规则添加到领域对象
      领域对象基类定义了 AddValidationRule 方法,用于添加验证规则对象.
      从领域对象外部调用 AddValidationRule 传入验证规则.
      1.     var entity = new TestEntity();
      2.     entity.AddValidationRule( new SampleValidationRule( entity ) );
      复制代码
      可以通过工厂方法封装验证规则.
      1. public class TestEntity : AggregateRoot<TestEntity> {
      2.     public TestEntity() : this( Guid.Empty ) {
      3.     }
      4.     public TestEntity( Guid id ) : base( id ) {
      5.     }
      6.     [Required]
      7.     public string Name { get; set; }
      8.     public static TestEntity Create() {
      9.         var entity = new TestEntity();
      10.         entity.AddValidationRule( new SampleValidationRule( entity ) );
      11.         return entity;
      12.     }
      13. }
      14. var entity = TestEntity.Create();
      15. entity.Validate();
      复制代码
      对于比较固定且只依赖领域对象本身的验证规则,可以在构造方法添加.
      1. public class TestEntity : AggregateRoot<TestEntity> {
      2.     public TestEntity() : this( Guid.Empty ) {
      3.     }
      4.     public TestEntity( Guid id ) : base( id ) {
      5.         AddValidationRule( new SampleValidationRule( this ) );
      6.     }
      7.     [Required]
      8.     public string Name { get; set; }
      9. }
      复制代码
    • 设置验证处理器
      验证规则仅返回验证结果,验证失败如何处理由验证处理器决定.
      1. /// <summary>
      2. /// 验证处理器
      3. /// </summary>
      4. public interface IValidationHandler {
      5.     /// <summary>
      6.     /// 处理验证错误
      7.     /// </summary>
      8.     /// <param name="results">验证结果集合</param>
      9.     void Handle( ValidationResultCollection results );
      10. }
      复制代码
      领域对象默认的验证处理器在验证失败时抛出 Warning 异常.
      你可以设置自己的验证处理器来替换默认的.
      下面定义的 NothingHandler 在验证失败时什么也不做.
      1. /// <summary>
      2. /// 验证失败,不做任何处理
      3. /// </summary>
      4. public class NothingHandler : IValidationHandler {
      5.     /// <summary>
      6.     /// 处理验证错误
      7.     /// </summary>
      8.     /// <param name="results">验证结果集合</param>
      9.     public void Handle( ValidationResultCollection results ) {
      10.     }
      11. }
      复制代码
      调用 SetValidationHandler 方法设置验证处理器.
      1. var entity = new TestEntity();
      2. entity.AddValidationRule( new SampleValidationRule( entity ) );
      3. entity.SetValidationHandler( new NothingHandler() );
      复制代码

验证拦截器

Util应用框架定义了几个用于验证的参数拦截器.

  • NotNullAttribute

    • 验证是否为 null,如果为 null 抛出 ArgumentNullException 异常.
    • 使用范例:
    1.   public interface ITestService : ISingletonDependency {
    2.       void Test( [NotNull] string value );
    3.   }
    复制代码
  • NotEmptyAttribute

    • 使用 string.IsNullOrWhiteSpace 验证是否为空字符串,如果为空则抛出 ArgumentNullException 异常.
    • 使用范例:
    1.   public interface ITestService : ISingletonDependency {
    2.       void Test( [NotEmpty] string value );
    3.   }
    复制代码
  • ValidAttribute

    • 如果对象实现了 IValidation 验证接口,则自动调用对象的 Validate 方法进行验证.
    • 使用范例:
      验证单个对象.
    1.   public interface ITestService : ISingletonDependency {
    2.       void Test( [Valid] CustomerDto dto );
    3.   }
    复制代码
    验证对象集合.
    1.   public interface ITestService : ISingletonDependency {
    2.       void Test( [Valid] List<CustomerDto> dto );
    3.   }
    复制代码
源码解析

DataAnnotationValidation 数据注解验证操作

可以调用 DataAnnotationValidationValidate 方法验证数据注解.
  1. /// <summary>
  2. /// 数据注解验证操作
  3. /// </summary>
  4. public static class DataAnnotationValidation {
  5.     /// <summary>
  6.     /// 验证
  7.     /// </summary>
  8.     /// <param name="target">验证目标</param>
  9.     public static ValidationResultCollection Validate( object target ) {
  10.         if( target == null )
  11.             throw new ArgumentNullException( nameof( target ) );
  12.         var result = new ValidationResultCollection();
  13.         var validationResults = new List<ValidationResult>();
  14.         var context = new ValidationContext( target, null, null );
  15.         var isValid = Validator.TryValidateObject( target, context, validationResults, true );
  16.         if ( !isValid )
  17.             result.AddList( validationResults );
  18.         return result;
  19.     }
  20. }
复制代码
ValidationResultCollection 验证结果集合

ValidationResultCollection 用于收集验证结果消息.
  1. /// <summary>
  2. /// 验证结果集合
  3. /// </summary>
  4. public class ValidationResultCollection : List<ValidationResult> {
  5.     /// <summary>
  6.     /// 初始化验证结果集合
  7.     /// </summary>
  8.     public ValidationResultCollection() : this( "" ) {
  9.     }
  10.     /// <summary>
  11.     /// 初始化验证结果集合
  12.     /// </summary>
  13.     /// <param name="result">验证结果</param>
  14.     public ValidationResultCollection( string result ) {
  15.         if( string.IsNullOrWhiteSpace( result ) )
  16.             return;
  17.         Add( new ValidationResult( result ) );
  18.     }
  19.     /// <summary>
  20.     /// 成功验证结果集合
  21.     /// </summary>
  22.     public static readonly ValidationResultCollection Success = new();
  23.     /// <summary>
  24.     /// 是否有效
  25.     /// </summary>
  26.     public bool IsValid => Count == 0;
  27.     /// <summary>
  28.     /// 添加验证结果集合
  29.     /// </summary>
  30.     /// <param name="results">验证结果集合</param>
  31.     public void AddList( IEnumerable<ValidationResult> results ) {
  32.         if( results == null )
  33.             return;
  34.         foreach( var result in results )
  35.             Add( result );
  36.     }
  37.     /// <summary>
  38.     /// 输出验证消息
  39.     /// </summary>
  40.     public override string ToString() {
  41.         if( IsValid )
  42.             return string.Empty;
  43.         return this.First().ErrorMessage;
  44.     }
  45. }
复制代码
ThrowHandler 验证处理器

ThrowHandler 是默认的验证处理器,在验证失败时抛出 Warning 异常.
  1. /// <summary>
  2. /// 验证失败,抛出异常
  3. /// </summary>
  4. public class ThrowHandler : IValidationHandler{
  5.     /// <summary>
  6.     /// 处理验证错误
  7.     /// </summary>
  8.     /// <param name="results">验证结果集合</param>
  9.     public void Handle( ValidationResultCollection results ) {
  10.         if ( results.IsValid )
  11.             return;
  12.         throw new Warning( results.First().ErrorMessage );
  13.     }
  14. }
复制代码
ValidAttribute 验证拦截器

ValidAttribute 是一个 Aop 参数拦截器,可以对实现了 IValidation 接口的单个对象或对象集合进行验证.
  1. /// <summary>
  2. /// 验证拦截器
  3. /// </summary>
  4. public class ValidAttribute : ParameterInterceptorBase {
  5.     /// <summary>
  6.     /// 执行
  7.     /// </summary>
  8.     public override async Task Invoke( ParameterAspectContext context, ParameterAspectDelegate next ) {
  9.         Validate( context.Parameter );
  10.         await next( context );
  11.     }
  12.     /// <summary>
  13.     /// 验证
  14.     /// </summary>
  15.     private void Validate( Parameter parameter ) {
  16.         if ( Reflection.IsGenericCollection( parameter.RawType ) ) {
  17.             ValidateCollection( parameter );
  18.             return;
  19.         }
  20.         IValidation validation = parameter.Value as IValidation;
  21.         validation?.Validate();
  22.     }
  23.     /// <summary>
  24.     /// 验证集合
  25.     /// </summary>
  26.     private void ValidateCollection( Parameter parameter ) {
  27.         if ( !( parameter.Value is IEnumerable<IValidation> validations ) )
  28.             return;
  29.         foreach ( var validation in validations )
  30.             validation.Validate();
  31.     }
  32. }
复制代码
欢迎转载何镇汐的技术博客微信扫描二维码支持Util
1.jpeg

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册