我正在使用这个
ValidationBehavior
课程:
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
var context = new ValidationContext<TRequest>(request);
var failures = _validators
.ToAsyncEnumerable()
.SelectAwait(async validator => await validator.ValidateAsync(context))
.ToEnumerable()
.SelectMany(result => result.Errors)
.Where(failure => failure is not null)
.ToList();
if (failures.Any())
{
throw new ValidationException(failures);
}
return await next();
}
}
我正在通过 MediatR 管道进行操作,就像这样:
services.AddMediatR(options =>
{
options.RegisterServicesFromAssembly(typeof(ServiceCollectionExtension).Assembly);
options.AddOpenBehavior(typeof(ValidationBehavior<,>));
});
验证器非常简单,只是检查金额是否大于0。
internal class DepositCommandHandlerValidator : AbstractValidator<DepositCommand>
{
public DepositCommandHandlerValidator()
{
RuleFor(c => c.Amount).GreaterThan(0).WithMessage("Amount must be greater than 0.");
}
}
控制器中有一个动作:
[HttpPatch("deposit")]
public async Task<IActionResult> Deposit([FromRoute] Guid id, [FromBody] decimal amount)
{
bool result = await _mediator.Send(new DepositCommand(id, amount), CancellationToken.None);
if (result)
{
return NoContent();
}
return BadRequest();
}
现在,正常的 api 错误会产生正确的消息,例如,我发送一个完全空的正文,我得到:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.13",
"title": "Unsupported Media Type",
"status": 415,
"traceId": "00-55b083e06040a567d23741ac86df0166-6c9093c560f5addc-00"
}
但是,当出现验证异常时,我可以看到正确的消息,这没关系,而且我还可以看到整个堆栈跟踪。例如,假设我在请求正文中输入一个负值,我得到:
FluentValidation.ValidationException: Validation failed:
-- Amount: Amount must be greater than 0. Severity: Error
at BankApp.Core.Middleware.ValidationBehavior`2.Handle(TRequest request, RequestHandlerDelegate`1 next, CancellationToken cancellationToken) in C:\Users\Jan\source\repos\BankApi\BankApi.Core\Middleware\ValidationBehavior.cs:line 33
at BankApp.Api.Controllers.CustomersController.Deposit(Guid id, Decimal amount) in C:\Users\Jan\source\repos\BankApi\BankApi.Api\Controllers\CustomersController.cs:line 25
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
HEADERS
=======
Accept: */*
Connection: keep-alive
Host: localhost:7098
User-Agent: PostmanRuntime/7.33.0
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 2
Postman-Token: 72d7805a-490a-478c-a1d1-a5d53283f05e
为什么要发送整个堆栈跟踪?
中间件出现错误。它没有像评论中那样正确处理异常