写点什么

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

作者:何镇汐
  • 2023-11-11
    四川
  • 本文字数:7405 字

    阅读完需:约 24 分钟

验证


Util 应用框架对象验证方法


本节介绍 Util 应用框架如何进行验证.


概述

验证是业务健壮性的基础.


.Net 提供了一套称为 DataAnnotations 数据注解的方法,可以对属性进行一些基本验证,比如必填项验证,长度验证等.


Util 应用框架使用标准的数据注解作为基础验证,并对自定义验证进行扩展.

基础用法

引用 Nuget 包

Nuget 包名: Util.Validation.


通常不需要手工引用它.

数据注解

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

常用数据注解

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


  • RequiredAttribute 必填项验证

  • [Required] 验证属性不能是空值.

  • 范例:


  • public class Test { [Required] public string Name { get; set; } }


  • [Required] 支持一些参数,可以设置验证失败的提示消息.


public class Test {

[Required(ErrorMessage = "名称不能为空")]

public string Name { get; set; }

}


  • StringLengthAttribute 字符串长度验证

  • [StringLength] 可以对字符串长度进行验证.

  • 下面的例子验证 Name 属性的字符串最大长度为 5.


public class Test {

[StringLength(5)]

public string Name { get; set; }

}


  • 还可以同时设置最小长度.

  • 下面验证 Name 属性字符串最小长度为 1,最大长度为 5.


public class Test {

[StringLength(5,MinimumLength = 1)]

public string Name { get; set; }

}


  • MaxLengthAttribute 字符串最大长度验证

  • [MaxLength] 也可以用来验证字符串最大长度.

  • 验证 Name 属性的字符串最大长度为 5.


public class Test {

[MaxLength(5)]

public string Name { get; set; }

}


  • MinLengthAttribute 字符串最小长度验证

  • [MinLength] 也可以用来验证字符串最小长度.

  • 验证 Name 属性的字符串最小长度为 1.


public class Test {

[MinLength(1)]

public string Name { get; set; }

}


  • RangeAttribute 数值范围验证

  • [Range] 用于验证数值范围.

  • 下面验证 Money 属性的值必须在 1 到 5 之间的范围.


public class Test {

[Range( 1, 5 )]

public int Money { get; set; }

}


  • EmailAddressAttribute 电子邮件验证

  • [EmailAddress] 用于验证电子邮件的格式.


public class Test {

[EmailAddress]

public int Email { get; set; }

}


  • PhoneAttribute 手机号验证

  • [Phone] 用于验证手机号的格式.


public class Test {

[Phone]

public int Tel { get; set; }

}


  • IdCardAttribute 身份证验证

  • [IdCard] 用于验证身份证的格式.

  • 它是一个 Util 应用框架自定义的数据注解.


public class Test {

[IdCard]

public int IdCard { get; set; }

}


  • UrlAttribute Url 验证

  • [Url] 用于验证网址格式.


public class Test {

[Url]

public int Url { get; set; }

}


  • RegularExpressionAttribute 正则表达式验证

  • [RegularExpression] 可以使用正则表达式进行验证.

  • 由于正则表达式比较复杂,对于经常使用的场景,应封装成自定义数据注解.

  • 下面使用正则表达式验证身份证,可以封装到 [IdCard] 数据注解,从而避免正则表达式的复杂性.


public class Test {

[RegularExpression( @"(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)" )]

public string IdCard { get; set; }

}

验证数据注解

虽然在对象属性上添加了数据注解,但它们并不会自动触发验证.


你可以使用 Asp.Net Core 提供的方法验证对象上的数据注解.


Util 提供了一个辅助方法 Util.Validation.DataAnnotationValidation.Validate 用来验证数据注解.


DataAnnotationValidation.Validate 方法接收一个对象参数,只需将要验证的对象实例传入即可.


返回类型为验证结果集合,包含所有验证失败的消息.


    public class Test {        [Required]        public string Name { get; set; }
public ValidationResultCollection Validate() { return DataAnnotationValidation.Validate( this ); } }
复制代码


大部分情况下,你并不需要调用 DataAnnotationValidation.Validate 方法验证数据注解.


实体,值对象,DTO 等对象已经内置了 Validate 方法,它们会自动验证数据注解.

Util Angular UI 数据注解验证支持

