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

使用Fluent Validation API与Mud Blazor时出现异常验证错误

问题描述

我给AssessmentModel配置了Fluent Validation的NotEqual规则,要求Owner、DelegateOwner、Reviewer这三个UserModel类型的字段不能相互重复。当字段值相同时,验证器能正常显示自定义错误信息;但当字段值不同时,却出现了异常的.Email错误提示,这不符合预期。

验证器类代码

using FluentValidation;
using Data.Models.DBModels;

namespace Compyl.WebApp.Validators
{
    public class AssessmentModelFluentValidator : AbstractValidator<AssessmentModel>
    {
        public AssessmentModelFluentValidator()
        {
            RuleLevelCascadeMode = CascadeMode.Stop;
            
            RuleFor(x => x.Owner)
                .NotEqual(x=> x.DelegateOwner)
                .WithMessage("Owner cannot be the same as Delegate Owner")
                .NotEqual(X => X.Reviewer)
                .WithMessage("Owner cannot be the same as Reviewer");

            RuleFor(x => x.DelegateOwner)       
                .NotEqual(x => x.Owner)
                .WithMessage("Delegate Owner cannot be the same as Owner")
                .NotEqual(X => X.Reviewer)
                .WithMessage("Delegate Owner cannot be the same as Reviwer");

            RuleFor(x => x.Reviewer)
                .NotEqual(x => x.Owner)
                .WithMessage("Reviewer cannot be the same as Owner")
                .NotEqual(X => X.DelegateOwner)
                .WithMessage("Reviewer cannot be the same as Delegate Owner");
        }

        public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
        {
            var result = await ValidateAsync(ValidationContext<AssessmentModel>.CreateWithOptions((AssessmentModel)model, x => x.IncludeProperties(propertyName)));
            if (result.IsValid)
                return Array.Empty<string>();
            return result.Errors.Select(e => e.ErrorMessage);
        };
    }
}

表单部分代码

<MudForm  @ref="form" Model="@assessment" @bind-IsValid="@success"  Validation="@(assessmentValidator.ValidateValue)" @bind-Errors="@errors">
  <MudCard Elevation="0">
     <MudCardHeader>
         <CardHeaderContent>
             <MudText Typo="Typo.h6">@assessment.Name</MudText>
              <MudText>@assessment.Description</MudText>
          </CardHeaderContent>
                    
       </MudCardHeader>
       <MudCardContent>
        <MudTabs Elevation="2" Rounded="true" ApplyEffectsToContainer="true" PanelClass="pa-6">
            <MudTabPanel Text="Assessment Details">
                  <MudTextField  Immediate="true" For="@(() => assessment.Name)"  Variant="Variant.Outlined" Disabled="@(!canEdit)" RequiredError="Assessment Name is Required!"  Required Label="Name" @bind-Value="assessment.Name" />
                   <MudTextField  For="@(() => assessment.Description)"  Variant="Variant.Outlined"  Disabled="@(!canEdit)" Lines="4" Label="Description" @bind-Value="assessment.Description" />
                   <MudAutocomplete   Immediate="true" For="@(() => assessment.Owner)" Variant="Variant.Outlined"  Disabled="@(!canEdit)" T="UserModel"  @bind-Value="assessment.Owner" RequiredError="Assessment Owner is Required!" Required Label="Owner"   ToStringFunc="@(e=> e==null? null : $"{e.Email}")" SearchFunc="@SearchUsers" />
                 <MudAutocomplete  Immediate="true" For="@(() => assessment.DelegateOwner)" Variant="Variant.Outlined" Disabled="@(!canEdit)" T="UserModel"  @bind-Value="assessment.DelegateOwner" Label="Delegate Owner" ToStringFunc="@(e=> e==null? null : $"{e.Email}")" SearchFunc="@SearchUsers" />
                  <MudAutocomplete  Immediate="true" For="@(() => assessment.Reviewer)" Variant="Variant.Outlined" Disabled="@(!canEdit)" T="UserModel" @bind-Value="assessment.Reviewer" RequiredError="Assessment Reviewer is Required!" Required Label="Reviewer"  ToStringFunc="@(e=> e==null? null : $"{e.Email}")" SearchFunc="@SearchUsers" />
解决方案

出现异常.Email错误提示的核心原因是Fluent Validation默认会递归验证复杂类型的内部属性,当验证Owner这类UserModel对象时,它会自动去验证UserModel的Email属性(如果UserModel有对应的验证规则),导致无关错误被抛出。

1. 基于唯一标识比较(推荐)

直接比较UserModel的唯一标识(如Id),避免直接比较整个对象触发递归验证,同时保证相等判断的准确性:

public AssessmentModelFluentValidator()
{
    RuleLevelCascadeMode = CascadeMode.Stop;

    RuleFor(x => x.Owner)
        .NotNull().WithMessage("Owner is required")
        .DependentRules(() =>
        {
            RuleFor(x => x.Owner.Id)
                .NotEqual(x => x.DelegateOwner?.Id).WithMessage("Owner cannot be the same as Delegate Owner")
                .NotEqual(x => x.Reviewer?.Id).WithMessage("Owner cannot be the same as Reviewer");
        });

    RuleFor(x => x.DelegateOwner)
        .DependentRules(() =>
        {
            RuleFor(x => x.DelegateOwner?.Id)
                .NotEqual(x => x.Owner.Id).WithMessage("Delegate Owner cannot be the same as Owner")
                .NotEqual(x => x.Reviewer?.Id).WithMessage("Delegate Owner cannot be the same as Reviewer");
        });

    RuleFor(x => x.Reviewer)
        .NotNull().WithMessage("Reviewer is required")
        .DependentRules(() =>
        {
            RuleFor(x => x.Reviewer.Id)
                .NotEqual(x => x.Owner.Id).WithMessage("Reviewer cannot be the same as Owner")
                .NotEqual(x => x.DelegateOwner?.Id).WithMessage("Reviewer cannot be the same as Delegate Owner");
        });
}

2. 关闭当前验证上下文的递归验证

如果需要保留对象比较逻辑,可在验证上下文里关闭子属性验证:

public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
    var result = await ValidateAsync(ValidationContext<AssessmentModel>.CreateWithOptions((AssessmentModel)model, x => 
        x.IncludeProperties(propertyName)
         .ValidateChildProperties = false)); // 关闭子属性验证
    if (result.IsValid)
        return Array.Empty<string>();
    return result.Errors.Select(e => e.ErrorMessage);
};

3. 全局关闭递归验证(谨慎使用)

若项目中所有验证器都不需要递归验证复杂类型,可全局配置关闭:

public AssessmentModelFluentValidator()
{
    RuleLevelCascadeMode = CascadeMode.Stop;
    // 全局关闭子属性递归验证
    ValidatorOptions.Global.ValidateChildProperties = false;

    // 保留原有对象比较规则
    RuleFor(x => x.Owner)
        .NotEqual(x=> x.DelegateOwner).WithMessage("Owner cannot be the same as Delegate Owner")
        .NotEqual(x => x.Reviewer).WithMessage("Owner cannot be the same as Reviewer");
    // ... 其他规则
}

注意:全局配置会影响所有验证器,若其他场景需要验证复杂类型内部属性,不建议使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 00:54:12