客户端Blazor自定义RequiredIf特性客户端验证失效如何解决
Blazor客户端自定义RequiredIf验证失效修复
现有代码结构
- 父页面Blazor组件代码:
<EditForm Model="@MyModel" OnValidSubmit="@HandleValidSubmit"> <DataAnnotationsValidator /> ... <MyComponent DataModel="@MyModel.Transaction" /> </EditForm> @code { private void HandleValidSubmit() { } }
- 子组件
MyComponent.razor代码:
... <InputText @bind-Value="@DataModel.InputValue" disabled="@DataModel.TextboxDisabled" /> @code { [Parameter] public MyModel DataModel { get; set; } }
注:原代码中InputText绑定的
@Model和参数名DataModel不匹配,会直接编译报错,上述代码已修正绑定路径。
- 模型类
MyModel.cs定义:
[RequiredIf(nameof(TextBoxEnabled), true)] public string InputValue { get; set; } public bool TextBoxEnabled { get; set; } public bool TextBoxDisabled => !TextBoxEnabled;
- 自定义验证特性
RequiredIfAttribute.cs实现:
public sealed class RequiredIfAttribute : ValidationAttribute { private readonly string propertyName; private readonly object? isValue; private readonly bool inverse; /// <summary> /// Initializes a new instance of the <see cref="RequiredIfAttribute"/> class. /// </summary> /// <param name="propertyName">Name of the depending property.</param> /// <param name="isValue">Required value. If <see cref="propertyName"/> is <see cref="isValue"/> then the property is not required.</param> /// <param name="inverse">If set to true, the value is not required when <see cref="propertyName"/> is /// not <see cref="isValue"/>.</param> public RequiredIfAttribute(string propertyName, object? isValue, bool inverse = false) { this.propertyName = propertyName ?? throw new ArgumentNullException(nameof(propertyName)); this.isValue = isValue; this.inverse = inverse; } /// <inheritdoc /> public override string FormatErrorMessage(string name) { var inverseString = !inverse ? string.Empty : "not "; var errorMessage = $"Property '{name}' is required when '{propertyName}' is {inverseString}'{isValue}'"; return ErrorMessage ?? errorMessage; } protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) { var isRequired = IsRequired(validationContext, propertyName, isValue, inverse); if (!isRequired) { return ValidationResult.Success; } var validationResult = value == null ? new ValidationResult(FormatErrorMessage(validationContext.DisplayName)) : ValidationResult.Success; return validationResult; } private static bool IsRequired( ValidationContext? validationContext, string propertyName, object? requiredValue, bool invert) { ArgumentNullException.ThrowIfNull(validationContext); ArgumentNullException.ThrowIfNull(propertyName); var owningType = validationContext.ObjectType; var property = owningType.GetProperty(propertyName); if (property == null) { throw new NotSupportedException($"Can't find {propertyName} on searched type: {owningType.Name}"); } var requiredIfTypeActualValue = property.GetValue(validationContext.ObjectInstance); if (requiredIfTypeActualValue == null && requiredValue != null) { return false; } if (!invert) { return requiredIfTypeActualValue == null || requiredIfTypeActualValue.Equals(requiredValue); } return requiredIfTypeActualValue == null || !requiredIfTypeActualValue.Equals(requiredValue); } }
问题现象
当文本框切换为启用状态、输入内容为空时点击提交按钮,HandleValidSubmit方法仍会被执行;预期此时基于TextBoxEnabled标识的RequiredIf验证规则应判定验证失败,阻止提交方法执行。
失效原因
- 空值判断逻辑漏洞:现有RequiredIf特性仅判定
null为无效值,而Blazor的InputText组件绑定空输入时返回的是空字符串"",不属于null,会直接判定为验证通过。 - 验证触发逻辑限制:内置
DataAnnotationsValidator仅在字段自身值变更、表单首次提交时触发对应字段的验证,切换TextBoxEnabled状态时,InputValue字段值未发生变化,不会自动重新执行验证规则,会直接复用之前缓存的验证通过结果。 - 子组件绑定笔误:原代码子组件内InputText绑定的对象名和传入的Parameter参数名不匹配,会导致编译失败或绑定不生效。
修复方案
- 修复RequiredIf特性的空值判断逻辑
找到IsValid方法内的验证结果判断代码,替换为以下逻辑,将空字符串、全空格字符串都纳入无效值范围:
// 替换原有value == null的判断逻辑 if (value == null || (value is string strVal && string.IsNullOrWhiteSpace(strVal))) { return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } return ValidationResult.Success;
- 处理关联属性变更的重验证触发
两种实现方式二选一即可:
- 方式一:全局重验证。在
TextBoxEnabled属性的set方法中,或者切换文本框启用状态的交互逻辑中,调用EditContext的Validate方法全量重跑表单验证。首先需要在父页面持有EditContext实例:
<EditForm EditContext="_editContext" OnValidSubmit="@HandleValidSubmit"> <DataAnnotationsValidator /> ... <MyComponent DataModel="@MyModel.Transaction" /> </EditForm> @code { private EditContext _editContext; private MyModel MyModel = new(); protected override void OnInitialized() { _editContext = new EditContext(MyModel); } // 切换文本框状态的方法 private void ToggleTextboxState(bool enabled) { MyModel.Transaction.TextBoxEnabled = enabled; // 触发全表单验证 _editContext.Validate(); } private void HandleValidSubmit() { // 提交逻辑 } }
- 方式二:单字段重验证。如果不需要全量验证,仅需在TextBoxEnabled变更时通知验证系统重新校验InputValue字段即可,替换上述ToggleTextboxState方法内的
_editContext.Validate()为:
_editContext.NotifyFieldChanged(_editContext.Field(nameof(MyModel.InputValue)));
- 修正子组件绑定
将子组件内InputText的绑定路径从@Model.InputValue改为@DataModel.InputValue,和Parameter参数名保持一致。
内容的提问来源于stack exchange,提问作者Snake Eyes
相关产品推荐
相关产品推荐

