【问题标题】:How do you centralise ASP.NET Core Web API attribute validation on multiple DTOs with similar properties?如何在具有相似属性的多个 DTO 上集中进行 ASP.NET Core Web API 属性验证?
【发布时间】:2022-01-19 17:14:06
【问题描述】:

有没有办法跨多个 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 实现集中验证并放弃使用属性。

【问题讨论】:

  • 我认为属性验证是针对每个对象完成的,如果你让所有其他类继承自 DisplayName 类(使用字符串 displayname 作为属性),那么你可以使用一个验证。如果对象相同,那么您可能正在复制您的代码
  • 属性仅适用于特定类型或属性。如果您想集中验证,您必须使用例如 FluendValidation 以编程方式指定它。无论如何这是一个好主意 - 属性是静态的,而同一类在不同阶段可能有不同的要求。例如,在创建和编辑客户对象时应用不同的规则。

标签: asp.net-core asp.net-web-api asp.net-core-webapi


【解决方案1】:

我选择使用自定义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 属性,从而为其创建集中验证?。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-09
    • 1970-01-01
    • 2021-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多