您如何在具有相似属性的多个 DTO 上集中 ASP.NET Core Web API 属性验证?

How do you centralise ASP.NET Core Web API attribute validation on multiple DTOs with similar properties?

有没有办法在多个 DTO 中集中对相同 属性 名称的模型验证?

例如,如果我将以下 类 用作 Web API 操作中的请求正文。

public class RegisterRequest
{
    [Required]
    [EmailAddress]
    public string EmailAddress { get; set; } = null!;

    [Required]
    [MinLength(8)]
    [RegularExpression(UserSettings.PasswordRegex)]
    public string Password { get; set; } = null!;

    [Required]
    [MaxLength(100)]
    public string DisplayName { get; set; } = null!;
}
public class UserProfileRequest
{
    [Required]
    public int UserId { get; set; }
    [Required]
    [MaxLength(100)]
    public string DisplayName { get; set; } = null!;
    [Range(3, 3)]
    public string? CCN3 { get; set; }
}

我可以将属性验证集中在 DisplayName 上吗,复制属性违反单一责任原则。我相信我可以使用 IFilterFactory 并放弃使用属性来实现集中验证。

我选择使用自定义 ActionFilterAttribute 来实现集中验证。下面的示例用于验证国家代码 (CCN3).

CountryCodeValidationAttribute.cs - 要应用于属性的自定义属性(不包含逻辑)

[AttributeUsage(AttributeTargets.Property)]
public class CountryCodeValidationAttribute : Attribute
{

}

CountryCodeValidationActionFilter.cs - 支持依赖注入并在属性上查找自定义属性的自定义操作过滤器。在我的例子中,我返回标准的无效模型错误请求响应。

public class CountryCodeValidationActionFilter : ActionFilterAttribute
{
    private readonly ICountryService countryService;
    private readonly IOptions<ApiBehaviorOptions> apiBehaviorOptions;

    public CountryCodeValidationActionFilter(
        ICountryService countryService,
        IOptions<ApiBehaviorOptions> apiBehaviorOptions)
    {
        this.countryService = countryService;
        this.apiBehaviorOptions = apiBehaviorOptions;
    }

    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        var actionArguments = context.ActionArguments;

        foreach (var actionArgument in actionArguments)
        {
            if (actionArgument.Value == null) continue;

            var propertiesWithAttributes = actionArgument.Value
                .GetType()
                .GetProperties()
                .Where(x => x.GetCustomAttributes(true).Any(y => y.GetType() == typeof(CountryCodeValidationAttribute)))
                .ToList();

            foreach (var property in propertiesWithAttributes)
            {
                var value = property.GetValue(actionArgument.Value)?.ToString();

                if (value != null && await countryService.GetCountryAsync(value) != null) await next();
                else
                {
                    context.ModelState.AddModelError(property.Name, "Must be a valid country code");
                    context.Result = apiBehaviorOptions.Value.InvalidModelStateResponseFactory(context);
                }
            }
        }

        await base.OnActionExecutionAsync(context, next);
    }
}

Program.cs - 注册自定义操作过滤器。

builder.Services.AddMvc(options =>
{
    options.Filters.Add(typeof(CountryCodeValidationActionFilter));
});

UserProfile.cs - 将 [CountryCodeValidation] 属性应用于 CountryCode 属性.

public class UserProfile
{
    [Required]
    [MaxLength(100)]
    public string DisplayName { get; set; } = null!;
    [CountryCodeValidation]
    public string? CountryCode { get; set; }
}

我可以采用相同的方法并将其应用于 DisplayName 属性 以创建集中式验证。