上一篇文章《MediatR在.NET应用中的实践》中,我们在讲MediatR的管线内容时,提到过可以在管线中增加 Command/Query 的验证。今天我来带领大家了解一个.NET技术领域中很「流行」的强类型验证规则构建库:FluentValidation

FluentValidation 简介

这么多年的开发工作中,我一直很喜欢「Fluent」编程风格,所以对Fluent开头或风格上比较Fluent的各种类库工具也都蛮喜欢。比如.NET领域的:FluentAssertionsFluentMigratorFluentFTPFluentSchedulerFluentEmail以及Flurl等等。以后我会另起几篇文章介绍一下他们。

「FluentValidation」 是一个面向 .NET 应用的强类型验证规则构建库,且使用 Apache-2.0 协议开源在 https://github.com/FluentValidation/FluentValidation 。官方网站是: https://fluentvalidation.net 。

官网直接在首屏以源代码方式来展现他:直观、简洁、很 Fluent的显著特:

public class CustomerValidator : AbstractValidator<Customer> {public CustomerValidator() {RuleFor(x => x.Surname).NotEmpty();RuleFor(x => x.Forename).NotEmpty().WithMessage("Please specify a first name");RuleFor(x => x.Discount).NotEqual(0).When(x => x.HasDiscount);RuleFor(x => x.Address).Length(20, 250);RuleFor(x => x.Postcode).Must(BeAValidPostcode).WithMessage("Please specify a valid postcode");}private bool BeAValidPostcode(string postcode) {// custom postcode validating logic goes here}
}

简单理解一下其中的几个关键要素:

  1. 这是一个针对Customer类型对象的验证规则

  2. 规则验证器必须继承自 AbstractValidator<T>,其中的T就是你所希望在这个验证器中验证的实际数据类型;

  3. 规则验证器通过构造函数直接进行规则设定;

  4. 通常针对一个属性的验证规则我们直接以RuleFor(x => x.*** )作为代码开头进行流畅的规则验证;

  5. 内置规则方法已经非常丰富:NotEmptyNotEqualLength等,也可以使用 Must 进行自定义设置;

  6. 可以用When设定规则验证的前提;

  7. 默认常见内置规则,都有统一内置的验证不通过的消息;可通过WithMessage设置独立的验证不通过的消息;

FluentValidation 的相关包

「FluentValidation」 的验证规则设置能力非常强大,下图中是目前所有内置的规则验证:用之前,我们通常需要引用的几个包:

  • 「FluentValidation」 核心包,必须的

  • 「FluentValidation.DependencyInjectionExtensions」 当你需要在依赖注入的场景下用的时候,这是必须的

