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

Blazor中Fluent Validator适配动态Min/Max并在组件值更新时触发验证

解决方案:Blazor Server 中动态参数的 FluentValidation 实时验证

针对你提出的两个核心问题,以下提供两种可行方案,优先推荐组件内集成验证的方式,更适配动态参数场景:


方案1:组件内集成 FluentValidation(推荐)

将验证逻辑直接封装在 DoubleNumericOnly 组件内部,自动识别当前实例的动态 Min/Max 参数,且在值更新时立即触发验证,无需依赖父级模型规则。

组件完整代码

@using Blazored.FluentValidation
@using FluentValidation
@using FluentValidation.Validators
@implements IDisposable

<input type="text" @onchange="OnValueChanged" value="@Data" />
@if (CurrentEditContext != null)
{
    <ValidationMessage For="@_fieldIdentifier" />
}

@code {
    [Parameter]
    public double Data { get; set; }

    [Parameter]
    public EventCallback<double> DataChanged { get; set; }

    [Parameter]
    public double? Min { get; set; }

    [Parameter]
    public double? Max { get; set; }

    [CascadingParameter]
    private EditContext? CurrentEditContext { get; set; }

    private ValidationMessageStore? _messageStore;
    private IValidator<double>? _validator;
    private FieldIdentifier _fieldIdentifier;

    protected override void OnInitialized()
    {
        if (CurrentEditContext == null)
            throw new InvalidOperationException("DoubleNumericOnly 组件必须在 EditForm 内部使用");

        _fieldIdentifier = CurrentEditContext.Field(nameof(Data));
        _messageStore = new ValidationMessageStore(CurrentEditContext);
        _validator = new InlineValidator<double>();
        
        UpdateValidationRules();
        CurrentEditContext.OnFieldChanged += HandleFieldChanged;
    }

    protected override void OnParametersSet()
    {
        UpdateValidationRules();
        ValidateCurrentValue();
    }

    private void UpdateValidationRules()
    {
        _validator?.Rules.Clear();

        if (Min.HasValue)
        {
            _validator?.RuleFor(x => x)
                .GreaterThanOrEqualTo(Min.Value)
                .WithMessage($"数值不能小于 {Min.Value}");
        }

        if (Max.HasValue)
        {
            _validator?.RuleFor(x => x)
                .LessThanOrEqualTo(Max.Value)
                .WithMessage($"数值不能大于 {Max.Value}");
        }
    }

    private async Task OnValueChanged(ChangeEventArgs e)
    {
        if (double.TryParse(e.Value?.ToString(), out var newValue))
        {
            await DataChanged.InvokeAsync(newValue);
            ValidateCurrentValue();
        }
    }

    private void ValidateCurrentValue()
    {
        if (_validator == null || CurrentEditContext == null || _messageStore == null) return;

        _messageStore.Clear(_fieldIdentifier);
        var validationContext = new ValidationContext<double>(Data);
        var validationResult = _validator.Validate(validationContext);

        foreach (var error in validationResult.Errors)
        {
            _messageStore.Add(_fieldIdentifier, error.ErrorMessage);
        }

        CurrentEditContext.NotifyValidationStateChanged();
    }

    private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        if (e.FieldIdentifier.Equals(_fieldIdentifier))
        {
            ValidateCurrentValue();
        }
    }

    public void Dispose()
    {
        if (CurrentEditContext != null)
        {
            CurrentEditContext.OnFieldChanged -= HandleFieldChanged;
        }
    }
}

核心特性

  • 验证逻辑与组件绑定,复用性强,每个实例自动适配自身的 Min/Max 参数
  • 值更新时立即触发验证,无需等待表单提交
  • 验证消息直接显示在组件下方,用户反馈更直观
  • 自动响应 Min/Max 参数的动态变更

方案2:父级传递动态参数到 FluentValidation 上下文

若需在父级统一管理验证规则,可通过 ValidationContext 的 RootContextData 传递每个属性的动态 Min/Max 参数。

步骤1:模型验证器

public class MyModelValidator : AbstractValidator<MyModel>
{
    public MyModelValidator()
    {
        RuleFor(x => x.DAmount)
            .Custom((value, context) =>
            {
                if (context.RootContextData.TryGetValue("DAmount_Min", out var minObj) 
                    && minObj is double min 
                    && value < min)
                {
                    context.AddFailure($"DAmount 不能小于 {min}");
                }

                if (context.RootContextData.TryGetValue("DAmount_Max", out var maxObj) 
                    && maxObj is double max 
                    && value > max)
                {
                    context.AddFailure($"DAmount 不能大于 {max}");
                }
            });

        RuleFor(x => x.AnotherDAmount)
            .Custom((value, context) =>
            {
                if (context.RootContextData.TryGetValue("AnotherDAmount_Min", out var minObj) 
                    && minObj is double min 
                    && value < min)
                {
                    context.AddFailure($"AnotherDAmount 不能小于 {min}");
                }

                if (context.RootContextData.TryGetValue("AnotherDAmount_Max", out var maxObj) 
                    && maxObj is double max 
                    && value > max)
                {
                    context.AddFailure($"AnotherDAmount 不能大于 {max}");
                }
            });
    }
}

步骤2:父页面代码

<EditForm Model="@MyModel" OnValidSubmit="HandleSubmit">
    <Blazored.FluentValidation.FluentValidationValidator @ref="_validator" />
    
    <DoubleNumericOnly @bind-Data="@MyModel.DAmount" Min="50" Max="4000" DataExpression="() => MyModel.DAmount" />
    <ValidationMessage For="() => MyModel.DAmount" />

    <DoubleNumericOnly @bind-Data="@MyModel.AnotherDAmount" Min="100" Max="400" DataExpression="() => MyModel.AnotherDAmount" />
    <ValidationMessage For="() => MyModel.AnotherDAmount" />

    <button type="submit">提交</button>
</EditForm>

@code {
    private MyModel MyModel = new();
    private FluentValidationValidator? _validator;

    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender && _validator?.EditContext != null)
        {
            // 为每个属性绑定动态参数
            _validator.EditContext.RootContextData["DAmount_Min"] = 50;
            _validator.EditContext.RootContextData["DAmount_Max"] = 4000;
            _validator.EditContext.RootContextData["AnotherDAmount_Min"] = 100;
            _validator.EditContext.RootContextData["AnotherDAmount_Max"] = 400;
            
            // 触发初始验证
            _validator.EditContext.Validate();
        }
    }

    private void HandleSubmit()
    {
        // 提交逻辑
    }
}

组件补充(实时验证触发)

在 DoubleNumericOnly 组件中添加以下代码,实现值更新时触发验证:

[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }

[Parameter]
public Expression<Func<double>>? DataExpression { get; set; }

private FieldIdentifier? _fieldIdentifier;

protected override void OnInitialized()
{
    if (DataExpression != null && CurrentEditContext != null)
    {
        _fieldIdentifier = FieldIdentifier.Create(DataExpression);
    }
}

private async Task OnValueChanged(ChangeEventArgs e)
{
    if (double.TryParse(e.Value?.ToString(), out var newValue))
    {
        await DataChanged.InvokeAsync(newValue);
        if (_fieldIdentifier != null && CurrentEditContext != null)
        {
            CurrentEditContext.ValidateField(_fieldIdentifier.Value);
        }
    }
}

内容的提问来源于stack exchange,提问作者bruceiow

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 15:19:57