如何使用 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>
{ ... }
在控制器中,我在构造函数中有多个 CommandHandler
和 QueryHandler
,我想将其缩短为类似于 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 中的工作方式(我实际上会说:在数学中)这是不可能的。
但是,有两个简单的解决方案可以解决您的问题。
- 使
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
。这意味着实现成为基础结构组件;它成为你的一部分 Composition Root.
我有以下情况:
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
和 QueryHandler
,我想将其缩短为类似于 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 中的工作方式(我实际上会说:在数学中)这是不可能的。
但是,有两个简单的解决方案可以解决您的问题。
- 使
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
。这意味着实现成为基础结构组件;它成为你的一部分 Composition Root.