news 2026/9/24 14:00:28

FluentValidation 入门实战:从第一个验证器到复杂属性验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FluentValidation 入门实战:从第一个验证器到复杂属性验证
  • 后端

【免费下载链接】FluentValidation

A popular .NET validation library for building strongly-typed validation rules.

项目地址:https://gitcode.com/gh_mirrors/fl/FluentValidation
点击查看免费下载

导读

本文基于 FluentValidation 官方入门文档(docs/start.md),带你从零创建一个强类型验证器:先通过AbstractValidator<T>RuleFor定义规则,再调用Validate获取ValidationResult处理错误;随后深入链式验证、ValidateAndThrow异常抛出,以及通过SetValidator复用子验证器验证复杂属性。文末结合当前仓库的 AbstractValidator.cs、ValidationResult.cs、ValidationStrategy.cs 等源码,揭示每一步背后的底层实现原理,帮助你在实际项目中写出严谨、可维护的验证逻辑。


创建你的第一个验证器

FluentValidation 采用"验证器(Validator)与业务对象分离"的设计:要为某个对象定义一组验证规则,你需要创建一个继承自AbstractValidator<T>的类,其中T就是要验证的对象类型。

假设你有一个Customer类:

public class Customer { public int Id { get; set; } public string Surname { get; set; } public string Forename { get; set; } public decimal Discount { get; set; } public string Address { get; set; } }

通过继承AbstractValidator<Customer>来定义它的验证器:

using FluentValidation; public class CustomerValidator : AbstractValidator<Customer> { }

验证规则本身应当写在验证器类的构造函数中。构造函数会在验证器实例化时执行,通过流式 API 把一条条规则注册到内部规则集合里。

要针对某个属性指定验证规则,调用RuleFor方法,并传入一个指示待验证属性的 lambda 表达式。例如,要确保Surname不为 null,验证器写成:

using FluentValidation; public class CustomerValidator : AbstractValidator<Customer> { public CustomerValidator() { RuleFor(customer => customer.Surname).NotNull(); } }

从源码看,RuleFor的定义位于 AbstractValidator.cs:它把 lambda 表达式交给PropertyRule<T, TProperty>.Create解析成一条内部规则,添加到Rules集合,并返回一个RuleBuilder供后续链式调用。也就是说,构造函数中每写一条RuleFor,就相当于向验证器注册了一条待执行规则;验证器还实现了IEnumerable<IValidationRule>,因此可以直接遍历它持有的全部规则。


运行验证:Validate 方法与 ValidationResult

定义好验证器后,实例化它并调用Validate方法,传入要验证的对象即可执行验证:

Customer customer = new Customer(); CustomerValidator validator = new CustomerValidator(); ValidationResult result = validator.Validate(customer);

Validate返回一个ValidationResult对象,其中包含两个核心属性:

  • IsValid—— 布尔值,表示验证是否成功。
  • Errors—— 一组ValidationFailure对象,包含所有验证失败的详细信息。

IsValid的实现非常直观,见 ValidationResult.cs:Errors.Count == 0即验证成功。Errors集合中的每个ValidationFailure都携带丰富信息,除PropertyName(属性名)与ErrorMessage(错误消息)外,还包含AttemptedValue(导致失败的值)、CustomState(自定义状态)、Severity(严重级别,默认Severity.Error)与ErrorCode(错误码),完整定义见 ValidationFailure.cs。

下面的代码会把所有验证失败信息输出到控制台:

using FluentValidation.Results; Customer customer = new Customer(); CustomerValidator validator = new CustomerValidator(); ValidationResult results = validator.Validate(customer); if(! results.IsValid) { foreach(var failure in results.Errors) { Console.WriteLine("Property " + failure.PropertyName + " failed validation. Error was: " + failure.ErrorMessage); } }

将错误合并为字符串:ToString

ValidationResult还重写了ToString,可以把所有错误消息合并成单个字符串。默认使用换行符分隔各条消息;如果你想自定义分隔符,可以向ToString传入一个分隔字符:

ValidationResult results = validator.Validate(customer); string allMessages = results.ToString("~"); // In this case, each message will be separated with a `~`

源码层面,ToString()无参重载委托给ToString(Environment.NewLine),最终通过string.Join(separator, _errors.Select(failure => failure.ErrorMessage))拼接(见 ValidationResult.cs)。

注意:如果没有验证错误,ToString()会返回一个空字符串。

另外,ValidationResult还提供ToDictionary()方法,将错误按属性名分组,返回IDictionary<string, string[]>,便于在 API 层直接序列化或绑定到表单错误上。


链式验证:一条规则串联多个约束

你可以针对同一个属性把多个验证器链在一起,每个验证器按书写顺序依次执行:

