本节介绍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. Unity UGUI的LayoutElement(布局元素)组件的介绍及使用

    Unity UGUI的LayoutElement(布局元素)组件的介绍及使用 1. 什么是LayoutElement组件? LayoutElement是Unity UGUI中的一个布局元素组件,用于控 ...

  2. 一:wince 开发环境

    1:下载相关文件,vs2008 可以自行搜索安装 链接:https://pan.baidu.com/s/1b2shwCqmc1o9x-zsy8CmeA 提取码:qing

  3. grafana 容器无法启动,打印权限问题

    报错日志 open /var/lib/grafana/alerting/1/notifications: permission denied 问题原因 sudo chown -R docker: /v ...

  4. netstat 某连接的 Recv-Q(接收队列)达到500多万字节的内核参数排查

    思路: cat proc文件系统下的 sys/net 目录下所有文件,根据结果降序排序(如果打印前xx,可能会漏掉关键信息,在定位问题时需要注意,慎用过滤),根据结果使用 grep -rn xxx 找 ...

  5. Typescript:基础语法学习(尚硅谷 李立超)

    官方文档:https://www.tslang.cn/docs/handbook/typescript-in-5-minutes.html 搭建开发环境 npm i -g typescript安装完成 ...

  6. 渗透-02:HTTPS主干-分支和HTTPS传输过程

    一.HTTPS主干-分支 第一层 第一层,是主干的主干,加密通信就是双方都持有一个对称加密的秘钥,然后就可以安全通信了. 问题就是,无论这个最初的秘钥是由客户端传给服务端,还是服务端传给客户端,都是明 ...

  7. 基于C#的窗体阴影效果方案 - 开源研究系列文章

    最近在研究C#的Winform窗体的效果,上次介绍了窗体动画效果的博文( 基于C#的无边框窗体动画效果的完美解决方案 - 开源研究系列文章 ),这次将窗体阴影效果的方案进行一个介绍. 找了一下度娘,具 ...

  8. 微信的 h5 支付和 jsapi 支付

    目录 申请商户号 申请商户证书 设置APIv3密钥 下载 SDK 开发包 下载平台证书 关联 AppID 账号 开通 H5 支付 H5支付流程 开通 JSAPI 支付 JSAPI 支付流程 通用微信支 ...

  9. ChatGPT接入Siri(保姆级教程)

    今天,我将为大家分享如何将ChatGPT应用集成到苹果手机的Siri中 (当然手机是需要魔法(TZ)的) 第一步:获取OpenAPI的Key 提取API网址:https://platform.open ...

  10. css面试题一

    1.继承 css的继承:就是给父级设置一些属性,子级继承了父级的该属性,这就是我们css中的继承.官方的解释,继承是一种规则,它允许样式不仅应用于特定的html标签元素,而且应用于其后代元素. a.有 ...