验证器藏在哪里?深入解析 Blazored.FluentValidation 的 DI 注册与程序集扫描机制
【免费下载链接】FluentValidationA library for using FluentValidation with Blazor项目地址: https://gitcode.com/gh_mirrors/flue/FluentValidation
Blazored.FluentValidation是一个让 FluentValidation 在 Blazor 项目中无缝工作的开源库,很多新手第一次用它时都会产生同一个困惑:明明写了AbstractValidator<T>,为什么表单提交时验证规则没有生效?答案往往就藏在"验证器是怎么被找到的"这个环节里。本文就带你把DI 注册与程序集扫描这两条查找路径彻底摸清,从此再也不怕验证器"迷路"。
为什么验证器会"找不到"?🤔
在 Blazor 的EditForm中,验证动作是由<FluentValidationValidator />组件驱动的。这个组件本身并不知道你的PersonValidator存在,它只负责一件事:根据当前表单绑定的模型类型,去"找"对应的验证器。
查找规则只有两条,按顺序执行:
- 先去DI 容器里找有没有注册
IValidator<Person>; - 找不到,再启动程序集扫描,用反射在整个应用的程序集中翻找。
这两条路径都藏在库的扩展方法里,入口就在 EditContextFluentValidationExtensions.cs 的GetValidatorForModel方法中,它是整个查找机制的"心脏"。
第一条路径:DI 注册优先,命中即用 ✅
先看最简单、也最推荐的方式。在Program.cs里把验证器注册进 DI 容器,例如示例项目 BlazorServer/Program.cs 中的写法:
builder.Services.AddTransient<IValidator<Person>, PersonValidator>(); builder.Services.AddTransient<IValidator<Address>, AddressValidator>();运行时,库会动态构造IValidator<Person>这个泛型类型,然后调用serviceProvider.GetService(...)查询容器。如果拿到了实例,就直接使用,完全不会触发程序集扫描。
💡 小技巧:只要走 DI 这条路,你的验证器可以随意命名、可以依赖构造函数注入其他服务(比如访问数据库做唯一性校验),非常灵活。
第二条路径:程序集扫描自动兜底 🕵️
如果 DI 里没找到,库会启动程序集扫描兜底:遍历AppDomain.CurrentDomain.GetAssemblies()返回的所有程序集,用 FluentValidation 自带的AssemblyScanner.FindValidatorsInAssembly找出里面所有验证器,再与当前模型类型匹配。
这部分代码有几个值得注意的细节:
- 结果被缓存在静态字段
AssemblyScanResults中,扫描过的程序集也记录在ScannedAssembly列表里,不会重复扫描,避免性能损耗; - 扫描某个程序集抛异常会被静默吞掉(源码注释明确说明这是为了防止第三方依赖的问题搞崩整个应用);
- 找到验证器类型后,通过
ActivatorUtilities.CreateInstance(serviceProvider, modelValidatorType)实例化,依然能让验证器享受依赖注入的能力。
这个机制正是 BlazorWebAssembly 示例 使用的方案——它的Program.cs里没有注册任何验证器,全靠扫描自动发现。
DisableAssemblyScanning:一键切换查找模式的开关 🎛️
程序集扫描虽方便,但反射遍历 + 全程序集匹配毕竟有开销。库提供了一个DisableAssemblyScanning参数,让你明确告诉它"只用 DI,别扫描":
<FluentValidationValidator DisableAssemblyScanning="@true" />这个开关的行为在单元测试里有非常直观的验证,见 AssemblyScanning/Tests.cs:
| 场景 | DisableAssemblyScanning | DI 注册验证器 | 结果 |
|---|---|---|---|
| 关闭扫描,无 DI 注册 | true | ❌ | 校验不执行 |
| 关闭扫描,但有 DI 注册 | true | ✅ | 校验正常执行 |
| 开启扫描(默认) | false | ❌ | 靠扫描自动发现 |
| 不设置(默认值) | 未设置 | ❌ | 靠扫描自动发现 |
从测试可以看出:即使关闭了扫描,只要 DI 里注册了验证器,一切依然正常——DI 永远是第一优先级。
程序集扫描的四个注意事项 ⚠️
想靠自动扫描找到你的验证器,必须满足以下条件:
- 验证器类必须是 public 公开类,且直接继承自
AbstractValidator<T>; - 模型的属性类型要和验证器的泛型参数完全匹配,扫描结果通过
IValidator<>泛型接口判断归属; - 程序集必须在当前
AppDomain可加载的范围内,未被引用的程序集自然扫不到; - 部分依赖注入(如 Blazor WASM)中程序集加载时机不同,扫描可能不如服务端可靠——这也是为什么 README 里两个示例采用了不同策略。
组件本身的注册与初始化逻辑见 FluentValidationsValidator.cs:它在OnInitialized时把自己挂到EditContext上,把DisableAssemblyScanning、Validator等参数一并传下去,随后的每次表单验证、字段级验证都会走同一条查找链路。
实战建议:何时用 DI,何时靠扫描?🧭
给普通用户一个简单粗暴的结论:
- 项目小、验证器少→ 直接 DI 注册,明确、快速、可调试;
- 验证器成百上千、不想逐个注册→ 打开程序集扫描,
DisableAssemblyScanning保持默认false即可; - 想两者兼顾→ 保持默认:DI 优先命中,扫描作为兜底,互不冲突。
现在你知道了验证器藏在哪里:它要么在 DI 容器里等你注册,要么藏在某个程序集的角落等着被扫描。理解这两条路径后,遇到"验证不生效"的问题,第一步就该检查——验证器到底走的是哪条路,路通了吗?
🔧 想亲手验证这套机制?可以克隆示例项目
https://gitcode.com/gh_mirrors/flue/FluentValidation到本地,对照 BlazorServer 与 BlazorWebAssembly 两个示例分别运行,直观感受两种查找模式的差异。
【免费下载链接】FluentValidationA library for using FluentValidation with Blazor项目地址: https://gitcode.com/gh_mirrors/flue/FluentValidation
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考