using FluentValidation; public class CustomerValidator : AbstractValidator<Customer> { public CustomerValidator() { RuleFor(customer => customer.Surname).NotNull().NotEqual("foo"); } }

这条链式规则同时确保Surname不为 null,且不等于字符串"foo"。链式调用的机制在于RuleFor返回的IRuleBuilder上挂载了一系列内置验证器扩展方法(.NotNull().NotEqual().Length().EmailAddress()等),每个方法都会把一个新的验证器组件追加到当前规则上,并返回构建器本身以继续链式调用。仓库中的内置验证器分布在 src/FluentValidation/Validators 目录下,例如 NotNullValidator.cs、EqualValidator.cs。

需要说明的是,链式验证属于**规则内部(rule-level)**的级联行为:默认情况下,同一条链上的验证器无论前一个是否失败都会全部执行;若希望同一条链上某个验证器失败后立即停止后续验证器,可通过级联模式(CascadeMode)配置,相关机制详见 cascade.md。


抛出异常:ValidateAndThrow

前面使用Validate时,验证失败只会体现在返回的ValidationResult中,并不会中断程序。如果你希望在验证失败时直接抛出异常,可以使用ValidateAndThrow方法:

Customer customer = new Customer(); CustomerValidator validator = new CustomerValidator(); validator.ValidateAndThrow(customer);

当验证失败时,它会抛出一个ValidationException,该异常通过Errors属性携带全部错误消息(异常类型定义见 ValidationException.cs,其ErrorsIEnumerable<ValidationFailure>)。

注意ValidateAndThrow是一个扩展方法,因此文件顶部必须用using FluentValidation;引入命名空间,该方法才可用。同步版本ValidateAndThrow与异步版本ValidateAndThrowAsync都定义在 DefaultValidatorExtensions_Validate.cs 中。

底层等价写法:Options API

ValidateAndThrow本质上是 FluentValidation Options API 的一个便捷封装,等价于:

validator.Validate(customer, options => options.ThrowOnFailures());

ThrowOnFailures()定义于 ValidationStrategy.cs,它会设置内部_throw标志,使得Validate执行完毕后若结果无效便抛出异常。

如果需要在抛异常的同时,组合使用 Rule Sets(规则集)或只验证指定属性,可以通过 Options 语法同时配置多个选项:

validator.Validate(customer, options => { options.ThrowOnFailures(); options.IncludeRuleSets("MyRuleSets"); options.IncludeProperties(x => x.Name); });

这里用到的三个关键方法都来自ValidationStrategy<T>

  • ThrowOnFailures():验证失败时抛异常;
  • IncludeRuleSets("MyRuleSets"):仅执行指定规则集中的规则;
  • IncludeProperties(x => x.Name):仅验证指定属性。

ValidationStrategy还提供IncludeAllRuleSets()(相当于"*",执行所有规则)、IncludeRulesNotInRuleSet()(相当于"default",只执行不在规则集中的规则)和UseCustomSelector(...)(使用自定义选择器)等高级选项。当多个选项同时出现时,内部会将对应的MemberNameValidatorSelectorRulesetValidatorSelector组合成CompositeValidatorSelector一起生效。

仓库中的测试 ValidateAndThrowTester.cs 验证了相关行为:验证失败时抛出ValidationException、携带错误信息、验证成功时不抛出异常,以及规则集与ValidateAndThrowAsync的组合场景。

自定义异常类型

ValidateAndThrow默认抛出ValidationException。如果需要每次抛出特定类型的自定义异常,可以通过在验证器中重写RaiseValidationException方法实现,具体做法见 自定义验证异常。


复杂属性:复用子验证器

验证器可以针对复杂属性进行复用。假设有两个类CustomerAddress

public class Customer { public string Name { get; set; } public Address Address { get; set; } } public class Address { public string Line1 { get; set; } public string Line2 { get; set; } public string Town { get; set; } public string Country { get; set; } public string Postcode { get; set; } }

先为Address定义一个AddressValidator

