我在 ASP.NET core 项目中使用 Mediatr 来处理所有请求。我实现了几个请求/响应/处理程序。它们中的每一个都可以抛出一个特定的异常,我们将其称为“MyException”类。 我将异常处理程序定义为
public class MyExceptionHandler : RequestExceptionHandler<MyRequest<MyResponse>, MyResponse, MyException>
{
protected override void Handle(MyRequest<MyResponse> request, MyException exception, RequestExceptionHandlerState<MyResponse> state)
{
MyResponse response = new MyResponse();
//Set some specific properties here in the response to indicate an error occurred
state.SetHandled(response);
}
}
我将此异常处理程序添加到 (Autofac) 依赖项容器中,如果我在 MyRequest 和 MyResponse 的 MyHandler 中抛出 MyException,则该异常处理程序有效。但是,我有数十个请求、响应和相应的处理程序。那么我如何为所有这些异常注册这个特定异常的异常处理程序(注意:所有响应都派生自同一个基类)。它尝试了类似下面的内容,但是,没有被调用。它仅在我给出实际类型时才有效,但这意味着我必须为每种类型创建一个异常处理程序,这远远不切实际。关于如何解决这个问题有什么想法吗?
public class MyExceptionHandler : RequestExceptionHandler<IRequest<BaseResponse>, BaseResponse, MyException>
{
protected override void Handle(IRequest<BaseResponse> request, MyException exception, RequestExceptionHandlerState<BaseResponse> state)
{
BaseResponse response = new BaseResponse();
//Set some specific properties here in the response to indicate an error occurred
state.SetHandled(response);
}
}
您可以像这样创建一个通用的:
public class ExceptionLoggingHandler<TRequest, TResponse, TException> : IRequestExceptionHandler<TRequest, TResponse, TException>
where TRequest : IRequest<TResponse>
where TException : Exception
{
private readonly ILogger<ExceptionLoggingHandler<TRequest, TResponse, TException>> _logger;
public ExceptionLoggingHandler(ILogger<ExceptionLoggingHandler<TRequest, TResponse, TException>> logger)
{
_logger = logger;
}
public Task Handle(TRequest request, TException exception, RequestExceptionHandlerState<TResponse> state, CancellationToken cancellationToken)
{
_logger.LogError(exception, "Something went wrong while handling request of type {@requestType}", typeof(TRequest));
// TODO: when we want to show the user somethig went wrong, we need to expand this with something like
// a ResponseBase where we wrap the actual response and return an indication whether the call was successful or not.
state.SetHandled(default!);
return Task.CompletedTask;
}
}
然后在 IerviceC 上像这样注册它:
services.AddTransient(typeof(IRequestExceptionHandler<,,>), typeof(ExceptionLoggingHandler<,,>));
因为
RequestExceptionProcessorBehavior
类将查找具有 3 个泛型的类型,而不是 IRequestExceptionHandler<,>
一个。
AFAIK,处理程序旨在用于特定类型的请求、响应和异常。在这种情况下,
MediatR
无法执行通用变体,因为当发生异常时,这不是解决它们的方式。
所以基本上,您所做的与您想要实现的目标非常接近。您可以创建一个通用抽象处理程序,并根据需要拥有任意数量的派生类型实现,但您始终必须创建该样板文件。这是捕获
InvalidOperationException
的处理程序的示例。
public class BaseResponse
{
public bool Error { get; set; }
public string ErrorMessage { get; set; } = null!;
}
public class SomeResponse : BaseResponse {}
public class BaseRequest<TResponse> : IRequest<TResponse> where TResponse : BaseResponse {}
public class SomeRequest : BaseRequest<SomeResponse> {}
public class SomeRequestHandler : IRequestHandler<SomeRequest, SomeResponse>
{
public Task<SomeResponse> Handle(SomeRequest request, CancellationToken cancellationToken)
{
throw new InvalidOperationException();
}
}
public abstract class
AbstractInvalidOperationExceptionHandler<TRequest, TResponse> : RequestExceptionHandler<TRequest, TResponse, InvalidOperationException>
where TRequest : BaseRequest<TResponse>
where TResponse : BaseResponse, new()
{
protected override void Handle(TRequest request, InvalidOperationException exception, RequestExceptionHandlerState<TResponse> state)
{
var response = new TResponse
{
Error = true,
ErrorMessage = exception.Message
};
state.SetHandled(response);
}
}
public class SomeInvalidOperationExceptionHandler : AbstractInvalidOperationExceptionHandler<SomeRequest, SomeResponse>
{
}
这应该可以按需要工作,要求您为
TRequest
+ TResponse
的每个组合添加一种派生类型。如果您不使用 MediatR 的 Autofac-PlugIn,则还必须手动注册每个派生类型,该插件使用程序集扫描来注册 RequestExceptionHandler<,,,>
的所有实现。还有第二种选择,与建议的中间件类似,但您不必编写中间件,而是为接口的每个异常创建一个实现
IExceptionFilter
(如果此处需要
IAsyncExceptionFilter
,则为
async
) .再次处理
InvalidOperationFilter
类型的异常,它看起来像这样:
public class InvalidOperationExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
if (context.Exception is not InvalidOperationException invalidOperationException)
{
return;
}
context.ExceptionHandled = true; // this follows the same principle as MediatR, you need to set it as handled, for the pipeline to stop executing!
// this will set the response to 409
context.Result = new ConflictObjectResult(new
{
Message = invalidOperationException.Message
});
}
}
现在这工作得很好,过滤器只需要注册一次,但这可能不是处理错误并将其返回给客户端的理想方式。它还(紧密)将您的异常处理逻辑与框架耦合起来。
当然,您应该考虑您是否总是(据您目前所知)以完全相同的方式处理异常,因为如果不是,则意味着如果您开始检查中间件解决方案,中间件解决方案很可能会降低您的代码的可维护性和清洁度相同异常的自定义条件。
所以,这是一个中间件示例:
public class ValidationExceptionHandlerMiddleware
{
private readonly RequestDelegate next;
public ValidationExceptionHandlerMiddleware(RequestDelegate next) => this.next = next;
public async Task Invoke(HttpContext context)
{
try
{
await this.next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
switch (exception)
{
string result = "";
case MyException validationException:
//custom handling of my exception
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
result = //your serialized json object with error data
break;
default:
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
result = //your serialized json object with error data
break;
}
return context.Response.WriteAsync(result);
}
}
然后您需要做的就是在您认为合适的管道中注册该中间件。您必须在要处理的中间件之前注册它。您可以使用
Configure()
中的
Startup.cs
方法来完成此操作:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
builder.UseMiddleware<ValidationExceptionHandlerMiddleware>();
...
}
所以现在,应用程序中所有抛出的和未处理的异常都将由中间件处理。
请注意,这是一个直接写入响应的较低级别的解决方案,因此如果您需要在处理异常时执行业务逻辑,这可能不合适。