我想对 .NET WebApi 的响应正文/输出应用类验证,就像 .NET 使用类验证自动验证请求正文一样。
public class RequestAndResponseDto
{
[Required]
public string SomeProperty { get; set; }
}
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
[HttpPost]
Task<RequestAndResponseDto> Post([FromBody] RequestAndResponseDto requestBody)
{
return new RequestAndResponseDto();
}
}
我希望抛出异常,因为 SomeProperty 是必需的属性。其他验证也是如此,例如 MinLength 等。
该解决方案应适用于应用程序中的所有控制器和所有方法。如果不在每个端点中添加自定义逻辑,我将如何实现这一目标?
要在 .NET Web API 中的所有控制器和方法中对响应正文/输出全局应用类验证,您可以创建并注册一个操作过滤器,该过滤器对操作结果执行模型验证。这种方法允许您重用验证逻辑,而无需单独修改每个端点。
以下是实现此目标的方法:
首先,创建一个自定义操作过滤器,检查操作结果是否属于需要验证的类型(例如,您的 DTO),然后使用 Validator 类执行验证。
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.ComponentModel.DataAnnotations;
public class ValidateResponseAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Result is ObjectResult objectResult)
{
var result = objectResult.Value;
var validationContext = new ValidationContext(result, serviceProvider: null, items: null);
var validationResults = new List<ValidationResult>();
bool isValid = Validator.TryValidateObject(result, validationContext, validationResults, true);
if (!isValid)
{
context.Result = new BadRequestObjectResult(validationResults);
}
}
}
}
接下来,您需要全局注册此操作过滤器,以便将其应用于所有控制器和操作。您可以在 Startup.cs 中或在应用程序中配置服务的任何位置执行此操作。
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.Filters.Add(new ValidateResponseAttribute()); // Register the filter globally
});
// Other service configurations...
}
此设置可确保您的响应 DTO 得到验证,就像您的请求 DTO 一样。如果验证失败,则会返回包含验证错误的 BadRequest 响应。 注意:此方法会在将响应发送回客户端之前对其进行验证。确保您的应用程序逻辑正确处理响应数据可能未通过验证的情况非常重要,因为这可能表明您的应用程序的数据处理或业务逻辑存在缺陷。
如果您想在控制器级别验证它,您可以将其添加到控制器中。
if (!ModelState.IsValid)
{
return UnprocessableEntity(ModelState);
}
public class RequestAndResponseDto
{
[MinLength(1)]
[Required(ErrorMessage = "some error")]
public string SomeProperty { get; set; }
}
public class ModelValidationMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ModelValidationMiddleware> _logger;
public ModelValidationMiddleware(RequestDelegate next, ILogger<ModelValidationMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/api")) // Assuming API endpoints for validation
{
// Read request body
string requestBody = await new StreamReader(context.Request.Body).ReadToEndAsync();
// Deserialize request body into model
var model = JsonConvert.DeserializeObject<RequestAndResponseDto>(requestBody);
// Validate model using data annotations
var validationResults = new List<ValidationResult>();
var isValid = Validator.TryValidateObject(model, new ValidationContext(model), validationResults, true);
if (!isValid)
{
_logger.LogWarning("Model validation failed.");
// Collect validation errors
var errors = new List<string>();
foreach (var validationResult in validationResults)
{
foreach (var memberName in validationResult.MemberNames)
{
errors.Add($"{memberName}: {validationResult.ErrorMessage}");
}
}
// Return validation errors in the response
var errorResponse = new { Errors = errors };
var jsonResponse = JsonConvert.SerializeObject(errorResponse);
context.Response.StatusCode = StatusCodes.Status400BadRequest;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(jsonResponse);
return;
}
}
await _next(context);
}
}
builder.UseMiddleware<ModelValidationMiddleware>()