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

ASP.NET Core MVC:扩展RequiredAttribute的验证逻辑

问题

希望在强类型视图模型中扩展RequiredAttribute的功能。现有引用数据视图模型如下:

public class ReferenceDataViewModel
{
    public int? Id { get; set; }
    public string? Name { get; set; }
}

它被用于其他视图模型,示例:

public class MyEditViewModel
{
    // 可选:该属性可为null,其Id值也可为null
    public ReferenceDataViewModel? OptionalRef { get; set; }
    
    // 必填:该属性本身不能为null,且其Id值也不能为null
    [Required]
    public ReferenceDataViewModel? RequiredRef { get; set; }
}

针对RequiredRef,内置的RequiredAttribute仅能验证属性本身不为null,但还需要确保其Id也不为null。

已尝试通过自定义ValidationAttribute实现需求(代码如下),但为了一致性,希望直接扩展内置的RequiredAttribute:

public class ExtendedRequiredAttribute : RequiredAttribute
{
    public ExtendedRequiredAttribute() { }

    public ExtendedRequiredAttribute(RequiredAttribute attributeToCopy)
    {
        AllowEmptyStrings = attributeToCopy.AllowEmptyStrings;

        if (attributeToCopy.ErrorMessage != null)
        {
            ErrorMessage = attributeToCopy.ErrorMessage;
        }
   
        if (attributeToCopy.ErrorMessageResourceType != null)
        {
            ErrorMessageResourceName = attributeToCopy.ErrorMessageResourceName;
            ErrorMessageResourceType = attributeToCopy.ErrorMessageResourceType;
        }
    }

    public override bool IsValid(object? value)
    {
        if (value is ReferenceDataViewModel entityReference)
        {
            return entityReference?.Id != null;
        }

        return base.IsValid(value);
    }
}

之后尝试使用AttributeAdapter(该方式在ASP.NET MVC中有效)但未生效,代码如下:

public class ExtendedRequiredAttributeAdapter : AttributeAdapterBase<RequiredAttribute>
{
    public ExtendedRequiredAttributeAdapter(ExtendedRequiredAttribute attribute, IStringLocalizer? stringLocalizer)
        : base(attribute, stringLocalizer)
    { }

    public override void AddValidation(ClientModelValidationContext context)
    {
        MergeAttribute(context.Attributes, "data-val",          "true");
        MergeAttribute(context.Attributes, "data-val-required", GetErrorMessage(context));
    }

    public override string GetErrorMessage(ModelValidationContextBase validationContext)
    {
        ArgumentNullException.ThrowIfNull(validationContext);
        return GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
    }
}

public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
{
    private readonly IValidationAttributeAdapterProvider _innerProvider = new ValidationAttributeAdapterProvider();

    public IAttributeAdapter? GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer? stringLocalizer)
    {
        var type = attribute.GetType();

        if (type == typeof(RequiredAttribute))
        {
            var requiredAttribute = (RequiredAttribute)attribute;
            var extendedRequiredAttrib = new ExtendedRequiredAttribute(requiredAttribute);
            return new ExtendedRequiredAttributeAdapter(extendedRequiredAttrib, stringLocalizer);
        }

        return _innerProvider.GetAttributeAdapter(attribute, stringLocalizer);
    }
}

已在启动时注册:

services.AddSingleton<IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

请问如何正确扩展内置RequiredAttribute以实现需求?

解决方案

你的思路方向是对的,但目前实现仅覆盖了客户端验证适配,服务器端验证逻辑并未被替换。IValidationAttributeAdapterProvider仅负责将验证属性映射到客户端验证规则,服务器端验证仍由原始RequiredAttribute执行,因此扩展逻辑未生效。

要完整实现需求,需同时替换服务器端验证逻辑和客户端适配,具体步骤如下:

1. 替换服务器端验证逻辑

ASP.NET Core中,服务器端验证由ModelValidatorProvider处理,需自定义一个提供者,将所有RequiredAttribute替换为你的ExtendedRequiredAttribute:

public class ExtendedRequiredModelValidatorProvider : IModelValidatorProvider
{
    public void CreateValidators(ModelValidatorProviderContext context)
    {
        // 遍历所有已注册的验证器
        for (int i = context.Results.Count - 1; i >= 0; i--)
        {
            var validatorItem = context.Results[i];
            // 找到基于RequiredAttribute的验证器
            if (validatorItem.Validator is RequiredAttributeValidator)
            {
                // 获取原始的RequiredAttribute
                if (validatorItem.ValidatorMetadata is RequiredAttribute originalAttribute)
                {
                    // 创建扩展后的属性
                    var extendedAttribute = new ExtendedRequiredAttribute(originalAttribute);
                    // 替换验证器为基于扩展属性的RequiredAttributeValidator
                    context.Results[i] = new ValidatorItem
                    {
                        Validator = new RequiredAttributeValidator(extendedAttribute),
                        ValidatorMetadata = extendedAttribute
                    };
                }
            }
        }
    }
}

2. 注册自定义的ModelValidatorProvider

在启动类的ConfigureServices中,添加自定义验证器提供者:

services.AddControllersWithViews(options =>
{
    options.ModelValidatorProviders.Add(new ExtendedRequiredModelValidatorProvider());
});
// 保留之前注册的适配器提供者
services.AddSingleton<IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

3. 修正AttributeAdapter的泛型类型

将ExtendedRequiredAttributeAdapter的泛型参数改为ExtendedRequiredAttribute,避免潜在类型问题:

public class ExtendedRequiredAttributeAdapter : AttributeAdapterBase<ExtendedRequiredAttribute>
{
    public ExtendedRequiredAttributeAdapter(ExtendedRequiredAttribute attribute, IStringLocalizer? stringLocalizer)
        : base(attribute, stringLocalizer)
    { }

    public override void AddValidation(ClientModelValidationContext context)
    {
        MergeAttribute(context.Attributes, "data-val", "true");
        MergeAttribute(context.Attributes, "data-val-required", GetErrorMessage(context));
        // 若需客户端验证Id,添加自定义验证属性
        MergeAttribute(context.Attributes, "data-val-required-refid", "请选择有效的引用数据");
    }

    public override string GetErrorMessage(ModelValidationContextBase validationContext)
    {
        ArgumentNullException.ThrowIfNull(validationContext);
        return GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
    }
}

4. 客户端验证补充(可选)

若需客户端也验证Id不为null,添加对应的JavaScript逻辑:

$.validator.addMethod("required-refid", function (value, element, params) {
    // 假设引用数据的Id字段对应元素的name以.Id结尾
    var idElement = $(element).closest('.reference-data').find('[name$=".Id"]');
    return idElement.val() !== "" && idElement.val() !== null;
});

$.validator.unobtrusive.adapters.addBool("required-refid");

关键说明

  • IValidationAttributeAdapterProvider仅负责客户端验证规则生成,服务器端验证由ModelValidatorProvider管理的验证器执行,必须同时替换两部分。
  • 通过替换RequiredAttributeValidator的元数据为ExtendedRequiredAttribute,服务器端验证时会执行你重写的IsValid方法。
  • 修改后,所有标注[Required]的ReferenceDataViewModel类型属性,会自动验证属性本身和Id都不为null,无需替换为自定义属性,保证了一致性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 05:50:19