仅为 EmailAddressAttribute 调用 IValidationAttributeAdapterProvider

IValidationAttributeAdapterProvider is called only for EmailAddressAttribute

我用 ASP.NET MVC 5

做了什么
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MaxLengthAttribute), typeof(MyMaxLengthAttributeAdapter));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(RequiredAttribute), typeof(MyRequiredAttributeAdapter));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(MinLengthAttribute), typeof(MyMinLengthAttribute));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(EmailAddressAttribute), typeof(MyEmailAddressAttributeAdapter));

现在我正在将它迁移到 ASP.NET 核心 6

我们不能再使用 DataAnnotationsModelValidatorProvider,所以我正在尝试使用 IValidationAttributeAdapterProvider,这对我来说效果不佳。

我的代码

下面是我的IValidationAttributeAdapterProvider

public class MyValidationAttributeAdapterProvider : ValidationAttributeAdapterProvider, IValidationAttributeAdapterProvider
{
    IAttributeAdapter? IValidationAttributeAdapterProvider.GetAttributeAdapter(
        ValidationAttribute attribute,
        IStringLocalizer? stringLocalizer)
    {
        return attribute switch
        {
            EmailAddressAttribute => new MyEmailAddressAttributeAdapter((EmailAddressAttribute)attribute, stringLocalizer),
            MaxLengthAttribute => new MyMaxLengthAttributeAdapter((MaxLengthAttribute)attribute, stringLocalizer),
            MinLengthAttribute => new MyMinLengthAttribute((MinLengthAttribute)attribute, stringLocalizer),
            RequiredAttribute => new MyRequiredAttributeAdapter((RequiredAttribute)attribute, stringLocalizer),
            _ => base.GetAttributeAdapter(attribute, stringLocalizer),
        };
    }
}

下面是我的模型class

public class LogInRequestDTO
{
    [Required]
    [EmailAddress]
    [MaxLength(FieldLengths.Max.User.Mail)]
    [Display(Name = "mail")]
    public string? Mail { get; set; }

    [Required]
    [MinLengthAttribute(FieldLengths.Min.User.Password)]
    [DataType(DataType.Password)]
    [Display(Name = "password")]
    public string? Password { get; set; }
}

在我的 Program.cs 中,我喜欢下面的内容。

builder.Services.AddControllersWithViews()
    .AddDataAnnotationsLocalization(options =>
    {
        options.DataAnnotationLocalizerProvider = (type, factory) => factory.Create(typeof(Resources));
    });

builder.Services.AddSingleton<IValidationAttributeAdapterProvider, MyValidationAttributeAdapterProvider>();

让我开心的事情

我希望为每个属性调用 GetAttributeAdapter,例如 EmailAddressAttributeMaxLengthAttribute 等。 但是它只用 EmailAddressAttribute 调用了一次。 所以,所有其他验证结果都不是我的适配器定制的。

如果我从模型 class 中删除 [EmailAddress],则永远不会调用 GetAttributeAdapter

我是不是漏掉了什么?

添加于 2022/05/24

我想做什么

可重现的项目

我创建了可以重现问题的最小示例项目。 https://github.com/KuniyoshiKamimura/IValidationAttributeAdapterProviderSample

  1. 用Visual Studio2022(17.2.1)打开解决方案。
  2. 在 MyValidationAttributeAdapterProvider 上设置断点。
  3. 运行 项目。
  4. 在浏览器的文本框中输入内容并提交。
  5. 断点只命中一次 EmailAddressAttribute 属性。
  6. 浏览器显示电子邮件的自定义消息和所有其他验证的默认消息。

下面是工作demo,大家可以参考一下

在所有 AttributeAdapter 中,按如下所示更改您的代码。

public class MyEmailAddressAttributeAdapter : AttributeAdapterBase<EmailAddressAttribute>
    {
        // This is called as expected.
        public MyEmailAddressAttributeAdapter(EmailAddressAttribute attribute, IStringLocalizer? stringLocalizer)
           : base(attribute, stringLocalizer)
        {
            //attribute.ErrorMessageResourceType = typeof(Resources);
            //attribute.ErrorMessageResourceName = "ValidationMessageForEmailAddress";
            //attribute.ErrorMessage = null;
        }

        public override void AddValidation(ClientModelValidationContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-must-be-true", GetErrorMessage(context));
        }

        // This is called as expected.
        // And I can see the message "Input the valid mail address.".
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
        {
            return GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName());
        }
    }

在家庭控制器中:

public IActionResult Index()
    {
        return View();
    }
    [HttpPost]
    public IActionResult Index([FromForm][Bind("Test")] SampleDTO dto)
    {
        return View();
    }

索引视图:

@model IV2.Models.SampleDTO

@{
    ViewData["Title"] = "Index";
}

<h1>Index</h1>

<h4>SampleDTO</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Index">
           <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Test" class="control-label"></label>
                <input asp-for="Test" class="form-control" />
                <span asp-validation-for="Test" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

结果 1:

结果2:

我找到了解决方案。

我要用的不是ValidationAttributeAdapterProvider而是IValidationMetadataProvider.

This article详细介绍了用法。 请注意,某些属性(包括 EmailAddressAttribute 必须以特殊方式处理,如描述 here 因为它们具有默认值 non-null ErrorMessage.

我已确认 EmailAddressAttribute 和其他一些属性。

此外,还有相关文章 here