验证规则构建神器 FluentValidation.md

上一篇文章《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」 的验证规则设置能力非常强大,下图中是目前所有内置的规则验证:1be129d7a8571d55f7756341e4169eae.png用之前,我们通常需要引用的几个包:

  • 「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尝试一下,「实践出真知」

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/294230.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基本排序算法一

一 选择排序 原理&#xff1a;选择排序很简单&#xff0c;他的步骤如下&#xff1a; 从左至右遍历&#xff0c;找到最小(大)的元素&#xff0c;然后与第一个元素交换。从剩余未排序元素中继续寻找最小&#xff08;大&#xff09;元素&#xff0c;然后与第二个元素进行交换。以此…

老大爷的手法一看就不一般!

1 超市门口的双枪老大爷▼2 小朋友&#xff1a;谢邀&#xff0c;人在机场&#xff0c;刚下飞船▼3 向你保证这真的是一副刺绣作品▼4 外国最新挑战【我打我自己接力】▼5 疫情期间在家隔离的健身人士们快要被逼疯了▼6 给大家表演一个大变活人吧▼7 家有神兽的家长最近一…

Gamebryo实例学习之二BackgroundLoad

2019独角兽企业重金招聘Python工程师标准>>> 一、简介 后台加载允许应用程序以一个优先级低于主线程的后台线程来加载NIF文件。这个程序演示了如何使用BackgroundLoad后台加载。 二、解析 程序继承了实例基类NiSample。 CallbackStream继…

WPF 实现圣诞树

WPF开发者QQ群&#xff1a; 340500857 | 微信群 -> 进入公众号主页 加入组织由于微信群人数太多入群请添加小编微信号&#xff08;yanjinhuawechat&#xff09;或&#xff08;W_Feng_aiQ&#xff09;邀请入群&#xff08;需备注WPF开发者&#xff09;PS&#xff1a;有更好的…

sdut2784cf 126b Good Luck!(next数组)

链接 next数组的巧妙应用 学弟出给学弟的学弟的题。。 求最长的 是前缀也是后缀同时也是中缀的串 next的数组求的就是最长的前后缀 但是却不能求得中缀 所以这里 就把尾部去掉之后再求 这样就可以保证是中缀了 先把所有既是前缀也是后缀的长度的求出来标记 然后再去掉尾部 求…

聊一聊基于Nacos的metadata完成服务间的AB测试

背景 在很多时候&#xff0c;产品同学或其他 boss 会有一些想法&#xff0c;或好或坏&#xff0c;都会想放到线上环境去验证&#xff0c;看看能不能带来更好的效果。这其实就是一个提出假设和验证假设的过程&#xff0c;而 AB 测试&#xff0c;是验证假设的好方法。对于服务之间…

豆瓣评分9分+,每一部看完不禁感慨!这里是神州大地!

全世界只有3.14 % 的人关注了爆炸吧知识纪录片的一大重要意义&#xff0c;就在于它能将我们的视野和脚步&#xff0c;引向我们无法企及的地方和领域&#xff0c;又能让那些我们曾经到过的地方、经历过的人事&#xff0c;变得更有深意。今天&#xff0c;就给大家分享7部顶级纪录…

旅游社交网站 游范儿

为什么80%的码农都做不了架构师&#xff1f;>>> 应用名称&#xff1a;旅游社交网站 游范儿 应用URL地址&#xff1a;http://tumi.cloudfoundry.com/ 应用说明及使用场景&#xff1a; 用于爱好旅游的人士&#xff0c;发游记&#xff0c;以及所见所闻&#xff0c;…

nginx源码学习Unix - Unix域协议

说到什么是域协议就会出现这么个解释&#xff1a; UNIX域协议并不是一个实际的协议族&#xff0c;而是在单个主机上执行客户/服务器通信的一种方法&#xff0c;所用API与在不同主机上执行客户/服务器通信所使用的API相同。UNIX域协议可以视为IPC方法之一。 我们白话解释下Unix域…

oracle12c考试内容,12c ocp考试内容

