Как отправить звонок от посредника с помощью Simple Injector - PullRequest
0 голосов
/ 06 апреля 2019

У меня следующая ситуация:

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>
{ ... }

В контроллерах у меня несколько CommandHandler s и QueryHandler s в конструкторе, и я хотел бы сократить его до шаблона-посредника, как в 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 и QueryHandlers

1 Ответ

1 голос
/ 06 апреля 2019

Почему это не работает в SI?

Это не работает по той же причине, что и в .NET.Это будет работать только тогда, когда ваш ICommandHandler<T> интерфейс будет определен как ковариантный , но это невозможно, потому что TCommand является аргументом input .

Давайте удалимКонтейнер DI с картинки на мгновение.Используя обычный старый код 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 # определитследующее:

Ошибка CS0266: невозможно неявное преобразование типа 'Command1Handler' в 'ICommandHandler '.Существует явное преобразование (вам не хватает приведения?)

Это источник вашей проблемы.Ваши обработчики команд не могут быть преобразованы, например, с ICommandHandler<Command1> в ICommandHandler<ICommand>.Чтобы понять это, вам нужно узнать о ковариации и контравариантности.Возможно, вы захотите начать здесь .

Чтобы разрешить 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);
    }
}
В качестве альтернативы, если сделать универсальный метод 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.Это означает, что реализация становится инфраструктурным компонентом;оно становится частью вашего корня композиции .

...