您如何在具有相似属性的多个 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
属性 以创建集中式验证。
有没有办法在多个 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
属性 以创建集中式验证。