如何在Mediator中为所有请求定义一个特定的异常处理程序



我将Mediator用于ASP。NET核心项目来处理所有请求。我已经实现了几个请求/响应/处理程序。它们中的每一个都可以抛出一个特定的异常,让我们称之为"异常";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;
}
}

然后像这样在一个服务上注册:

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插件,则这些派生类型中的每一个也必须手动注册,该插件使用汇编扫描来注册RequestExceptionHandler<,,,>的所有实现。

还有第二个选项,它类似于建议的中间件,但您不需要编写中间件,而是为接口IExceptionFilter的每个异常创建一个实现(如果这里需要async,则为IAsyncExceptionFilter(。

如果再次处理类型为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
});
}
}

现在这工作得很好,该过滤器只需要注册一次,但它可能不是处理错误并将其返回给客户端的理想方式。它还(紧密地(将异常处理逻辑与框架耦合在一起。

当您在MediatR处理程序中抛出相同类型的异常时,另一种方法是放弃MediatR异常处理程序,并通过中间件全局处理它。通过这样做,您可以确保不会将相同的错误处理代码分散在多个地方。

当然,您应该考虑(据您目前所知(是否总是以完全相同的方式处理异常,因为如果不是,那意味着如果您开始检查同一异常的自定义条件,中间件解决方案很可能会降低代码的可维护性和干净性。

因此,这里有一个中间件示例:

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);
}
}

然后,您需要做的是在您认为合适的管道中注册这个中间件。你必须先注册它,然后才能处理你想要的中间件。

您可以在Startup.cs:中的Configure()方法中执行此操作

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
builder.UseMiddleware<ValidationExceptionHandlerMiddleware>();
...
}

因此,现在,应用程序中所有抛出的和未处理的异常都将由中间件处理。

请注意,这是一个直接写入响应的较低级别解决方案,因此,如果在处理异常时需要执行业务逻辑,这可能不合适。

相关内容

最新更新