Util Angular UI 支持 Razor TagHelper 服务端标签语法.


可以在表单组件使用 Lambda 表达式绑定 DTO 对象属性.


TestDto 参数对象 Name 属性使用 [Required] 设置必填项验证.


    public class TestDto : DtoBase {        [Required]        [Display(Name = "name")]        public string Name { get; set; }    }
复制代码


Razor 页面声明 TestDto 模型, 定义输入框 util-input,使用 for 属性绑定到 TestDto 参数对象的 Name 属性.


@page@model TestDto
<util-form> <util-input id="input_Name" for="Name" /></util-form>
复制代码


Razor 页面最终会生成 html,表单标签 nz-form-label 添加了 nzRequired 必填项属性, 输入框 input 添加了 required 必填项属性.


<form nz-form>    <nz-form-item>        <nz-form-label [nzRequired]="true">name</nz-form-label>        <nz-form-control [nzErrorTip]="vt_input_Name">            <input #input_Name="" #v_input_Name="xValidationExtend" name="name" nz-input="" x-validation-extend="" [(ngModel)]="model.name" [required]="true" />            <ng-template #vt_input_Name="">{{v_input_Name.getErrorMessage()}}</ng-template>        </nz-form-control>    </nz-form-item></form>
复制代码


通过将 DTO 数据注解转换成标签的验证属性,可以让 Web Api 和 UI 的验证同步.

自定义验证

数据注解可以解决一些常见的验证场景.


但业务上可能需要编写自定义代码以更灵活的方式验证.


Util 应用框架定义了一个验证接口 Util.Validation.IValidation.


IValidation 接口定义了 Validate 方法,执行该方法返回验证结果集合.


/// <summary>/// 验证操作/// </summary>public interface IValidation {    /// <summary>    /// 验证    /// </summary>    ValidationResultCollection Validate();}
复制代码


实体,值对象,DTO 等对象类型实现了 IValidation 接口,意味着这些对象可以通过标准的 Validate 方法进行验证.


var entity = new TestEntity();entity.Validate();
复制代码


不论对象内部多么复杂,要验证它只需调用 Validate 方法即可.


验证逻辑被完全封装到对象内部.

DTO 自定义验证

DTO 参数对象 Validate 方法默认仅验证数据注解,如果有错误将抛出 Warning 异常.


Warning 异常代表业务错误,它的错误消息会返回给客户端.


Validate 是一个虚方法,可以进行重写.


    public class TestDto : DtoBase {        [Required]        public string Name { get; set; }
public override ValidationResultCollection Validate() { base.Validate(); if ( Name.Contains( "test" ) ) throw new Warning( "名称不能包含test" ); return ValidationResultCollection.Success; } }
复制代码


TestDto 重写了 Validate 方法.


首先调用 base.Validate(); ,保证数据注解得到验证.


如果数据注解验证通过, 判断 Name 属性是否包含 test 字符串,如果包含则抛出 Warning 异常.


由于 DTO 参数仅用来传递数据,不应包含复杂的验证逻辑,通过重写 Validate 方法添加简单自定义验证逻辑应能满足需求.


另外, DTO 参数验证失败,可直接抛出 Warning 异常,让全局异常处理器进行处理.

领域对象自定义验证

领域对象包含实体和值对象等.


对于较复杂的业务场景,与 DTO 不同的是,领域对象可用于业务处理,而不是传递数据.


需要为领域对象提供更多的验证支持.


领域对象有多种方式进行自定义验证.


  • 重写 Validate 方法

  • 领域对象最简单的自定义验证方式是重写 Validate 方法,并提供额外的验证逻辑.


   public class TestEntity : AggregateRoot<TestEntity> {        public TestEntity() : this( Guid.Empty ) {        }        public TestEntity( Guid id ) : base( id ) {        }
[Required] public string Name { get; set; }
public override ValidationResultCollection Validate() { base.Validate(); if( Name.Contains( "test" ) ) throw new Warning( "名称不能包含test" ); return ValidationResultCollection.Success; } }
复制代码


  • 不过重写 Validate 验证方式也存在一些问题.

  • Validate 方法逐渐变得臃肿,代码稳定性在降低.

  • 代码的清晰度很低,重要的验证条件属于业务规则,却被一堆杂乱的 if else 判断淹没了.

  • 验证规则

  • 验证规则 Util.Validation.IValidationRule 代表一个验证条件,接口定义如下.


