本节介绍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();
}
}

Util应用框架基础(四) - 验证的更多相关文章

  1. Util应用程序框架公共操作类(四):验证公共操作类

    为了能够验证领域实体,需要一个验证公共操作类来提供支持.由于我将使用企业库(Enterprise Library)的验证组件来完成这项任务,所以本文也将演示对第三方框架的封装要点. .Net提供了一个 ...

  2. Hadoop 框架基础(四)

    ** Hadoop 框架基础(四) 上一节虽然大概了解了一下 mapreduce,徒手抓了海胆,不对,徒手写了 mapreduce 代码,也运行了出来.但是没有做更深入的理解和探讨. 那么…… 本节目 ...

  3. HBase框架基础(四)

    * HBase框架基础(四) 上一节我们介绍了如何使用HBase搞一些MapReduce小程序,其主要作用呢是可以做一些数据清洗和分析或者导入数据的工作,这一节我们来介绍如何使用HBase与其他框架进 ...

  4. Hibernatel框架基础使用

    Hibernatel框架基础使用 1.简介 1.1.Hibernate框架由来 Struts:基于MVC模式的应用层框架技术 Hibernate:基于持久层的框架(数据访问层使用)! Spring:创 ...

  5. 【原】Shiro框架基础搭建[2]

    简介: 关于搭建一个最基础的shiro网上的例子有很多,这里是记录一下自己尝试去看官方文档所搭建的一个小demo,项目采用的是原始的java静态工程,导入相关jar包后就能运行. 首先进入官网http ...

  6. Hibernate框架基础

    Hibernate框架基础 Hibernate框架 ORM概念 O, Object 对象 R, Realtion 关系 (关系型数据库: MySQL, Oracle…) M,Mapping 映射 OR ...

  7. Spring学习指南-第二章-Spring框架基础(完)

    第二章 Spring框架基础 面向接口编程的设计方法 ​ 在上一章中,我们看到了一个依赖于其他类的POJO类包含了对其依赖项的具体类的引用.例如,FixedDepositController 类包含 ...

  8. .NET面试题系列[1] - .NET框架基础知识(1)

    很明显,CLS是CTS的一个子集,而且是最小的子集. - 张子阳 .NET框架基础知识(1) 参考资料: http://www.tracefact.net/CLR-and-Framework/DotN ...

  9. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  10. JavaScript框架设计(四) 字符串选择器(选择器模块结束)

    JavaScript框架设计(四) 字符串选择器(选择器模块结束) 经过前面JavaScript框架设计(三) push兼容性和选择器上下文的铺垫,实现了在某一元素下寻找,现在终于进入了字符串选择器 ...

随机推荐

  1. Linux中的进程页表

    是什么 进程页表是用于管理进程虚拟地址空间和物理内存之间映射关系的数据结构.它记录了进程中每个虚拟页对应的物理页的信息. 什么作用 进程使用进程页表的方式是通过虚拟地址访问内存.当进程访问一个虚拟地址 ...

  2. 探索Java通信面试的奥秘:揭秘IO模型、选择器和网络协议,了解面试中的必备知识点!

    了解常见的TCP/UDP TCP(Transmission Control Protocol)是一种面向连接的可靠的传输协议.类似于打电话,它通过建立一个连接和保证数据的可靠传输来提高通信的可靠性.然 ...

  3. JVM调优篇:探索Java性能优化的必备种子面试题

    JVM内存模型 首先面试官会询问你在进行JVM调优之前,是否了解JVM内存模型的基础知识.这是一个重要的入门问题.JVM内存模型主要包括程序计数器.堆.本地方法栈.Java栈和方法区(1.7之后更改为 ...

  4. 关于package-lock.json

    前言 上篇文章我们了解了package.json,一般与它同时出现的还有一个package-lock.json,这两者又有什么关系呢?下面一起来了解吧. 介绍 package-lock.json 它会 ...

  5. 【腾讯云 Cloud Studio 实战训练营】在线 IDE 编写 canvas 转换黑白风格头像

    关于 Cloud Studio Cloud Studio 是基于浏览器的集成式开发环境(IDE),为开发者提供了一个永不间断的云端工作站.用户在使用Cloud Studio 时无需安装,随时随地打开浏 ...

  6. CTC蜀道会:第一次圆桌会圆满结束

    近期,成都.NET俱乐部核心成员经过讨论会,我们成立了CTC蜀道会,它是一个专注于创业历程.研发管理.AIGC.副业之路..NET.Vue.微软技术.开源技术等领域的社区,立足于蓉城成都,致力于连接同 ...

  7. Python条件控制和循环语句(if while for )

    Python条件控制和循环语句(if while for ) 条件控制 概念:Python 条件语句是通过一条或多条语句的执行结果(True 或者 False)来决定执行的代码块 结构 1. 顺序结构 ...

  8. 千万级数据的表,我把慢sql优化后性能提升30倍!

    分享技术,用心生活 背景:系统中有一个统计页面加载特别慢,前端设置的40s超时时间都加载不出来数据,因为是个统计页面,基本上一猜就知道是mysql的语句有问题,遗留了很久没有解决,正好趁不忙的时候,下 ...

  9. 深入探究API接口

    作为程序员,我们经常会遇到需要获取外部数据或调用外部服务的情况.而API(Application Programming Interface,应用程序编程接口)接口就是这样的一种机制,它允许我们的应用 ...

  10. 使用 OpenTelemetry 构建 .NET 应用可观测性(2):OpenTelemetry 项目简介

    前世今生 OpenTracing OpenTracing 项目启动于 2016 年,旨在提供一套分布式追踪标准,以便开发人员可以更轻松地实现分布式追踪. OpenTracing 定义了一套 Traci ...