  • 「FluentValidation.AspNetCore」 当你需要在ASP.NET Core相关业务场景用的时候,最好也引用一下这个.

「注意」 FluentValidation.AspNetCore 中以及包含了对 FluentValidationFluentValidation.DependencyInjectionExtensions 的依赖。

ASP.NET Core 中启用FluentValidation的

默认情况下的ASP.NETASP.NET Core,Controler 中 Action 的参数会被自动绑定和验证通过 DataAnnotation 相关的 Attribute 约定的验证规则,但是 DataAnnotations 应对简单的验证还行,如果需要分不同场景或者有前提条件等的时候,他就明显力不从心了。「FluentValidation」 则可以针对我们各种需求进行验证,所以我建议大家在实际项目中多考虑使用之。

入门使用

public void ConfigureServices(IServiceCollection services)
{// 或者是 services.AddControllers(setup =>services.AddMvc(setup => {//...mvc setup...}).AddFluentValidation();
}

通过上面的代码启用 FluentValidation 后,MVC 将使用 FluentValidation 来验证 Controller 上 Action 中绑定的 Model 对象。

「注意:」 作为 .NET 6 一部分的 Minimal API 不支持自动验证

此时你可能会问,他怎么知道用哪个验证规则啊?嗯,上面这种简单启用时,验证规则也需要通过显性的代码进行验证规则注入:

services.AddTransient<IValidator<Customer>, CustomerValidator>();

可以想象,如果你有很多的 Model 类型和对应的验证规则设置,这样一个一个的注册,会心态崩溃,最终放弃的。

自动注册

我们可以根据需要通过下面两种方式来进行自动化的注册:

// 扫描并注册 Startup 类型所在程序集中的 Validator 验证器
services.AddValidatorsFromAssemblyContaining(typeof(Startup));
// 扫描并注册指定名称程序集中的所有 Validator 验证器
services.AddValidatorsFromAssembly(Assembly.Load("SomeAssembly"));

嗯,很好,这样我就可以随意增加新的 Validator ,而不必担心忘记注册了。

进阶设置

默认情况下,在执行 FluentValidation 之后,任何其他验证器提供程序也将有机会执行,这也就意味着您可以将 FluentValidationDataAnnotations 属性(或其他 ModelValidatorProvider)混合使用。

但我们可能并不想混乱的开启那么多验证,造成对同一个 Model 有多套验证,一旦发现不符合业务预期,要到处找验证是怎么回事儿。所以推荐大家只使用其一,比如在启用 FluentValidation 时禁用 DataAnnotations:

AddFluentValidation(fv => {// 禁用 MVC 默认的 DataAnnotations 验证fv.DisableDataAnnotationsValidation = true;
});

这样,我们的 ASP.NET Core 就会忽略默认的 DataAnnotations 验证。

隐式子属性验证

如果你详细阅读过 FluentValidation 的官方文档,你会了解到它带有子属性验证的场景。也就是一个 Model 的属性类型,是另外一个设置过验证规则的类。我们想让子属性也在父对象被验证时同时被验证,还懒得在验证规则中明文通过SetValidator设置子属性验证,怎么办?

services.AddMvc().AddFluentValidation(fv =>
{
// 递归检查所有子属性的验证规则fv.ImplicitlyValidateChildProperties = true;
});

虽然这样可以让你偷懒,但是我不建议这样做,因为验证器不只是 MVC 中需要的。我们在验证规则中应该明文设置子属性验证规则,这样也可针对不同的场景和业务要求让规则「显性」

对于数据集合,默认情况下,您必须创建特定的集合验证器或启用隐式子属性验证来验证属于集合类型的模型。例如,定义一个继承自 AbstractValidator<List<Customer>> 的验证器。启用隐式子属性验证(见上文)后,您不必显式创建集合验证器类,因为集合中的每个 customer 元素都将被自动验证。但 Customer 对象上的任何子属性也将自动验证!如果你不希望这样,可以选择仅对根集合元素启用隐式验证:

services.AddMvc().AddFluentValidation(fv =>
{fv.ImplicitlyValidateRootCollectionElements = true;
});

再次声明,我不建议在 MVC 中启用隐式的子属性验证,这给你的实际业务会带来不确定性和不必要的性能损害。

规则集 RuleSet

规则集允许您将验证规则组合在一起,这些规则可以作为一个组一起执行:

public class PersonValidator : AbstractValidator<Person> {public PersonValidator() {RuleSet("Names", () => {RuleFor(x => x.Surname).NotNull();RuleFor(x => x.Forename).NotNull();});RuleFor(x => x.Id).NotEqual(0);}
}

这样在手动验证等场景下,可以通过代码指定仅验证规则集中的规则,而忽略其他规则:

var validator = new PersonValidator();
var person = new Person();
var result = validator.Validate(person, options => options.IncludeRuleSets("Names"));

在 MediatR 的管线中对Request进行自动验证

正如开头我们说的,MediatR 可以通过管线对 Request 进行验证,这里我们也使用 FluentValidation 作为 MediatR 的默认验证。

首先定义验证管线:

public class RequestValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>where TRequest : IRequest<TResponse>
{private readonly IEnumerable<IValidator<TRequest>> _validators;public RequestValidationBehavior(IEnumerable<IValidator<TRequest>> validators){_validators = validators;}public Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next){var failures = _validators.Select(v => v.Validate(request)).SelectMany(result => result.Errors).Where(f => f != null).ToList();if (failures.Count != 0){throw new ValidationException(failures);}return next();}
}

然后注册管线:

services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestValidationBehavior<,>));

「注意」 建议验证管线加在其他自定义管线之前,使得每次通过 Mediator Send 一个 Request 时,都会优先执行验证,验证不通过就没后面管线什么事儿了。

最后根据需要在你自己的 ASP.NET Core 自定义异常处理管线中增加针对 ValidationException 的统一处理,下面是我针对 WebAPI 的样例代码参考:

