[英]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.