ASP.NET Core MVC:扩展RequiredAttribute的工作方式

问题描述 投票:0回答:1

我想扩展

RequiredAttribute
如何与强类型视图模型一起工作。例如,我有一个参考数据视图模型,如下所示:

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

这可以用于其他视图模型,如下所示:

public class MyEditViewModel
{
    // Optional: this property can be null or this property's Id value can be null
    public ReferenceViewModel? OptionalRef { get; set; }
    
    // Required: this property should not be null nor should this property's Id value
    [Required]
    public ReferenceViewModel? RequiredRef { get; set; }
}

使用

RequiredRef
属性本身不应该是
null
(由
RequiredAttribute
处理),而且
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>();
c# asp.net-core asp.net-core-mvc
1个回答
0
投票

我通过采取稍微不同的路线解决了这个问题。本质上,我通过创建一个

IModelValidator
来利用 MVC 核心验证基础设施,然后通过
IModelValidatorProvider
将其发挥作用,然后在
MvcOptions
中注册该提供程序。这是代码:

public class ReferenceDataViewModelRequiredValidator : IModelValidator
{
    public IEnumerable<ModelValidationResult> Validate(ModelValidationContext context)
    {
        var model = context.Model as ReferenceDataViewModel;
        if (model == null || model.Id.HasValue == false)
        {
            var message = GetErrorMessage(context.ModelMetadata);
            yield return new ModelValidationResult("", message);
        }
    }

    private string GetErrorMessage(ModelMetadata metadata)
    {
        string? message = null;

        var parent    = metadata.ContainerType;
        var modelName = metadata.PropertyName;
        if (parent != null && !string.IsNullOrWhiteSpace(modelName))
        {
            var modelProperty  = parent.GetProperty(modelName);
            var requiredAttrib = modelProperty?.GetCustomAttribute<RequiredAttribute>();
            if (requiredAttrib != null)
            {
                message = requiredAttrib.ErrorMessage;
                if (string.IsNullOrWhiteSpace(message))
                {
                    message = requiredAttrib.FormatErrorMessage(modelName);
                }
            }
        }

        if (string.IsNullOrWhiteSpace(message))
        {
            message = $"{metadata.GetDisplayName()} is required.";
        }
        return message;
    }

}

这可以通过

ModelValidatorProvider
:

集成到 MVC 的验证中
public class MyModelValidatorProvider : IModelValidatorProvider
{
    public void CreateValidators(ModelValidatorProviderContext context)
    {
        if (HasRequiredAttribute(context.ModelMetadata))
        {
            if (context.ModelMetadata.ModelType == typeof(ReferenceDataViewModel))
            {
                var validatorItem = new ValidatorItem()
                {
                    IsReusable = true,
                    Validator  = new ReferenceDataViewModelRequiredValidator()
                };
                // put the validator in early
                context.Results.Insert(0, validatorItem);
            }

    }
    /// <summary>
    /// Determines if the property being validated has a [Required] attribute
    /// </summary>
    /// <remarks>
    /// There is an IsRequired property on the <see cref="ModelMetadata"/>, but always seems to be "true".
    /// Not sure why.  
    /// </remarks>
    private static bool HasRequiredAttribute(ModelMetadata metadata)
    {
        if (metadata.ContainerType != null && !string.IsNullOrWhiteSpace(metadata.PropertyName))
        {
            var property = metadata.ContainerType.GetProperty(metadata.PropertyName);
            return property?.HasAttribute<RequiredAttribute>() ?? false;
        }
        return false;
    }
}

启动时需要注册

ModelValidatorProvider

builder
    .AddControllersWithViews(opts =>
    {
        opt.ModelValidatorProviders.Add(new MyModelValidatorProvider())
    });
© www.soinside.com 2019 - 2024. All rights reserved.