[英]Register Autofac decorator for only one generic command handler
我们有许多通用命令处理程序,由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!
}
}
正如评论所说,装饰器没有被应用,装饰器注册被忽略。
任何想法如何注册这个装饰? 我究竟做错了什么?
在我的头撞击键盘足够多次之后,我已经找到了解决问题的方法:
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的装饰器功能并手动包装装饰器。 因此,如果装饰器中的依赖项数量增加,我将需要更新容器以解决所有必需的依赖项。
如果您知道更好的解决方案,请告诉我们!
要避免在@ 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());
最后,该功能已添加到4.9.0版本的Autofac中。
builder.RegisterAssemblyTypes(typeof(AutofacRegistration).Assembly)
.AsClosedTypesOf(typeof(ICommandHandler<>));
builder.RegisterDecorator<TestCommandHandlerDecorator, ICommandHandler<TestCommand>>();
我不能在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擅长的地方。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.