/// <summary>

/// 验证规则

/// </summary>

public interface IValidationRule {

/// <summary>

/// 验证

/// </summary>

ValidationResult Validate();

}


  • 可以为较复杂和重要的验证条件创建验证规则对象,把复杂的验证逻辑封装起来,并从领域对象中分离出来.

  • 创建验证规则对象

  • 约定: 验证规则对象需要取一个符合业务验证规则的名称, 并以 ValidationRule 结尾,文件放到 ValidationRules 目录中.

    ValidationRule 结尾可能导致名称过长.

  • 这里演示就随便起一个 SampleValidationRule.

  • 验证规则依赖一些对象才能进行验证,如何才能获取依赖?

  • 通过验证规则对象的构造方法传入需要的依赖对象.

  • 验证规则不通过 Ioc 容器管理,在需要的地方通过 new 创建验证规则实例.

  • SampleValidationRule 示例构造方法只接收一个参数,但可以根据需要接收更多依赖项.

  • 实现验证规则的 Validate 方法.

  • 如果验证成功返回 ValidationResult.Success.

  • 如果验证失败返回验证结果对象 ValidationResult, 并设置验证失败消息.


public class SampleValidationRule : IValidationRule {    private readonly TestEntity _entity;
public SampleValidationRule( TestEntity entity ) { _entity = entity; }
public ValidationResult Validate() { if( _entity.Name.Contains( "test" ) ) return new ValidationResult( "名称不能包含test" ); return ValidationResult.Success; }}
复制代码


  • 将验证规则添加到领域对象

  • 领域对象基类定义了 AddValidationRule 方法,用于添加验证规则对象.

  • 从领域对象外部调用 AddValidationRule 传入验证规则.


var entity = new TestEntity();

entity.AddValidationRule( new SampleValidationRule( entity ) );


  • 可以通过工厂方法封装验证规则.


public class TestEntity : AggregateRoot<TestEntity> {    public TestEntity() : this( Guid.Empty ) {    }    public TestEntity( Guid id ) : base( id ) {    }
[Required] public string Name { get; set; }
public static TestEntity Create() { var entity = new TestEntity(); entity.AddValidationRule( new SampleValidationRule( entity ) ); return entity; }}
var entity = TestEntity.Create();entity.Validate();
复制代码


  • 对于比较固定且只依赖领域对象本身的验证规则,可以在构造方法添加.


public class TestEntity : AggregateRoot<TestEntity> {    public TestEntity() : this( Guid.Empty ) {    }
public TestEntity( Guid id ) : base( id ) { AddValidationRule( new SampleValidationRule( this ) ); }
[Required] public string Name { get; set; }}
复制代码


  • 设置验证处理器

  • 验证规则仅返回验证结果,验证失败如何处理由验证处理器决定.


/// <summary>/// 验证处理器/// </summary>public interface IValidationHandler {    /// <summary>    /// 处理验证错误    /// </summary>    /// <param name="results">验证结果集合</param>    void Handle( ValidationResultCollection results );}
复制代码


  • 领域对象默认的验证处理器在验证失败时抛出 Warning 异常.

  • 你可以设置自己的验证处理器来替换默认的.

  • 下面定义的 NothingHandler 在验证失败时什么也不做.


/// <summary>/// 验证失败,不做任何处理/// </summary>public class NothingHandler : IValidationHandler {    /// <summary>    /// 处理验证错误    /// </summary>    /// <param name="results">验证结果集合</param>    public void Handle( ValidationResultCollection results ) {    }}
复制代码


  • 调用 SetValidationHandler 方法设置验证处理器.


var entity = new TestEntity();

entity.AddValidationRule( new SampleValidationRule( entity ) );

entity.SetValidationHandler( new NothingHandler() );

验证拦截器

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


  • NotNullAttribute

  • 验证是否为 null,如果为 null 抛出 ArgumentNullException 异常.

  • 使用范例:


public interface ITestService : ISingletonDependency {

void Test( [NotNull] string value );

}


  • NotEmptyAttribute

  • 使用 string.IsNullOrWhiteSpace 验证是否为空字符串,如果为空则抛出 ArgumentNullException 异常.

  • 使用范例:


