如何装饰 MediatR 处理程序

Posted

技术标签:

【中文标题】如何装饰 MediatR 处理程序【英文标题】:How to decorate a MediatR Handler 【发布时间】:2019-07-02 00:12:10 【问题描述】:

我只想装饰一个 MediatR 处理程序。我尝试使用 Behaviours,但 Behaviors 为每个实现 IRequestHandler<TRequest,TResponse>的处理程序注入了装饰器

public class ProcessFirstCommand : IRequest<bool>

    public string Message  get; set; 


public class ProcessFirstCommandHandler : IRequestHandler<ProcessFirstCommand, bool>

    public Task<bool> Handle(ProcessFirstCommand request, CancellationToken cancellationToken)
    
        Console.WriteLine("Inside Process First Command Handler");

        return Task.FromResult(true);
    


public class Manager

    private readonly IMediator _mediator;

    public Manager(IMediator mediator)
    
        _mediator = mediator;
    

    public void Execute()
    
        _mediator.Send(new ProcessFirstCommand());
    


//Registering in Autofac for IRequestHandler
public class Module : Autofac.Module

    protected override void Load(ContainerBuilder builder)
    
        builder.RegisterAssemblyTypes(ThisAssembly)
            .AsClosedTypesOf(typeof(IRequestHandler<,>));
    

问题:如何添加一个装饰器,该装饰器将在调用 ProcessFirstCommandHandler 类的 Handle 方法之前执行,而不是为实现 IRequestHandler 的其他类添加。

当Manager对象执行此行_mediator.Send(new ProcessFirstCommand());时,如何在ProcessFirstCommandHandler之前首先调用下面的类Handle方法@

public class ProcessFirstCommandHandlerDecorator<TRequest, TResponse> : IRequestHandler<ProcessFirstCommand, bool>
                                                                            where TRequest : ProcessFirstCommand                    
    
        private readonly IRequestHandler<ProcessFirstCommand, bool> _handler;

        public ProcessFirstCommandHandlerDecorator(IRequestHandler<ProcessFirstCommand, bool> handler)
        
            _handler = handler;
        
        public Task<bool> Handle(ProcessFirstCommand request, CancellationToken cancellationToken)
        
            Console.WriteLine("Inside Process First Command Handler Decorator");

            _handler.Handle(request, cancellationToken);

            return Task.FromResult(true);
    

【问题讨论】:

【参考方案1】:

如果您想要做的只是在调用处理程序之前让某些东西运行,那么您可以利用行为来实现这一点。我知道你说过你之前已经尝试过,但是,你可以创建一个通用的 Behavior 来运行 IRequestPreProcessor 的所有实现。

注意:以下过程也适用于在处理程序运行后实现某些内容,您只需将接口从 IRequestPreProcessor 更改为 IRequestPostProcessor

所以如果你有你的命令处理程序:

public class ProcessFirstCommandHandler : IRequestHandler<ProcessFirstCommand, bool>

    public Task<bool> Handle(ProcessFirstCommand request, CancellationToken cancellationToken)
    
        Console.WriteLine("Inside Process First Command Handler");

        return Task.FromResult(true);
    

你可以实现 IRequestPreProcessor(你需要的装饰器),但一定要指定你希望它运行的命令

public class PreProcessFirstCommand : IRequestPreprocessor<ProcessFirstCommand>

    public ProcessFirstCommandHandlerDecorator()
    
        
    

    public Task Process(ProcessFirstCommand request, CancellationToken cancellationToken)
    
        Console.WriteLine("Inside Process First Command Handler Decorator");
    

这将由您的通用 PreProcessorBehaviour 激活,它将在每个 MediatR 请求上运行,但只会注入使用通用类型或指定 TRequest 类型的 IRequestPreProcessor 实现,就像我们上面的 PreProcessFirstCommand 类所做的那样:

public class RequestPreProcessValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : IRequest<TResponse>

    private readonly IEnumerable<IRequestPreProcessor<TRequest>> _preProcessors;

    public RequestPreProcessValidationBehaviour(IEnumerable<IRequestPreProcessor<TRequest>> preProcessors)
    
        _preProcessors = preProcessors;
    

    public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
    
        foreach (var processor in _preProcessors)
        
            await processor.Process(request, cancellationToken).ConfigureAwait(false);
        

        return await next().ConfigureAwait(false);
    

注意:此解决方案唯一的小问题是,如果您使用 ASP .NET Core 的默认依赖注入器,它只会注入实现 IRequestPreProcessor 并指定类型的类之一。

例如:

如果你有以下课程:

ProcessFirstCommandHandler.cs

public class ProcessFirstCommandHandler : IRequestHandler<ProcessFirstCommand, bool>

    public Task<bool> Handle(ProcessFirstCommand request, CancellationToken cancellationToken)
    
        Console.WriteLine("I'm inside the handler");

        return Task.FromResult(true);
    

PreProcessFirstCommand.cs

public class PreProcessFirstCommand : IRequestPreprocessor<ProcessFirstCommand>
    
    public ProcessFirstCommandHandlerDecorator()
    

    

    public Task Process(ProcessFirstCommand request, CancellationToken cancellationToken)
    
        Console.WriteLine("I ran before the handler");
    

另一个PreProcessFirstCommand.cs

public class AnotherPreProcessFirstCommand : IRequestPreprocessor<ProcessFirstCommand>

    public ProcessFirstCommandHandlerDecorator()
    

    

    public Task Process(ProcessFirstCommand request, CancellationToken cancellationToken)
    
        Console.WriteLine("I ran before the handler as well!");
    
 

GenericPreProcessCommand.cs

public class GenericPreProcessCommand<TRequest> : IRequestPreprocessor<TRequest>

    public ProcessFirstCommandHandlerDecorator()
    

    

    public Task Process(ProcessFirstCommand request, CancellationToken cancellationToken)
    
        Console.WriteLine("I'm generic!");
    
 

AnotherGenericPreProcessCommand.cs

public class AnotherGenericPreProcessCommand<TRequest> : IRequestPreprocessor<TRequest>

    public ProcessFirstCommandHandlerDecorator()
    

    

    public Task Process(ProcessFirstCommand request, CancellationToken cancellationToken)
    
        Console.WriteLine("I'm generic aswell!");
    
 

使用前面提到的通用 PreProcessorBehavior,这将注入 GenericPreProcessCommandAnotherGenericPreProcessCommand,但只注入 PreProcessFirstCommandAnotherPreProcessFirstCommand。这似乎只是 DI 的限制。我已经在official github issue 上为 MediatR 的创建者 Jimmy Bogard 留下了 cmets,因此请务必在此处阅读并做出贡献。

【讨论】:

【参考方案2】:

虽然在大多数情况下使用IRequestPreProcessor 是推荐和首选的方式,但您仍然可以直接调用您的 IoC 容器来添加装饰器。

例如,使用 SimpleInjector:

container.RegisterDecorator(typeof(IRequestHandler<,>), typeof(HandlerDecorator<,>));

【讨论】:

以上是关于如何装饰 MediatR 处理程序的主要内容,如果未能解决你的问题,请参考以下文章

如何在 VSCode 扩展中获取当前装饰列表或处理装饰位置更改?

如何使用 FluentAssertions 在 XUnit 中测试 MediatR 处理程序

如何使用FluentAssertions在XUnit中测试MediatR处理程序

如何实现多处理 python 装饰器?

python Neo4j Python装饰器处理程序

python Neo4j Python装饰器处理程序