Blazor结合FluentValidation如何清除跨属性验证提示?
解决Blazor EditForm中时间输入验证提示残留问题
问题背景
你使用Blazor的EditForm绑定OutputModel的StartProxy和EndProxy属性实现时间输入,通过FluentValidation定义了4个规则集:
- 校验
StartProxy格式 - 校验
EndProxy格式 - 校验开始时间早于结束时间(提示绑定到开始输入框)
- 校验结束时间晚于开始时间(提示绑定到结束输入框)
当前问题:修改结束时间使时间顺序合法后,开始输入框的旧错误提示未自动清除。
核心原因
当修改EndProxy时,仅触发了结束时间相关的验证规则,但开始输入框上的错误是绑定在StartProxy属性上的,未被主动清除或重新验证,导致旧提示残留。
解决方案
方案一:手动清除目标属性的验证消息
在处理每个输入变更时,主动清除另一个属性的时间相关错误,再执行当前属性的验证逻辑:
<EditForm EditContext="@EditContext"> <div> <label>开始时间:</label> <InputText @bind-Value="Model.StartProxy" @onchange="HandleStartChange" /> <ValidationMessage For="@(() => Model.StartProxy)" /> </div> <div> <label>结束时间:</label> <InputText @bind-Value="Model.EndProxy" @onchange="HandleEndChange" /> <ValidationMessage For="@(() => Model.EndProxy)" /> </div> </EditForm> @code { [Inject] private IValidator<OutputModel> Validator { get; set; } private OutputModel Model = new(); private EditContext EditContext; private ValidationMessageStore ValidationStore; protected override void OnInitialized() { EditContext = new EditContext(Model); ValidationStore = new ValidationMessageStore(EditContext); } private async Task HandleStartChange() { // 清除结束时间的时间顺序错误提示 ValidationStore.Clear(() => Model.EndProxy); // 验证开始时间格式 var formatResult = await Validator.ValidateAsync(EditContext, Model, ruleSet: "StartFormat"); if (!formatResult.IsValid) { ValidationStore.Add(() => Model.StartProxy, formatResult.Errors.Select(e => e.ErrorMessage)); } else { ValidationStore.Clear(() => Model.StartProxy); // 验证开始时间早于结束时间 var timeResult = await Validator.ValidateAsync(EditContext, Model, ruleSet: "StartBeforeEnd"); if (!timeResult.IsValid) { ValidationStore.Add(() => Model.StartProxy, timeResult.Errors.Select(e => e.ErrorMessage)); } } EditContext.NotifyValidationStateChanged(); } private async Task HandleEndChange() { // 清除开始时间的时间顺序错误提示 ValidationStore.Clear(() => Model.StartProxy); // 验证结束时间格式 var formatResult = await Validator.ValidateAsync(EditContext, Model, ruleSet: "EndFormat"); if (!formatResult.IsValid) { ValidationStore.Add(() => Model.EndProxy, formatResult.Errors.Select(e => e.ErrorMessage)); } else { ValidationStore.Clear(() => Model.EndProxy); // 验证结束时间晚于开始时间 var timeResult = await Validator.ValidateAsync(EditContext, Model, ruleSet: "EndAfterStart"); if (!timeResult.IsValid) { ValidationStore.Add(() => Model.EndProxy, timeResult.Errors.Select(e => e.ErrorMessage)); } } EditContext.NotifyValidationStateChanged(); } }
方案二:统一验证逻辑,双向触发全量校验
优化FluentValidation规则集,将时间顺序验证合并为一个规则集,在任意输入变更时,重新验证所有格式规则和时间顺序规则,同时清除所有旧错误:
首先调整Validator定义
public class OutputModelValidator : AbstractValidator<OutputModel> { public OutputModelValidator() { RuleSet("StartFormat", () => { RuleFor(x => x.StartProxy) .Matches(@"^\d{2}:\d{2}\.\d{3}$") .WithMessage("开始时间格式错误,需为HH:mm.sss"); }); RuleSet("EndFormat", () => { RuleFor(x => x.EndProxy) .Matches(@"^\d{2}:\d{2}\.\d{3}$") .WithMessage("结束时间格式错误,需为HH:mm.sss"); }); RuleSet("TimeOrder", () => { RuleFor(x => x.StartProxy) .Must((model, start) => DateTime.ParseExact(start, "HH:mm.fff", CultureInfo.InvariantCulture) < DateTime.ParseExact(model.EndProxy, "HH:mm.fff", CultureInfo.InvariantCulture)) .WithMessage("开始时间不能晚于结束时间") .When(x => !string.IsNullOrEmpty(x.StartProxy) && !string.IsNullOrEmpty(x.EndProxy)); RuleFor(x => x.EndProxy) .Must((model, end) => DateTime.ParseExact(end, "HH:mm.fff", CultureInfo.InvariantCulture) > DateTime.ParseExact(model.StartProxy, "HH:mm.fff", CultureInfo.InvariantCulture)) .WithMessage("结束时间不能早于开始时间") .When(x => !string.IsNullOrEmpty(x.StartProxy) && !string.IsNullOrEmpty(x.EndProxy)); }); } }
然后调整组件逻辑
<EditForm EditContext="@EditContext"> <div> <label>开始时间:</label> <InputText @bind-Value="Model.StartProxy" @onchange="HandleTimeInputChange" /> <ValidationMessage For="@(() => Model.StartProxy)" /> </div> <div> <label>结束时间:</label> <InputText @bind-Value="Model.EndProxy" @onchange="HandleTimeInputChange" /> <ValidationMessage For="@(() => Model.EndProxy)" /> </div> </EditForm> @code { [Inject] private IValidator<OutputModel> Validator { get; set; } private OutputModel Model = new(); private EditContext EditContext; private ValidationMessageStore ValidationStore; protected override void OnInitialized() { EditContext = new EditContext(Model); ValidationStore = new ValidationMessageStore(EditContext); } private async Task HandleTimeInputChange() { // 清除两个属性的所有旧验证消息 ValidationStore.Clear(() => Model.StartProxy); ValidationStore.Clear(() => Model.EndProxy); // 验证开始时间格式 var startFormatResult = await Validator.ValidateAsync(EditContext, Model, ruleSet: "StartFormat"); if (!startFormatResult.IsValid) { ValidationStore.Add(() => Model.StartProxy, startFormatResult.Errors.Select(e => e.ErrorMessage)); } // 验证结束时间格式 var endFormatResult = await Validator.ValidateAsync(EditContext, Model, ruleSet: "EndFormat"); if (!endFormatResult.IsValid) { ValidationStore.Add(() => Model.EndProxy, endFormatResult.Errors.Select(e => e.ErrorMessage)); } // 格式都合法时,验证时间顺序 if (startFormatResult.IsValid && endFormatResult.IsValid) { var timeOrderResult = await Validator.ValidateAsync(EditContext, Model, ruleSet: "TimeOrder"); foreach (var error in timeOrderResult.Errors) { ValidationStore.Add(EditContext.Field(error.PropertyName), error.ErrorMessage); } } EditContext.NotifyValidationStateChanged(); } }
方案对比
- 方案一:粒度更细,每个输入变更仅处理相关验证,适合需要单独控制验证流程的场景,但代码冗余度较高。
- 方案二:逻辑统一,一次处理所有验证,代码更简洁易维护,且能确保所有提示都是最新状态,避免残留问题。
内容的提问来源于stack exchange,提问作者Herminator
相关产品推荐
相关产品推荐

