基于MediatR管道的公共业务校验

Source

基于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也可以完成上面的功能。逻辑类似。

源码