oca1z0-047(Oracle Database SQL Expert 1Z0-047) 60个题&#xff0c;90分钟&#xff0c;66%过关。/1z0-051(Oracle Database 11g: SQL Fundamentals I 1Z0-051) 64个题&#xff0c;120分钟&#xff0c;60%过关。/1z0-061(Oracle Database 12c: SQL Fundamentals 1Z0-061) 75个…

微软开源的Web测试和自动化神器 Playwright

Playwright 是微软开源的一个用于 Web 测试和自动化的框架, 提供了可靠的端到端测试, 功能非常强大, 可以在测试, 爬虫&#xff0c;自动化场景中使用。跨浏览器Playwright 支持所有现代的渲染引擎&#xff0c;包括 Chromium、WebKit 和 Firefox。跨平台在 Windows, Linux 和 ma…

史上最厉害的“1+2”!这个270年前出现的大难题,已经60多年没有出现好消息了..........

全世界只有3.14 % 的人关注了爆炸吧知识费马费马欧拉欧拉数学是科学的皇后数论是数学中的皇冠这顶皇冠每一次被举起它的光芒都在照亮数学的前方从112到“12”人类一次次逼近“哥德巴赫猜想”的真相从一张白纸到上面写满n>2的证明“费马大定理”凝聚成了一部数学史从2、3、5、…

放寒假的硕博研究生将经历什么?

全世界只有3.14 % 的人关注了爆炸吧知识1月中下旬基本全国的高校都放假了&#xff0c;除了部分因为疫情滞留在学校和外地的学生&#xff0c;绝大多数的学生都会回家过年。平时自带学霸光环&#xff0c;可以借口工作学业繁忙&#xff0c;不回家&#xff0c;不用应酬&#xff0c;…

自动化测试有感

1、 研究自动化测试也有一段时间了&#xff0c;从不熟悉到慢慢的了解&#xff0c;从不会到会&#xff0c;从迷茫到清晰...... 前段时间一直都很疑惑&#xff0c;为什么要自动化&#xff0c;自动化能给我们带来哪些好处&#xff1f;它存在的价值在哪里&#xff1f;运行一个脚本…

iOS 集合的深复制与浅复制

2019独角兽企业重金招聘Python工程师标准>>> 概念 对象拷贝有两种方式&#xff1a;浅复制和深复制。顾名思义&#xff0c;浅复制&#xff0c;并不拷贝对象本身&#xff0c;仅仅是拷贝指向对象的指针&#xff1b;深复制是直接拷贝整个对象内存到另一块内存中。 一图以…

Adb安装程序出现TimeOut错误

为什么80%的码农都做不了架构师&#xff1f;>>> 安装Apk过程中&#xff0c;出现如下错误&#xff1a; Failed to install on device ‘XXX′: timeout 原因时设备速度太卡&#xff0c;导致启动超时&#xff0c;解决办法&#xff1a;延长超时时间。 方法&#xff…

2021.NET大会日程首发!行程亮点全曝光!

{倒计时4天文末有福利→.NET机器人定制抱枕}2021年12月18日由中国各地技术社区共同发起举办、知名企业和开源组织联合协办的2021年中国.NET开发者大会即将盛大开幕▽2020/12/18-12/19主题&#xff1a;开源共建|开放创新|开发赋能形式&#xff1a;线上直播- 长按二维码免费领票 …

继Science发文后,Nature也发文评论曹雪涛“误用图片”调查结果

全世界只有3.14 % 的人关注了爆炸吧知识本文转自&#xff1a;募格学术2021年1月26日傍晚 Nature 网站以头条新闻的方式刊出了题为“著名中国免疫学家没有剽窃和学术造假”的新闻并配以曹雪涛的照片&#xff0c;该新闻大篇幅报道了科技部等多部门对于中国工程院院士曹雪涛的联合…

linux运行.pak文件,使用game-to-flatpak脚本将商业Linux游戏安装程序转换为Flatpak应用程序...

现在有一个新的脚本&#xff0c;它允许你将各种商业Linux游戏的安装程序转换成可以在各种GNU/Linux发行版上运行的Flatpak软件包。这是一个开源的shell脚本&#xff0c;由GNOME开发人员Bastien Nocera开发&#xff0c;它做了一件事&#xff0c;即自动将各种格式的商业Linux游戏…