- 后端
【免费下载链接】FluentValidation
A popular .NET validation library for building strongly-typed validation rules.
导读
本文基于 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,其Errors为IEnumerable<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(...)(使用自定义选择器)等高级选项。当多个选项同时出现时,内部会将对应的MemberNameValidatorSelector、RulesetValidatorSelector组合成CompositeValidatorSelector一起生效。
仓库中的测试 ValidateAndThrowTester.cs 验证了相关行为:验证失败时抛出ValidationException、携带错误信息、验证成功时不抛出异常,以及规则集与ValidateAndThrowAsync的组合场景。
自定义异常类型
ValidateAndThrow默认抛出ValidationException。如果需要每次抛出特定类型的自定义异常,可以通过在验证器中重写RaiseValidationException方法实现,具体做法见 自定义验证异常。
复杂属性:复用子验证器
验证器可以针对复杂属性进行复用。假设有两个类Customer和Address:
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()); } }这样,当调用CustomerValidator的Validate时,会依次执行CustomerValidator与AddressValidator中定义的所有规则,并把两部分的失败信息合并到同一个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。
异步验证
对于包含MustAsync、SetValidator等异步验证器的场景,应使用ValidateAsync(见 AbstractValidator.cs),它接受一个CancellationToken,并会逐条执行规则的异步版本:
ValidationResult result = await validator.ValidateAsync(customer, cancellationToken);注意:ValidateAsync与Validate是两条独立的执行路径,同步调用无法执行异步规则,反之亦然,请根据实际规则类型选择对应入口。
级联模式(CascadeMode)
AbstractValidator<T>暴露了ClassLevelCascadeMode(规则之间)与RuleLevelCascadeMode(单条规则内部)两个可配置属性,默认值分别取自ValidatorOptions.Global.DefaultClassLevelCascadeMode与DefaultRuleLevelCascadeMode(见 AbstractValidator.cs)。Continue表示无论是否失败都继续执行,Stop表示失败即停止,这是控制失败后短路行为的关键开关,详见 cascade.md。
小结
通过本文,你已经掌握了 FluentValidation 的完整入门链路:
- 用
AbstractValidator<T>定义验证器,在构造函数中用RuleFor+ lambda 声明属性规则; - 调用
Validate得到ValidationResult,通过IsValid/Errors检查结果,用ToString/ToDictionary汇总错误; - 用链式调用对同一属性叠加多个约束;
- 用
ValidateAndThrow(及其 Options API 等价形式)在失败时抛出ValidationException; - 用
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.
相关推荐
FluentValidation入门指南:创建第一个验证器
FluentValidation入门指南:创建第一个验证器 什么是FluentValidation FluentValidation是一个流行的.NET验证库,
后端搞定复杂数据验证:FluentValidation对象与集合校验实战
搞定复杂数据验证:FluentValidation对象与集合校验实战 你还在为嵌套表单数据校验抓狂?用户提交的订单列表总是包含无效商品?本文将带你掌握Fluen
后端快速上手FluentValidation:从零构建你的第一个验证器的完整教程
快速上手FluentValidation:从零构建你的第一个验证器的完整教程 FluentValidation 是一款广受欢迎的 .NET 验证库,它通过流式接
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考