public interface ITestService : ISingletonDependency {

void Test( [NotEmpty] string value );

}


  • ValidAttribute

  • 如果对象实现了 IValidation 验证接口,则自动调用对象的 Validate 方法进行验证.

  • 使用范例:

  • 验证单个对象.


public interface ITestService : ISingletonDependency {

void Test( [Valid] CustomerDto dto );

}


  • 验证对象集合.


public interface ITestService : ISingletonDependency {

void Test( [Valid] List<CustomerDto> dto );

}


源码解析

DataAnnotationValidation 数据注解验证操作

可以调用 DataAnnotationValidationValidate 方法验证数据注解.


/// <summary>/// 数据注解验证操作/// </summary>public static class DataAnnotationValidation {    /// <summary>    /// 验证    /// </summary>    /// <param name="target">验证目标</param>    public static ValidationResultCollection Validate( object target ) {        if( target == null )            throw new ArgumentNullException( nameof( target ) );        var result = new ValidationResultCollection();        var validationResults = new List<ValidationResult>();        var context = new ValidationContext( target, null, null );        var isValid = Validator.TryValidateObject( target, context, validationResults, true );        if ( !isValid )            result.AddList( validationResults );        return result;    }}
复制代码

ValidationResultCollection 验证结果集合

ValidationResultCollection 用于收集验证结果消息.


/// <summary>/// 验证结果集合/// </summary>public class ValidationResultCollection : List<ValidationResult> {
/// <summary> /// 初始化验证结果集合 /// </summary> public ValidationResultCollection() : this( "" ) { }
/// <summary> /// 初始化验证结果集合 /// </summary> /// <param name="result">验证结果</param> public ValidationResultCollection( string result ) { if( string.IsNullOrWhiteSpace( result ) ) return; Add( new ValidationResult( result ) ); }
/// <summary> /// 成功验证结果集合 /// </summary> public static readonly ValidationResultCollection Success = new();
/// <summary> /// 是否有效 /// </summary> public bool IsValid => Count == 0;
/// <summary> /// 添加验证结果集合 /// </summary> /// <param name="results">验证结果集合</param> public void AddList( IEnumerable<ValidationResult> results ) { if( results == null ) return; foreach( var result in results ) Add( result ); }
/// <summary> /// 输出验证消息 /// </summary> public override string ToString() { if( IsValid ) return string.Empty; return this.First().ErrorMessage; }}
复制代码

ThrowHandler 验证处理器

ThrowHandler 是默认的验证处理器,在验证失败时抛出 Warning 异常.


/// <summary>/// 验证失败,抛出异常/// </summary>public class ThrowHandler : IValidationHandler{    /// <summary>    /// 处理验证错误    /// </summary>    /// <param name="results">验证结果集合</param>    public void Handle( ValidationResultCollection results ) {        if ( results.IsValid )            return;        throw new Warning( results.First().ErrorMessage );    }}
复制代码

ValidAttribute 验证拦截器

ValidAttribute 是一个 Aop 参数拦截器,可以对实现了 IValidation 接口的单个对象或对象集合进行验证.


/// <summary>/// 验证拦截器/// </summary>public class ValidAttribute : ParameterInterceptorBase {    /// <summary>    /// 执行    /// </summary>    public override async Task Invoke( ParameterAspectContext context, ParameterAspectDelegate next ) {        Validate( context.Parameter );        await next( context );    }
/// <summary> /// 验证 /// </summary> private void Validate( Parameter parameter ) { if ( Reflection.IsGenericCollection( parameter.RawType ) ) { ValidateCollection( parameter ); return; } IValidation validation = parameter.Value as IValidation; validation?.Validate(); }
/// <summary> /// 验证集合 /// </summary> private void ValidateCollection( Parameter parameter ) { if ( !( parameter.Value is IEnumerable<IValidation> validations ) ) return; foreach ( var validation in validations ) validation.Validate(); }}
复制代码


用户头像

何镇汐

关注

15年以上.Net开发经验,擅长代码封装 2023-11-01 加入

15年以上.Net开发经验,擅长代码封装,主要作品为Util应用框架

评论

发布
暂无评论
Util应用框架基础(四)- 验证_开源_何镇汐_InfoQ写作社区