public class AddressValidator : AbstractValidator<Address> { public AddressValidator() { RuleFor(address => address.Postcode).NotNull(); //etc } }

然后在CustomerValidator中通过SetValidator复用它:

public class CustomerValidator : AbstractValidator<Customer> { public CustomerValidator() { RuleFor(customer => customer.Name).NotNull(); RuleFor(customer => customer.Address).SetValidator(new AddressValidator()); } }

这样,当调用CustomerValidatorValidate时,会依次执行CustomerValidatorAddressValidator中定义的所有规则,并把两部分的失败信息合并到同一个ValidationResult中返回。

子属性为 null 时的行为

如果子属性为 null,则子验证器不会执行。这一行为在源码 ChildValidatorAdaptor.cs 中清晰可见:IsValid方法第一步就是if (value == null) return true;,即属性值为 null 时直接跳过子验证器。RuleBuilder.SetValidator(IValidator<TProperty>)(见 RuleBuilder.cs)内部会把这个子验证器包装成ChildValidatorAdaptor并注册到规则中,同时它天然支持同步与异步执行。

替代方案:内联子规则

除了使用子验证器,你也可以直接内联定义子属性规则:

RuleFor(customer => customer.Address.Postcode).NotNull()

注意,这种写法不会自动对Address执行 null 检查——属性表达式直接穿透到了Address.Postcode。如果Address为 null,访问Postcode会引发空引用问题,因此需要显式添加条件:

RuleFor(customer => customer.Address.Postcode).NotNull().When(customer => customer.Address != null)

.When(...)接收一个谓词,只有谓词返回 true 时才执行该规则;Address不为 null 时才校验Postcode。与之对应的否定形式是.Unless(...)(谓词为 false 时执行)。两者的实现同样位于 AbstractValidator.cs 中。


延伸:从源码看验证执行链路

Validate 的完整流程

AbstractValidator.cs 中,Validate(T instance)会构造一个ValidationContext<T>,然后进入ValidateInternal:先调用PreValidate钩子,再遍历Rules集合逐条执行;当某条规则产生失败且验证器的ClassLevelCascadeMode == CascadeMode.Stop时,会提前终止后续规则(快速失败)。此外,传入 null 模型会抛出InvalidOperationException(提示根模型不能为 null)。同步遍历规则时若遇到异步验证器,会抛出AsyncValidatorInvokedSynchronouslyException,提醒开发者改用ValidateAsync

异步验证

对于包含MustAsyncSetValidator等异步验证器的场景,应使用ValidateAsync(见 AbstractValidator.cs),它接受一个CancellationToken,并会逐条执行规则的异步版本:

ValidationResult result = await validator.ValidateAsync(customer, cancellationToken);

注意:ValidateAsyncValidate是两条独立的执行路径,同步调用无法执行异步规则,反之亦然,请根据实际规则类型选择对应入口。

级联模式(CascadeMode)

AbstractValidator<T>暴露了ClassLevelCascadeMode(规则之间)与RuleLevelCascadeMode(单条规则内部)两个可配置属性,默认值分别取自ValidatorOptions.Global.DefaultClassLevelCascadeModeDefaultRuleLevelCascadeMode(见 AbstractValidator.cs)。Continue表示无论是否失败都继续执行,Stop表示失败即停止,这是控制失败后短路行为的关键开关,详见 cascade.md。


小结

通过本文,你已经掌握了 FluentValidation 的完整入门链路:

  1. AbstractValidator<T>定义验证器,在构造函数中用RuleFor+ lambda 声明属性规则;
  2. 调用Validate得到ValidationResult,通过IsValid/Errors检查结果,用ToString/ToDictionary汇总错误;
  3. 用链式调用对同一属性叠加多个约束;
  4. ValidateAndThrow(及其 Options API 等价形式)在失败时抛出ValidationException
  5. SetValidator复用子验证器验证复杂属性,并注意子属性为 null 时的自动跳过与内联规则下需要手动添加When条件。

以上示例代码均可直接复制运行。若要继续深入,可依次阅读仓库中的 custom-validators.md、built-in-validators.md、collections.md 与 testing.md 等文档。

  • 后端

【免费下载链接】FluentValidation

A popular .NET validation library for building strongly-typed validation rules.

项目地址:https://gitcode.com/gh_mirrors/fl/FluentValidation
点击查看免费下载
上一篇:Fedora-Hyprland性能优化技巧:让你的Hyprland桌面运行如飞
下一篇:Ytt 集成开发指南:如何将模板引擎嵌入你的 Go 应用

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 14:00:23

RK3588平台开发系列讲解(调试篇)CGroup 精细化的控制

文章目录 一、CPU 与 CGroup 二、限制进程的 CPU 资源占用 三、cpu.shares:多个 cgroup 组的权重划分 四、sched_autogroup 沉淀、分享、成长,让自己和他人都能有所收获!😄 CGroup 的全称是 Control Group,是容器实现环境隔离的两种关键技术之一,它对很多子系统提供精细…

作者头像 李华
网站建设 2026/9/24 13:58:39

Django实现异步视图asyncio请求

随着现代Web应用程序对性能和响应速度的需求不断增加,开发者们越来越倾向于采用异步编程来提升应用的效率和用户体验。在传统的Web开发框架中,通常采用同步请求方式,这意味着每一个请求都需要等待前一个请求完成后才能继续处理。对于高并发的请求,可能会出现性能瓶颈。而Dj…

作者头像 李华
网站建设 2026/9/24 13:58:29

WCH-LINK与DAP-LINK驱动安装失败排查完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华