使用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
相关产品推荐
相关产品推荐

