You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 18:34:55