public class CustomExceptionHandlerMiddleware
{private readonly RequestDelegate _next;private readonly ILogger<CustomExceptionHandlerMiddleware> _logger;public CustomExceptionHandlerMiddleware(RequestDelegate next, ILogger<CustomExceptionHandlerMiddleware> logger){_next = next;_logger = logger;}public async Task Invoke(HttpContext context){try{await _next(context);}catch (Exception ex){await HandleExceptionAsync(context, ex);}}private Task HandleExceptionAsync(HttpContext context, Exception exception){var code = HttpStatusCode.InternalServerError;var result = string.Empty;switch (exception){case ValidationException validationException:code = HttpStatusCode.BadRequest;result = JsonConvert.SerializeObject(new { code, message = validationException.Failures.First().Value.FirstOrDefault() ?? validationException.Message, details = validationException.Failures });break;// case 其他需要统一处理的异常}context.Response.ContentType = "application/json";context.Response.StatusCode = (int)code;if (string.IsNullOrEmpty(result)){_logger.LogError("发生服务器端异常,{@exception}", exception);result = JsonConvert.SerializeObject(new { code, message = exception.Message });}return context.Response.WriteAsync(result);}
}

结束语

FluentValidation 远比我在文中介绍的要强大的多,小小的一篇公众号不可能把它完全讲的面面俱到,建议你通过本文了解一些特性后,去读一下官方文档。如果还从未用过,建议你写几个demo尝试一下,「实践出真知」

验证规则构建神器 FluentValidation.md相关推荐

  1. docker 导入镜像_官方下一代Docker镜像构建神器 -- BuildKit

    BuildKit是Docker官方社区推出的下一代镜像构建神器--可以更加快速,有效,安全地构建docker 镜像.Docker v18.06已经集成了该组件.BuildKit可用于多种导出格式(例如 ...

  2. [原创].NET 业务框架开发实战之九 Mapping属性原理和验证规则的实现策略

    .NET 业务框架开发实战之九 Mapping属性原理和验证规则的实现策略 前言:之前的讨论一直关注在怎么从DAL中获取数据,以及数据的Mapping问题.实际上,一个业务框架最主要的作用就是简化业务 ...

  3. 第47章 表单验证之DataAnnotations与FluentValidation

    DataAnnotations DataAnnotations是.Net(Core)框架内置的表单验证中间件,当前它的版本基本与.Net(Core)框架一致,.Net6框架最后一次更新的日期是:202 ...

  4. bootstrapvalidator已定义的验证规则

    bootstrapvalidator已定义的验证规则 说明 查找bootstrapValidator的选项options 查找bootstrapValidator的已验证规则 常用规则 说明 这里使用 ...

  5. php字段验证规则,ThinkPHP 自动验证及验证规则详解

    ThinkPHP 自动验证及验证规则详解 ThinkPHP 自动验证 ThinkPHP 内置了数据对象的自动验证功能来完成模型的业务规则验证.自动验证是基于数据对象的,而大多情况下数据对象是基于 $_ ...

  6. python基于条件、规则构建已有字典的子集

    python基于条件.规则构建已有字典的子集 字典是另一种可变容器模型,且可存储任意类型对象. 字典的每个键值 key=>value 对用冒号 : 分割,每个对之间用逗号(,)分割,整个字典包括 ...

  7. iview 表单 验证_iview必备技能一、表单验证规则

    iView表单组件使用async-validator验证器对表单域中数据进行验证,给 Form 设置属性 rules,同时给需要验证的 FormItem 设置属性 prop 指向对应字段即可. 完整的 ...

  8. SpringBoot-Security-用户权限分配-配置验证规则

    Spring Security配置 Spring Security配置是通过 @EnableWebSecurity注释和WebSecurityConfigurerAdapter共同提供基于网络的安全性 ...

  9. ThinkPHP5表单令牌+表单数据验证验证规则

    转:http://blog.163.com/zhuxun_why/blog/static/26813905020171861417642/ 表单验证真的很简单 相比较yii的表单验证tp做的很人性 也 ...

最新文章

  1. python把文字矢量化_这个python函数可以被矢量化吗?
  2. 在Leaflet地图上集成Echarts
  3. 神策 2019 数据驱动大会「 PPT 下载」,零距离感受大会精华
  4. ASP.NET中TextBox控件的AutoCompleteType属性(不保存历史输入记录)
  5. 【项目调研+论文阅读】(目录)中文实体识别研究方法综述 day6
  6. Atitit mybatis返回多个数据集总结 目录 1.1. 配置handleResult接受,但是只有第一个select语句的结果 1 2. 配置resultMap ok 1 2.1. 调
  7. 基于imx6ul下调试tlv320aic3x声卡
  8. 下载verycd的方法下载电驴资源隐藏资源的最新可用方法
  9. 企业微信网页应用开发 - 消息/事件回调接口
  10. android外设键盘按键映射表
  11. 主板风扇转不开机是什么问题_cpu风扇转主板不启动怎么办
  12. 网站搭建:从零搭建个人网站教程(1)
  13. scrapy 中日志的使用
  14. 计算机里面有盐有糖O(∩_∩)O哈哈~
  15. 传奇服务器开区修改,怎么修改传奇登陆器开区时间提前?
  16. 移动端vue+vant+高德地图实现拖拽选址,周边选址,搜索选址,自动定位,选择城市功能,获取地址经纬度,详细地址
  17. 长安链源码学习--提案(Proposer)(五)
  18. E2类 MCR-WPT系统的搭建
  19. html塔防游戏,HTML5 版塔防游戏
  20. 视频教程-springboot+Vue整合前后端分离权限后台管理系统-Java

热门文章

  1. 带中文索引的ListView 仿微信联系人列表
  2. Android loading进度条使用简单总结
  3. 几个想法,有兴趣的可以深入下去
  4. 【求助】AIX5.3主机下 memcached的内存使用异常
  5. 生信入门-爱课程上的华中农业大学
  6. PHP 多维数组转json对象
  7. 汉三水属国(北地属国、安定属国)
  8. 设计模式——享元模式具体解释
  9. php 设计模式 - 单例
  10. 【转】linux之fsck命令