FluentValidation验证器必须继承AbstractValidator<T>,泛型参数不可省;RuleFor后须接NotEmpty()等验证方法,否则规则不生效;类名须以Validator结尾,注册需AddFluentValidation()并引用FluentValidation.AspNetCore包。

FluentValidation 不是“加个特性就能跑”的验证工具,它需要显式注册、正确继承、链式书写,否则 Validate() 返回 IsValid == true 却不报错——不是没校验,是根本没被调用或构造失败静默退化。
验证器类怎么写才不会编译失败或静默失效
必须继承 AbstractValidator<T>,泛型参数不能漏;构造函数里每条 RuleFor() 后必须接至少一个验证方法(如 NotEmpty()、GreaterThan(0)),光写 RuleFor(x => x.Name) 不加后续调用,这条规则完全不生效。
-
RuleFor(x => x.Email)后不能跟?.Domain或方法调用(如x.GetNormalizedEmail()),表达式树只支持简单属性访问,否则抛"Expression not supported" - 字符串校验防空内容,用
NotEmpty(),别只用NotNull()——后者对""无效 - 验证器类名必须以
Validator结尾(如UserValidator),否则 ASP.NET Core 自动扫描会跳过它 - 若构造函数依赖
IOptions<MyConfig>等服务,手动new UserValidator()可能因构造失败返回空验证器;应从 DI 容器解析实例
ASP.NET Core 里怎么让它自动生效而不是手动调用
不注册就等于没装——AddFluentValidation() 是开关,不是装饰。默认只启用自动验证,但需确保控制器参数类型与验证器泛型匹配(如接收 User 就得有 UserValidator : AbstractValidator<User>)。
- 在
Program.cs中注册:builder.Services.AddControllers().AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<UserValidator>()); - 必须引用
FluentValidation.AspNetCore包,仅装FluentValidation核心包无法集成 MVC 生命周期 -
[ApiController]会自动返回 400,但前提是验证器已注册且类名规范;否则ModelState.IsValid仍为true,错误只在Validate()结果里 - 若同时用了
DataAnnotations,建议关掉默认校验:options.ModelValidatorProviders.Clear();防止重复触发和错误消息冲突
链式调用中断、规则不执行的常见原因
链式不是语法糖,是对象流转:每个 NotEmpty()、Must() 都返回新的 IRuleBuilder 实例。断开链 = 丢失上下文 = 规则被丢弃。
- ❌ 错误写法:
RuleFor(x => x.Age); GreaterThan(0);—— 第二行无接收者,编译失败 - ✅ 正确写法:
RuleFor(x => x.Age).GreaterThan(0).LessThan(150); - 规则顺序影响执行:若
Must()抛异常或返回 false,其后同字段的WithMessage()或其他验证不会执行;基础规则(NotNull()、NotEmpty())建议前置 - 跨字段逻辑(如“结束时间 > 开始时间”)必须用
Must(),且参数是整个对象:Must(x => x.EndTime > x.StartTime),不能写成RuleFor(x => x.EndTime).Must(...)
验证失败时怎么拿到可读的字段级错误
Validate() 返回 ValidationResult,不是布尔值;result.Errors 才是真实错误集合,每个 ValidationFailure 含 PropertyName、ErrorMessage、AttemptedValue。
- 前端要字段提示,直接序列化:
result.Errors.Select(e => new { e.PropertyName, e.ErrorMessage }),别拼字符串 -
WithMessage()必须紧跟在验证方法之后,否则消息不会绑定到该规则(如NotEmpty().WithMessage("xxx").MaximumLength(100),后者无消息) - 想覆盖默认字段名(如把
Email显示为“邮箱”),用WithName("邮箱"),放在链任意位置都生效 - 调试时别只看
IsValid,直接打印result.Errors—— 很多“没报错”其实是规则没命中,而非验证通过
最容易被忽略的是:验证器类名不以 Validator 结尾、没装 FluentValidation.AspNetCore、RuleFor 表达式用了 ?. 或方法调用——这三处任一出错,都会导致验证静默失效,且几乎不报编译或运行时异常。


















