基于MediatR的管道模式,我们可以在处理业务之前,进行统一验证,记录日志等。
所有命令(Command)再被处理(Handle)之前,都要经过IRequestPreProcessor处理,我们注入自己的拦截器,执行响应的验证代码即可。
下面主要是公司项目的实践,希望能给大家带来启发。
公司项目是 .net core 3.1 web api项目,使用MediatR的命令模式。对于增删改操作,每个接口都有对应的一个Command接收参数,一个Handle类处理命令。一般如果写业务员验证的话,会在Handle类的Handle方法中先去做业务验证,再写执行逻辑。
既然我们统一用了MediatR,那么就可以利用MediatR的管道模式,在处理命令之前,统一执行这些业务验证。
命令与校验器
因为每个业务都有自己独特的业务验证,所有我们为每个命令(command)都创建了对应的校验类(XXXXValidate),当然也有一些公共的校验类。通过注入的方式,将这些校验类注入到命令中。如图中BizBatchSetCreateCommand命令中注入了BizBatchSetCreateValidate校验器。所有需要统一业务校验的命令都必须继承自AutoVerificationCommandBase。校验器需要继承CommandVerificationBase<T>。
自定义MediatR拦截器
我司用的MediatR 8.0.0.0,包含接口IRequestPreProcessor<in TRequest>.顾名思义,这是命令被处理前会经过的拦截器。
namespace MediatR.Pipeline
{
//
// 摘要:
// Defined a request pre-processor for a handler
//
// 类型参数:
// TRequest:
// Request type
public interface IRequestPreProcessor<in TRequest> where TRequest : notnull
{
//
// 摘要:
// Process method executes before calling the Handle method on your handler
//
// 参数:
// request:
// Incoming request
//
// cancellationToken:
// Cancellation token
//
// 返回结果:
// An awaitable task
Task Process(TRequest request, CancellationToken cancellationToken);
}
}
我们继承此接口实现通用业务校验类GenericVerificationPreProcessor<TRequest>。Process函数中判断命令(TRequest)是否继承自IAutoVerificationCommand。如果是,取出其中的校验器并逐个执行。
public class GenericVerificationPreProcessor<TRequest> : IRequestPreProcessor<TRequest>
where TRequest : class
{
IServiceProvider _ServiceProvider;
/// <summary>
///
/// </summary>
public GenericVerificationPreProcessor(IServiceProvider ServiceProvider)
{
_ServiceProvider = ServiceProvider;
}
/// <summary>
///
/// </summary>
public async Task Process(TRequest request, CancellationToken cancellationToken)
{
if (request is IAutoVerificationCommand)//继承自动校验接口,表示需要自动校验
{
var command = request as IAutoVerificationCommand;
if (command != null && !command.IsProcessed)
{
command.IsProcessed = true;
if (command.VerificationProviders != null)
{
foreach (var typeOfProvider in command.VerificationProviders)
{
var service = _ServiceProvider.GetService(typeOfProvider);//通过_ServiceProvider创建校验器
if (service == null)
{
throw new Exception($"无法实例化类型:{typeOfProvider.Name},请检查类型是否已注册.");
}
if (service is CommandVerificationBase<TRequest>)
{
var provider = service as CommandVerificationBase<TRequest>;
if (provider != null)
{
provider.Verify(request);
//Process并没有短路的方法,所以用抛出异常的方式终止当前请求的执行。
provider.RaiseException();
}
}
}
}
}
}
}
}
因为Process不能中断管道的执行,所以当校验不通过的时候,直接抛了异常出来,在我们项目中有全局异常捕获。捕获到异常后会封装错误信息返回,这里就不管了,直接抛出异常了。
拦截器不需要特意的去注册。
当然实现IPipelineBehavior也可以完成上面的功能。逻辑类似。