Blazor子组件验证疑难:复杂类型字段的ValidationMessage无法正常显示如何解决?
我懂你遇到的这个坑——Blazor的验证系统是盯着主模型里字段的完整路径来匹配错误信息的,你子组件里写For="@(() => Value.Address1)",但主模型里这个字段的实际路径是FormData.Address.Address1,两者对不上,所以ValidationMessage找不到对应的错误提示,自然就不显示了。下面给你两种靠谱的解决办法,都能兼顾组件复用性:
方法一:给子组件传递主模型的字段表达式
这种方法最直接,让子组件知道当前地址属性在主模型里的完整路径,这样ValidationMessage就能正确匹配错误了。
修改Address子组件
添加一个Expression<Func<string>>类型的参数,用来接收主模型中Address1字段的表达式:
<InputText @bind-Value=Value.Address1 @onchange="UpdateValue" /> @if (Address1Expression != null) { <ValidationMessage For="@Address1Expression" /> } @code{ [Parameter] public AddressDataModel Value { get; set; } [Parameter] public EventCallback<AddressDataModel> ValueChanged { get; set; } // 新增:接收主模型中Address1的表达式 [Parameter] public Expression<Func<string>> Address1Expression { get; set; } protected async Task UpdateValue() { await ValueChanged.InvokeAsync(Value); } }
修改父组件的调用
在父组件里传递完整的字段路径表达式给子组件:
<EditForm Model=@FormData> <FluentValidator/> <ValidationSummary/> <InputText @bind-Value=FormData.FullName /> <ValidationMessage For="@(() => FormData.FullName)"/> <!-- 传递Address1的完整表达式 --> <ComponentAddress @bind-Value=FormData.Address Address1Expression="@(() => FormData.Address.Address1)" /> <input type="submit" value="Submit" class="btn btn-primary" /> </EditForm> @code{ PersonDataModel FormData = new PersonDataModel(); }
这样子组件里的ValidationMessage就会用主模型里的完整路径FormData.Address.Address1去匹配FluentValidation生成的错误,自然就能显示提示了。如果你的地址组件有多个需要验证的字段(比如Address2、City等),可以给每个字段都加对应的Expression参数,或者封装一个包含所有字段表达式的对象。
方法二:自定义适配子组件的ValidationMessage
如果不想每次调用子组件都传一堆表达式,可以自定义一个能处理子组件字段路径映射的ValidationMessage组件。核心思路是重写ValidationMessage的逻辑,把子组件里的Value.Xxx路径替换成主模型里的实际路径。
不过这种方法稍微复杂一点,适合需要大量复用地址组件的场景。这里给你简化版的实现思路:
- 创建一个
ChildValidationMessage组件,继承自Blazor自带的ValidationMessage<TValue> - 添加一个
ParentExpression参数,用来指定主模型中父属性(比如FormData.Address)的表达式 - 在组件内部,把子组件的字段表达式(比如
Value.Address1)和父表达式组合成完整的路径,再去匹配错误
示例代码大概是这样:
@typeparam TValue @inherits ValidationMessage<TValue> @code{ [Parameter] public Expression<Func<object>> ParentExpression { get; set; } protected override void OnParametersSet() { if (ParentExpression != null) { // 解析父表达式的路径,和子字段路径组合成完整路径 var parentPath = FieldIdentifier.Create(ParentExpression).FieldName; var childPath = FieldIdentifier.Create(For).FieldName.Replace("Value.", ""); // 替换当前组件的FieldIdentifier为完整路径 FieldIdentifier = new FieldIdentifier(EditContext.Model, $"{parentPath}.{childPath}"); } base.OnParametersSet(); } }
然后在地址子组件里使用这个自定义组件:
<InputText @bind-Value=Value.Address1 @onchange="UpdateValue" /> <ChildValidationMessage For="@(() => Value.Address1)" ParentExpression="@ParentExpression" /> @code{ [Parameter] public AddressDataModel Value { get; set; } [Parameter] public EventCallback<AddressDataModel> ValueChanged { get; set; } [Parameter] public Expression<Func<object>> ParentExpression { get; set; } protected async Task UpdateValue() { await ValueChanged.InvokeAsync(Value); } }
父组件调用时只需要传递父属性的表达式:
<ComponentAddress @bind-Value=FormData.Address ParentExpression="@(() => FormData.Address)" />
这种方式更灵活,适合子组件有多个验证字段的情况,不用每个字段都传表达式。
额外说明
你的FluentValidation配置是没问题的,RuleFor(r => r.Address.Address1).NotEmpty()已经正确指向了主模型里的完整字段路径,所以ValidationSummary能显示所有错误——因为Summary是遍历所有错误,而ValidationMessage是精确匹配字段路径的。
内容的提问来源于stack exchange,提问作者user3807918

