ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

源码剖析:Blazored.FluentValidation 如何将 FluentValidation 无缝接入 Blazor EditContext

源码剖析:Blazored.FluentValidation 如何将 FluentValidation 无缝接入 Blazor EditContext 源码剖析Blazored.FluentValidation 如何将 FluentValidation 无缝接入 Blazor EditContext【免费下载链接】FluentValidationA library for using FluentValidation with Blazor项目地址: https://gitcode.com/gh_mirrors/flue/FluentValidationBlazored.FluentValidation 是一个专为 Blazor 打造的 FluentValidation 集成库它用极少的代码将 FluentValidation 的强大校验能力桥接到 Blazor 的 EditContext 验证管道中让EditForm的表单校验变得优雅而自然。本文将从源码层面深入剖析它无缝接入的三大核心机制如何挂钩 EditContext、如何自动发现校验器、以及如何完成字段路径的双向映射帮助你彻底理解这套设计的精妙之处。一、整个库只有 4 个源文件极简架构一览整个核心库位于 src/Blazored.FluentValidation/ 目录下仅 4 个 C# 文件却撑起了完整的功能文件职责FluentValidationsValidator.cs对外暴露的 Blazor 组件作为桥接入口EditContextFluentValidationExtensions.cs核心引擎负责订阅事件、执行校验、映射字段IntersectingCompositeValidatorSelector.cs字段级校验的精妙交集选择器PropertyPathHelper.csFieldIdentifier 与 FluentValidation 属性路径的转换器它只依赖 FluentValidation 和 ASP.NET Core Components 两个包见 Blazored.FluentValidation.csproj支持 net6.0、net7.0、net8.0 三个目标框架零额外依赖的设计让它非常轻量。二、接入第一步如何拿到 EditContext 并挂载自己Blazor 的EditForm组件会通过级联参数CascadingParameter向下传递一个EditContext实例这是整个表单验证的总线。FluentValidationValidator组件继承自ComponentBase正是利用这一机制在 OnInitialized 生命周期中拿到级联的EditContext并立即调用扩展方法完成注册[CascadingParameter] private EditContext? CurrentEditContext { get; set; } protected override void OnInitialized() { CurrentEditContext.AddFluentValidation(ServiceProvider, DisableAssemblyScanning, Validator, this); }这一步非常关键它把 FluentValidation 的校验能力注入到了 Blazor 原生的验证事件流里从此EditContext的每一次验证请求都会自动触发 FluentValidation 校验。三、核心引擎订阅两个事件接管整表与字段校验打开 EditContextFluentValidationExtensions.cs你会发现核心逻辑其实只有两步——创建一个ValidationMessageStore用来存放错误消息然后订阅EditContext的两个事件editContext.OnValidationRequested async (sender, _) await ValidateModel(...); // 整表单验证点击提交时触发 editContext.OnFieldChanged async (_, eventArgs) await ValidateField(...); // 字段级验证输入框内容变化时触发OnValidationRequested表单提交时由EditContext.Validate()触发对应整表全量校验OnFieldChanged任一绑定字段的值变化时触发对应单个字段的实时校验。Blazor 原生组件DataAnnotationsValidator也是同样的模式这说明 Blazored.FluentValidation 完全遵循了 Blazor 的验证扩展点设计这也是它能无缝融入EditForm的根本原因。四、自动发现校验器DI 优先程序集扫描兜底在 ValidateModel 中首先需要找到对应模型的IValidatorT。查找策略在 GetValidatorForModel 中体现得淋漓尽致优先从 DI 容器获取通过typeof(IValidator).MakeGenericType(model.GetType())动态构造泛型类型并解析找不到就扫描程序集遍历当前AppDomain中所有程序集用 FluentValidation 自带的AssemblyScanner.FindValidatorsInAssembly反射查找继承AbstractValidatorT的校验器结果缓存在静态列表中避免重复扫描DisableAssemblyScanning参数如果设置为true则跳过扫描、只信任 DI 注册适合性能敏感或纯 DI 的场景。这种双保险策略让使用者既可以享受 DI 的显式控制参考 samples/BlazorServer/Program.cs也可以享受零配置的自动发现参考 Blazor WebAssembly 示例项目。五、整表校验把 ValidationResult 翻译成 ValidationMessageStore找到校验器后ValidateModel执行一次完整的异步校验然后把结果翻译进 Blazor 的验证系统var asyncValidationTask validator.ValidateAsync(context); editContext.Properties[PendingAsyncValidation] asyncValidationTask; // 挂起任务供异步调用 var validationResults await asyncValidationTask; messages.Clear(); foreach (var validationResult in validationResults.Errors) { var fieldIdentifier ToFieldIdentifier(editContext, validationResult.PropertyName); messages.Add(fieldIdentifier, validationResult.ErrorMessage); } editContext.NotifyValidationStateChanged();这里有两处亮点错误消息准确落位FluentValidation 返回的PropertyName会被转换成FieldIdentifier确保错误提示精准显示在对应输入框下方NotifyValidationStateChanged()通知 Blazor 重新渲染验证 UIValidationMessage、ValidationSummary等原生组件无需任何改动即可展示错误。完整结果还会存入LastValidationResult字典供GetFailuresFromLastValidation方法查询详见 FluentValidationsValidator.cs这样你就能拿到带Severity等完整信息的ValidationFailure实现警告与错误分级展示。六、字段级校验的精妙设计交集选择器只校验变了的字段这是整个库最值得学习的部分。当某个输入框内容变化触发OnFieldChanged时如果直接跑整表校验性能差且会产生其他字段的错误干扰。ValidateField的做法是只校验发生变化的那个字段。具体实现分两步ValidateField用PropertyPathHelper把FieldIdentifier转换为 FluentValidation 的属性路径如Address.Line1构造一个只包含该属性的校验上下文并通过 IntersectingCompositeValidatorSelector.cs 与原有的选择器做交集public bool CanExecute(IValidationRule rule, string propertyPath, IValidationContext context) { return _selectors.All(s s.CanExecute(rule, propertyPath, context)); }这个选择器要求所有子选择器都放行的规则才执行相当于原有规则集 ∩ 当前变更字段从而保证既遵循你配置的 RuleSets / IncludeProperties 策略又只针对当前字段运行校验。最后通过messages.Clear(fieldIdentifier)只清空该字段的旧错误避免闪烁。七、路径双向映射FieldIdentifier 与属性路径互转的秘密Blazor 的FieldIdentifier是(Model实例, 属性名)的二元组而 FluentValidation 的属性路径是字符串支持嵌套如Address.Town、集合索引如Orders[0].Total。库提供了两个方向的转换正向PropertyPathHelper.cs通过栈式深度遍历模型的非原始类型属性重建从根模型到目标字段的完整路径甚至能正确处理集合索引Orders[2]反向ToFieldIdentifier解析类似SomeProp.MyCollection[123].ChildProp的路径逐段用反射走属性、用Item索引器或数组索引访问一路追踪到最深的非空实例。这套映射代码参考了 Steve Sanderson 的经典实现还特别处理了 C# 12 集合表达式生成的IReadOnlyList等边界情况让嵌套模型如 samples/Shared/SharedModels/Person.cs 中 Person 内嵌 Address的校验路径完全一致。八、手动校验与异步校验Validate / ValidateAsync 的背后除了自动接入组件还提供了手动触发入口。Validate()本质上就是调用CurrentEditContext.Validate()走一遍整表校验而ValidateAsync()则更巧妙——它先同步触发校验再从EditContext.Properties中取出上一步挂起的AsyncValidationTask等待完成FluentValidationsValidator.csCurrentEditContext.Validate(); if (!CurrentEditContext.Properties.TryGetValue(PendingAsyncValidation, out var asyncValidationTask)) throw new InvalidOperationException(No pending ValidationResult found); await (TaskValidationResult)asyncValidationTask; return !CurrentEditContext.GetValidationMessages().Any();这样即使校验规则里有MustAsync这类异步方法见 Person.cs 中邮箱唯一性检查也能被完整等待支持 Blazor Server 下的事件处理器安全。九、性能与优雅并存的设计总结回顾整个源码Blazored.FluentValidation 的设计堪称小而美✅零侵入只做事件订阅与消息桥接不改动EditForm的任何行为✅双策略查器DI 优先、反射兜底兼顾性能与便捷✅精准校验交集选择器让字段级验证只跑必要的规则✅边界处理嵌套属性、集合索引、C# 12 集合表达式都有兼容方案。如果你想在自己项目中复刻这种把任意校验框架接入 Blazor 验证管道的能力这份源码就是最好的教科书。测试用例位于 tests/Blazored.FluentValidation.Tests/包含 BasicValidation、DirectValidation、RuleSets、FullFailureAccess 等场景配合阅读效果更佳。无论是想深入理解 Blazor 验证机制还是想学习组件库的优雅设计Blazored.FluentValidation 这 4 个文件都值得你仔细研读一遍 【免费下载链接】FluentValidationA library for using FluentValidation with Blazor项目地址: https://gitcode.com/gh_mirrors/flue/FluentValidation创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表