我们有许多通用命令处理程序,由Autofac以开放式通用方式注册。我们有几个装饰器来装饰所有手柄。现在我需要为一个命令处理程序注册一个装饰器,而不是影响所有其他命令处理程序。这是我的尝试,但我似乎没有正确的注册。
这是与我们的代码类似的简单测试代码:
我们有数百个命令如下:
class NormalCommand : ICommand { }
// This command handler should not be decorated
class NormalCommandHandler : ICommandHandler<NormalCommand>
{
public void Handle(NormalCommand command) { }
}
我想在装扮TestCommandHandler
TestCommandHandlerDecorator
class TestCommand : ICommand { }
// And I would like to put decorator around this handler
class TestCommandHandler : ICommandHandler<TestCommand>
{
public void Handle(TestCommand command) { }
}
// This decorator should be wrapped only around TestCommandHandler
class TestCommandHandlerDecorator : ICommandHandler<TestCommand>
{
private readonly ICommandHandler<TestCommand> decorated;
public TestCommandHandlerDecorator(ICommandHandler<TestCommand> decorated)
{
this.decorated = decorated;
}
public void Handle(TestCommand command)
{
// do something
decorated.Handle(command);
// do something again
}
}
这就是我注册组件的方式:
static class AutofacRegistration
{
public static IContainer RegisterHandlers()
{
var builder = new ContainerBuilder();
//Register All Command Handlers but not decorators
builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(AutofacRegistration)))
.Where(t => !t.Name.EndsWith("Decorator"))
.AsClosedTypesOf(typeof(ICommandHandler<>))
.InstancePerLifetimeScope();
// and here is the battle!
builder.RegisterType<TestCommandHandler>()
.Named<ICommandHandler<TestCommand>>("TestHandler")
.InstancePerLifetimeScope();
// this does not seem to wrap the decorator
builder.RegisterDecorator<ICommandHandler<TestCommand>>(
(c, inner) => new TestCommandHandlerDecorator(inner),
fromKey: "TestHandler")
.Named<ICommandHandler<TestCommand>>("TestHandler1")
.InstancePerLifetimeScope();
return builder.Build();
}
}
这就是我尝试确认我得到正确的命令处理程序/装饰器实例的方法:
class AutofacRegistrationTests
{
[Test]
public void ResolveNormalCommand()
{
var container = AutofacRegistration.RegisterHandlers();
var result = container.Resolve<ICommandHandler<NormalCommand>>();
// this resolves correctly
Assert.IsInstanceOf<NormalCommandHandler>(result); // pass
}
[Test]
public void TestCommand_Resolves_AsDecorated()
{
var container = AutofacRegistration.RegisterHandlers();
var result = container.Resolve<ICommandHandler<TestCommand>>();
// and this resolves to TestCommandHandler, not decorated!
Assert.IsInstanceOf<TestCommandHandlerDecorator>(result); // FAILS!
}
}
正如评论所说,装饰者没有被应用,装饰者注册被忽略。
任何想法如何注册这个装饰?我做错了什么?
答案 0 :(得分:3)
在我的头撞击键盘足够多次之后,我已经找到了解决问题的方法:
static class AutofacRegistration
{
public static IContainer RegisterHandlers()
{
var builder = new ContainerBuilder();
builder.RegisterAssemblyTypes(Assembly.GetAssembly(typeof(AutofacRegistration)))
.AsClosedTypesOf(typeof(ICommandHandler<>))
.InstancePerLifetimeScope();
builder.RegisterType<TestCommandHandler>()
.Named<ICommandHandler<TestCommand>>("TestHandler")
.InstancePerLifetimeScope();
// this works!
builder.Register(c => new TestCommandHandlerDecorator(c.ResolveNamed<ICommandHandler<TestCommand>>("TestHandler")))
.As<ICommandHandler<TestCommand>>()
.InstancePerLifetimeScope();
return builder.Build();
}
}
这里我没有使用Autofac的装饰器功能并手动包装装饰器。因此,如果装饰器中的依赖项数量增加,我将需要更新容器以解决所有必需的依赖项。
如果您知道更好的解决方案,请告诉我们!
答案 1 :(得分:3)
为避免在@ trailmax的回答中手动注册,您可以定义以下扩展方法:
public static class ContainerBuilderExtensions
{
public static void RegisterDecorator<TService, TDecorater, TInterface>(this ContainerBuilder builder,
Action<IRegistrationBuilder<TService, ConcreteReflectionActivatorData, SingleRegistrationStyle>> serviceAction,
Action<IRegistrationBuilder<TDecorater, ConcreteReflectionActivatorData, SingleRegistrationStyle>> decoratorAction)
{
IRegistrationBuilder<TService, ConcreteReflectionActivatorData, SingleRegistrationStyle> serviceBuilder = builder
.RegisterType<TService>()
.Named<TInterface>(typeof (TService).Name);
serviceAction(serviceBuilder);
IRegistrationBuilder<TDecorater, ConcreteReflectionActivatorData, SingleRegistrationStyle> decoratorBuilder =
builder.RegisterType<TDecorater>()
.WithParameter(
(p, c) => p.ParameterType == typeof (TInterface),
(p, c) => c.ResolveNamed<TInterface>(typeof (TService).Name))
.As<TInterface>();
decoratorAction(decoratorBuilder);
}
}
然后像这样使用它:
builder.RegisterDecorator<TestCommandHandler, TestCommandHandlerDecorator, ICommandHandler<TestCommand>>(
s => s.InstancePerLifetimeScope(),
d => d.InstancePerLifetimeScope());
答案 2 :(得分:2)
最后,该功能已在Autofac的4.9.0版本中添加。
builder.RegisterAssemblyTypes(typeof(AutofacRegistration).Assembly)
.AsClosedTypesOf(typeof(ICommandHandler<>));
builder.RegisterDecorator<TestCommandHandlerDecorator, ICommandHandler<TestCommand>>();
答案 3 :(得分:1)
我无法提供Castle Windsor或StructureMap的任何示例,根据我的经验,使用除Autofac和Simple Injector之外的其他任何东西都很难应用开放式通用装饰器。当涉及有条件地应用开放式通用装饰器(您的特定场景)时,AFAIK Simple Injector是唯一具有下降支持的DI容器。
使用Simple Injector,您可以按如下方式注册所有命令处理程序:
container.RegisterManyForOpenGeneric(
typeof(ICommandHandler<>),
typeof(ICommandHandler<>).Assembly);
装饰者可以按如下方式注册:
container.RegisterDecorator(
typeof(ICommandHandler<>),
typeof(CommandHandlerDecorator1<>));
container.RegisterDecorator(
typeof(ICommandHandler<>),
typeof(TestCommandHandlerDecorator));
container.RegisterDecorator(
typeof(ICommandHandler<>),
typeof(CommandHandlerDecorator2<>));
装饰器按照它们的注册顺序添加,这意味着在上面的情况下CommandHandlerDecorator2<T>
包装TestCommandHandlerDecorator
包装CommandHandlerDecorator1<T>
包装任何具体的命令处理程序。由于TestCommandHandlerDecorator
适用于一个特定的ICommandHandler<T>
,因此它仅包含在此类型中。所以在你的情况下,你在完成之前的注册后就完成了。
但你的情况实际上是一个简单的案例。 Simple Injector支持更多有趣的场景,例如基于谓词或泛型类型约束有条件地应用装饰器:
container.RegisterDecorator(
typeof(ICommandHandler<>),
typeof(SomeDecorator<>), c =>
c.ServiceType.GetGenericArguments()[0] == typeof(TestCommand));
通过向RegisterDecorator
提供谓词,您可以控制装饰器是否应用于某个注册。
另一种选择是将泛型类型约束应用于装饰器。 Simple Injector能够处理泛型类型约束:
// This decorator should be wrapped only around TestCommandHandler
class TestCommandHandlerDecorator<T> : ICommandHandler<T>
where T : TestCommand // GENERIC TYPE CONSTRAINT
{
// ...
}
当您有任何处理来自TestCommand
的命令的命令处理程序时,这很有用,但通常您会看到命令实现一个或多个接口,并且装饰器应用于处理命令的命令处理程序那些接口。
但不管怎样,装饰器可以简单地注册如下:
container.RegisterDecorator(
typeof(ICommandHandler<>),
typeof(TestCommandHandlerDecorator<>));
虽然我认为最终你可以在每个容器中使用它,但大多数容器都会使这个变得非常复杂。这就是Simple Injector擅长的地方。