如何在Blazor EditForm自定义验证处理程序中设置验证状态
问题根因
你遇到的异步验证未完成就触发OnValidSubmit的核心原因,是Blazor原生表单验证管线是同步执行设计的:
- 你给
OnValidationRequested绑定的是async void签名的方法,这类方法没有返回可等待的Task对象 - 管线触发完验证事件后,碰到
await就会立刻把控制权交回调用方,直接检查当前ValidationMessageStore里的错误信息 - 此时你的数据库查询还没返回结果,错误消息还没写入存储,管线会默认判定验证通过,直接触发提交逻辑
推荐解决方案
两种方案都可以完全避免重复写校验逻辑,你可以根据自己的代码结构选择:
方案1:最小改动版,用OnSubmit接管提交流程
不需要额外封装组件,只要替换掉原来的OnValidSubmit绑定,手动控制验证执行顺序即可:
- 把EditForm的提交事件从
OnValidSubmit改成OnSubmit<EditForm EditContext="editContext" OnSubmit="HandleFormSubmit"> <DataAnnotationsValidator /> <div class="form-item"> <label>标题</label> <InputText @bind-Value="myModel.Title" /> <ValidationMessage For="() => myModel.Title" /> </div> <ValidationSummary /> <button type="submit">提交</button> </EditForm> - 把原来的异步验证逻辑抽成可等待的独立方法,在提交逻辑中先等所有验证跑完再判断结果
private EditContext editContext; private ValidationMessageStore validationMessageStore; private AssignmentModel assignment = new(); private MyModel myModel = new(); protected override void OnInitialized() { editContext = new EditContext(assignment); validationMessageStore = new ValidationMessageStore(editContext); } /// <summary> /// 所有自定义异步验证逻辑统一写在这里,只需要实现一次 /// </summary> private async Task RunCustomValidationAsync() { validationMessageStore.Clear(); // 等待数据库查询完成 bool titleExists = await myService.IsPresent(myModel.Title); if (titleExists) { validationMessageStore.Add(() => myModel.Title, "该标题已被使用"); } editContext.NotifyValidationStateChanged(); } private async Task HandleFormSubmit(EditContext context) { // 先执行原生数据注解验证 bool baseValidationPassed = context.Validate(); // 等待所有自定义异步验证执行完成 await RunCustomValidationAsync(); // 最终校验是否存在错误 bool allValidationPassed = !context.GetValidationMessages().Any(); if (!allValidationPassed) { // 验证不通过直接返回,页面会自动显示错误消息 return; } // 验证通过,直接写提交逻辑即可,不需要重复校验 await myService.SaveData(assignment); // 后续跳转、成功提示逻辑写在这里 }
方案2:封装通用异步验证组件(适合多表单复用场景)
如果项目里有多个表单需要写异步验证,可以封装一个通用的异步验证组件,一劳永逸:
- 新建
AsyncValidation.razor组件@implements IDisposable @using System.Linq.Expressions <CascadingValue Value="this" IsFixed="true"> @ChildContent </CascadingValue> @code { [CascadingParameter] private EditContext CurrentEditContext { get; set; } = default!; [Parameter] public RenderFragment? ChildContent { get; set; } private ValidationMessageStore _messageStore = default!; private Func<Task>? _asyncValidators; private bool _isValidating; protected override void OnInitialized() { _messageStore = new ValidationMessageStore(CurrentEditContext); CurrentEditContext.OnValidationRequested += HandleValidationRequest; CurrentEditContext.OnFieldChanged += HandleFieldChange; } /// <summary> /// 注册异步验证规则 /// </summary> public void AddValidator(Func<Task> validateAction) { _asyncValidators += validateAction; } /// <summary> /// 给指定字段添加验证错误 /// </summary> public void AddFieldError<T>(Expression<Func<T>> fieldSelector, string errorText) { _messageStore.Add(fieldSelector, errorText); } private async void HandleValidationRequest(object? sender, ValidationRequestedEventArgs e) { if (_isValidating) return; _isValidating = true; _messageStore.Clear(); if (_asyncValidators != null) { // 等待所有注册的异步验证执行完成 await _asyncValidators.Invoke(); } CurrentEditContext.NotifyValidationStateChanged(); _isValidating = false; } private void HandleFieldChange(object? sender, FieldChangedEventArgs e) { _messageStore.Clear(e.FieldIdentifier); CurrentEditContext.NotifyValidationStateChanged(); } public void Dispose() { CurrentEditContext.OnValidationRequested -= HandleValidationRequest; CurrentEditContext.OnFieldChanged -= HandleFieldChange; } } - 在表单中使用该组件,注册验证规则即可,不需要改动原有
OnValidSubmit逻辑<EditForm Model="assignment" OnValidSubmit="HandleValidSubmit"> <DataAnnotationsValidator /> <AsyncValidation @ref="asyncValidator"> <div class="form-item"> <label>标题</label> <InputText @bind-Value="myModel.Title" /> <ValidationMessage For="() => myModel.Title" /> </div> <ValidationSummary /> <button type="submit">提交</button> </AsyncValidation> </EditForm> - 页面代码里注册验证规则
private AsyncValidation asyncValidator = default!; protected override void OnAfterRender(bool firstRender) { if (firstRender) { asyncValidator.AddValidator(async () => { bool titleExists = await myService.IsPresent(myModel.Title); if (titleExists) { asyncValidator.AddFieldError(() => myModel.Title, "该标题已被使用"); } }); } } private async Task HandleValidSubmit() { // 到这里所有同步、异步验证都已经完成,直接写提交逻辑即可 await myService.SaveData(assignment); }
注意事项
不要尝试通过给async void方法加ConfigureAwait(false)或者其他线程配置解决问题,本质上async void是不可等待的,只要用这种写法就没法保证异步逻辑执行完成后再触发提交判定。
内容的提问来源于stack exchange,提问作者Qrt
相关产品推荐
相关产品推荐

