正确写法是:对可能为null的导航属性用NotNull()守卫顶层+When()显式判空,值类型用Must()判断;异步校验需用MustAsync()或SetValidator()指向含异步规则的验证器;注册时须AddControllers().AddFluentValidation()并确保特性与泛型匹配。
FluentValidation 的
怎么写才不踩空引用?
直接继承
时,最常见的崩溃是校验器内部访问了
属性却没提前判空——比如
,但
是
,就会抛
。
正确做法是:所有可能为
的导航属性,必须用
或链式
+
分层守卫:
别依赖
自动跳过子属性校验——它只拦自己,不拦后续链式调用
如果
是值类型(如
),
无效,得用
手动判断
泛型校验器注册到 DI 容器时,确保生命周期是
或
,避免跨请求状态污染
怎么让
真正异步,而不是假异步?
默认是同步执行的,除非你显式用了异步规则(比如调用外部 API、数据库查重)。很多人误以为只要方法签名带
就自动异步,结果压测时线程池被卡死。
真正触发异步的只有两类规则:
:传入
指向一个继承自
且内部含异步规则的验证器
示例(邮箱唯一性检查):
注意:
内部不能混用同步 I/O(如直接调用
),否则会阻塞线程;也别在异步规则里 throw 异常,要用
+
控制错误输出。
C知道
CSDN推出的一款AI技术问答工具
下载
为什么
后模型绑定仍不触发校验?
ASP.NET Core 中,仅注册 FluentValidation 不等于开启自动校验。常见遗漏点:
没在
调用
(MVC)或
(Razor Pages)
控制器参数没加
特性 —— 它启用了默认的
自动验证拦截
参数是
或基类类型(如
),但注册时只配了子类
,需手动指定
或用
验证失败时,默认返回
并附带
错误。若想统一响应格式,要替换
,而不是重写 ActionFilter。
自定义错误消息里的占位符为什么不替换?
、
这些占位符失效,通常是因为:
手误写成大括号全角字符(中文输入法下容易中招)
在
中用了字符串插值(
),导致占位符被提前求值,变成字面量
使用了
但没配合
,导致
仍显示原始属性名
正确写法:
复杂消息建议用资源文件 +
,避免硬编码和多语言维护困难。
FluentValidation 的核心不是“写更多规则”,而是让每条规则的执行边界清晰、失败路径可控。最容易被忽略的是:异步规则的异常吞吐、
导航属性的隐式链式访问、以及 ASP.NET Core 中验证触发链的完整闭环——缺一环,就只是个漂亮的装饰器。
AbstractValidatorAbstractValidatornullRuleFor(x => x.Address.Street)AddressnullNullReferenceExceptionnullWhen()NotNull()SetValidator()RuleFor(x => x.Address).NotNull().WithMessage("地址不能为空");
RuleFor(x => x.Address.Street)
.NotEmpty().When(x => x.Address != null); // 显式守卫
NotNull()AddressstructNotNull()Must()ScopedTransientValidateAsync()ValidateAsync()AsyncMustAsync()Func> SetValidator()AbstractValidatorRuleFor(x => x.Email)
.MustAsync(async (model, email, ct) =>
{
return !await _userRepository.ExistsByEmailAsync(email, ct);
})
.WithMessage("该邮箱已被注册");
MustAsyncHttpClient.Send()return falseWithMessageAddFluentValidation()Program.csservices.AddControllers().AddFluentValidation(...)AddFluentValidationClientsideAdapters()[ApiController]ModelStateobjectPersonBasePersonValidatorvalidatorTypeRegisterValidatorsFromAssemblyContaining() 400 BadRequestModelStateInvalidModelStateResponseFactory{PropertyName}{PropertyValue}WithMessage()$"xxx {x.Name}"WithName()OverridePropertyName{PropertyName}RuleFor(x => x.Age)
.GreaterThan(0)
.WithMessage("{PropertyName} 必须大于 {ComparisonValue}") // ✅ 占位符原样传入
.WithName("年龄"); // ✅ 配合生效
WithLocalizedMessage()null