如何使用 Simple Injector 从中介发送呼叫

How to dispatch a call from a mediator using Simple Injector

我有以下情况:

public interface ICommand { }

public interface ICommandHandler<TCommand> where TCommand : ICommand
{
    void Handle(TCommand command);
}

public interface ISepsCommandHandler<TCommand> : ICommandHandler<TCommand>
    where TCommand : ICommand
{
    event EventHandler<EntityExecutionLoggingEventArgs> UseCaseExecutionProcessing;
}

public sealed class CalculateNewAverageElectricEnergyProductionPriceCommandHandler
    : BaseCommandHandler,
    ISepsCommandHandler<CalculateNewAverageElectricEnergyProductionPriceCommand>
{ ... }

public sealed class CalculateCpiCommandHandler
    : BaseCommandHandler, ISepsCommandHandler<CalculateNewConsumerPriceIndexCommand>
{ ... }

在控制器中,我在构造函数中有多个 CommandHandlerQueryHandler,我想将其缩短为类似于 MediatR 的中介模式。

public interface ICqrsMediator // <TCommand, TQuery, TQueryResult>
                               // where TCommand : ICommand
{
    void Send(ICommand command);
}

public class CqrsMediator : ICqrsMediator // <ICommand
                                          // where TCommand : ICommand
{
    private readonly IDictionary<Type, ICommandHandler<ICommand>> _commands;

    public CqrsMediator(
        IEnumerable<ICommandHandler<ICommand>> commands) { ... }
    ...    
}

问题:

我想将 ICommandHandler 的集合解析为 CqrsMediator 的构造函数。各种方法都试过了

问题: 为什么这在 SI 中不起作用?

var bla = GetAllInstances(typeof(ICommandHandler<ICommand>));

我收到一条消息,它找不到 ICommandHandler<ICommand>ICommandHandler<TCommand> 已注册,尽管我在泛型中给出了 TCommand 只能是 ICommand 类型的约束。

任何人都可以帮助构建 CommandHandlers and QueryHandlers 的中介模式吗?

Why doesn't this work in SI?

这与它在 .NET 中不起作用的原因相同。这仅在您的 ICommandHandler<T> 接口被定义为 covariant 时有效,但这是不可能的,因为 TCommand 是一个 input 参数。

让我们暂时从图片中删除 DI Container。使用普通的 C# 代码,以下是您想要完成的:

ICommandHandler<ICommand> handler1 = new Command1Handler();
ICommandHandler<ICommand> handler2 = new Command2Handler();
ICommandHandler<ICommand> handler3 = new Command3Handler();

IEnumerable<ICommandHandler<ICommand>> handlers = new[] { handler1, handler2, handler3 };

new CqrsMediator(handlers);

前面的代码片段创建了三个新的命令处理程序:

  • Command1Handler 实施 ICommandHandler<Command1>
  • Command2Handler 实施 ICommandHandler<Command2>
  • Command3Handler 实施 ICommandHandler<Command3>

因为要将它们注入 CqrsMediator,所以将它们放在 ICommandHandler<ICommand> 类型的变量中。这样就可以轻松构造一个数组(ICommandHandler<ICommand>[]),可以将其注入CqrsMediator.

但是,此代码无法编译。 C# 编译器将声明如下:

Error CS0266: Cannot implicitly convert type 'Command1Handler' to 'ICommandHandler<ICommand>'. An explicit conversion exists (are you missing a cast?)

这就是您问题的根源。您的命令处理程序不能从 ICommandHandler<Command1> 转换为 ICommandHandler<ICommand>。要理解这一点,您需要了解协变和逆变。您可能想要开始 here.

要允许 ICommandHandler<Command1> 可分配给 ICommandHandler<ICommand> 它需要 ICommandHandler<TCommand> 抽象是 协变 :

public interface ICommandHandler<out TCommand> where TCommand : ICommand { ... }

换句话说,你需要TCommand一个out参数。

但这不能做到,因为TCommand实际上是一个in参数(因此是逆变的)。

长话短说,由于变体和泛型在 .NET 中的工作方式(我实际上会说:在数学中)这是不可能的。

但是,有两个简单的解决方案可以解决您的问题。

  1. 使 CqrsMediator.Send 方法通用:
public class CqrsMediator : ICqrsMediator
{
    private readonly Container container;

    public CqrsMediator(Container container) => this.container = container;

    public void Send<TCommand>(TCommand command)
    {
        var handler = this.container.GetInstance<ICommandHandler<TCommand>>();
        handler.Handle(command);
    }
}
  1. 或者,如果使 Send 方法泛化不是一个选项,您还可以在 Send 方法内部使用反射来查找命令的正确类型:
public class CqrsMediator : ICqrsMediator
{
    private readonly Container container;

    public CqrsMediator(Container container) => this.container = container;

    public void Send<TCommand>(TCommand command)
    {
        var handlerType = typeof(ICommandHandler<>).MakeGenericType(command.GetType());
        dynamic handler = container.GetInstance(handlerType);
        return handler.Handle((dynamic)command);
    }
}

在这两种情况下,您都让 CqrsMediator 实现依赖于 Container。这意味着实现成为基础结构组件;它成为你的一部分 Composition Root.