2013-08-01 25 views
5

我們有許多通用命令處理程序,它們是由Autofac以開放式通用方式註冊的。我們有裝飾所有手柄的幾個裝飾器。現在我只需要爲一個命令處理程序註冊一個裝飾器,而不會影響所有其他命令處理程序。這是我的嘗試,但我似乎沒有獲得註冊權。只爲一個通用命令處理程序註冊Autofac裝飾器

下面是簡單的測試代碼是類似於我們的代碼:

我們有數百個這樣工作的命令:

class NormalCommand : ICommand { } 

// This command handler should not be decorated 
class NormalCommandHandler : ICommandHandler<NormalCommand> 
{ 
    public void Handle(NormalCommand command) { } 
} 

而且我想在裝飾ONLY 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! 
    } 
} 

正如評論說,是沒有得到應用的裝飾,裝飾註冊將被忽略。

任何ides如何註冊這個裝飾器?我究竟做錯了什麼?

+0

我可以提供使用其他的DI容器的解決方案,還是你迷上了Autofac? – Steven

+0

我剛剛迷上了Autofac,但如果您可以在Structure Map或Windsor中舉例,我也會對此感興趣。爲了教育目的。 – trailmax

回答

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

我無法舉出Castle Windsor或StructureMap上的任何示例,並且根據我的經驗,使用Autofac和Simple Injector以外的任何其他應用程序很難應用開放通用裝飾器。當涉及到有條件地應用開放的通用裝飾器(您的特定場景)時,AFAIK Simple Injector是唯一一個支持此功能的DI容器。

有了簡單注射器,註冊您的所有命令處理程序如下:

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>,因此僅包含這些類型。因此,就你的情況而言,在完成之前的註冊後你就完成了。

但你的情況實際上是一個簡單的情況。簡單的噴油器支持更多有趣的場景,如基於謂詞或一個泛型類型約束條件將裝飾:

container.RegisterDecorator(
    typeof(ICommandHandler<>), 
    typeof(SomeDecorator<>), c => 
     c.ServiceType.GetGenericArguments()[0] == typeof(TestCommand)); 

通過提供謂詞到RegisterDecorator如果裝飾應用到某些註冊就可以控制。

另一種選擇是將通用類型約束應用於裝飾器。簡單的噴油器能夠處理泛型類型的限制:

// 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<>)); 

雖然我認爲那到底你可以在每個集裝箱這個工作,大多數容器將使這個很複雜來實現。這是簡單注射器的優點。

+1

謝謝你,史蒂文,這很有趣。我想我必須給SimpleInjector一個機會,並在我的下一個項目上嘗試它。我真的很喜歡泛型類型約束的第二個選項 - 這是我在開始在Autofac中實現時想到的。 – trailmax

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()); 
+0

這看起來很酷。我會放棄它。整個問題更像是一個沒有真實世界應用的難題。 – trailmax

相關問題