2013-08-01 9 views
5

Wir haben eine Menge generische Befehlshandler, die von Autofac in Open-Generic Mode registriert sind. Wir haben Paardekorateure, die alle Griffe schmücken. Jetzt muss ich einen Decorator für nur einen Befehlshandler registrieren und nicht alle anderen Befehlshandler beeinflussen. Hier ist mein Versuch, aber ich glaube nicht, dass die Registrierung richtig ist.Registrierung Autofac Dekorator für nur einen generischen Befehl Handler

ist hier einfacher Test-Code, der in unseren Code ähnlich ist:

Wir hunderte von Befehlen haben wie diese Arbeit:

class NormalCommand : ICommand { } 

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

Und ich möchte wickeln NUR TestCommandHandler in Dekorateur 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 
    } 
} 

So registriere ich meine Komponenten:

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(); 
    } 
} 

Und das ist, wie ich versuche, zu bestätigen, dass ich richtig Instanzen von Befehlshandler/Dekorateure erhalten:

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! 
    } 
} 

Wie der Kommentar sagt, ist Dekorateur nicht angewandt zu werden, Dekorateur Registrierung ignoriert.

Irgendwelche Informationen, wie Sie diesen Dekorateur registrieren? Was mache ich falsch?

+0

Darf ich bieten eine Lösung unter Verwendung eines anderen DI-Container oder sind Sie mit Autofac verbunden? – Steven

+0

Ich bin gerade mit Autofac verbunden, aber wenn Sie Beispiele in Structure Map oder Windsor angeben können, werde ich mich auch darum kümmern. Für Bildungszwecke. – trailmax

Antwort

3

Nach meinen Kopf gegen Tastatur oft genug Schmatzen, habe ich eine Art von Lösung für mein Problem bekam:

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(); 
    } 
} 

Hier bin ich mit Dekorateur Funktionalität von Autofac nicht und manuell den Dekorateur wickeln. Wenn also die Anzahl der Abhängigkeiten im Decorator wächst, muss ich den Container aktualisieren, um alle erforderlichen Abhängigkeiten aufzulösen.

Wenn Sie eine bessere Lösung kennen, lassen Sie es mich wissen!

1

Ich kann keine Beispiele auf Castle Windsor oder StructureMap geben, und meiner Erfahrung nach ist es sehr schwierig, offene generische Dekoratoren mit etwas anderem als Autofac und Simple Injector anzuwenden. Wenn es darum geht, offene generische Dekoratoren bedingt zuzulassen (Ihr spezifisches Szenario), ist der AFAIK Simple Injector der einzige DI-Container mit Abstiegsunterstützung dafür.

mit einfachen Injector, registrieren Sie alle Befehlshandler wie folgt:

container.RegisterManyForOpenGeneric(
    typeof(ICommandHandler<>), 
    typeof(ICommandHandler<>).Assembly); 

Zierer registriert werden können wie folgt:

container.RegisterDecorator(
    typeof(ICommandHandler<>), 
    typeof(CommandHandlerDecorator1<>)); 

container.RegisterDecorator(
    typeof(ICommandHandler<>), 
    typeof(TestCommandHandlerDecorator)); 

container.RegisterDecorator(
    typeof(ICommandHandler<>), 
    typeof(CommandHandlerDecorator2<>)); 

Zierer werden in der Reihenfolge hinzugefügt, sie sind registriert, was bedeutet, dass in dem obigen Fall CommandHandlerDecorator2<T> wraps TestCommandHandlerDecorator, die CommandHandlerDecorator1<T> umschließt, die einen konkreten Befehlshandler umschließt. Da die TestCommandHandlerDecorator nur für eine bestimmte ICommandHandler<T> ist, ist es nur um solche Typen gewickelt. In Ihrem Fall sind Sie nach der vorherigen Registrierung fertig.

Aber Ihr Fall ist eigentlich ein einfacher Fall.Einfacher Injector unterstützt viel interessante Szenarien, wie zum Beispiel bedingt Dekorateure basierend auf einem Prädikat oder auf einer generischen Typ Einschränkung der Anwendung:

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

indem ein Prädikat zum RegisterDecorator Sie steuern können, wenn ein Dekorateur auf eine bestimmte Eintragung beantragt wird, .

Eine weitere Option besteht darin, generische Typ-Constraints auf den Decorator anzuwenden. Einfacher Injector ist in der Lage gattungsgemäße Einschränkungen zu handhaben:

// This decorator should be wrapped only around TestCommandHandler 
class TestCommandHandlerDecorator<T> : ICommandHandler<T> 
    where T : TestCommand // GENERIC TYPE CONSTRAINT 
{ 
    // ... 
} 

Dies ist nützlich, wenn Sie einen Befehls-Handler, die Befehle verarbeitet, die von TestCommand ableiten, aber oft werden Sie sehen, dass Befehle implementieren eine oder mehrere Schnittstellen und Dekorateure sind angewendet auf Befehlshandler, die einen Befehl mit einer dieser Schnittstellen behandeln.

Aber so oder so, kann der Dekorateur einfach registriert werden, wie folgt:

container.RegisterDecorator(
    typeof(ICommandHandler<>), 
    typeof(TestCommandHandlerDecorator<>)); 

Obwohl ich glaube, dass Sie am Ende dieses Arbeits in jedem Behälter bekommen, die meisten Container machen dies kompliziert wirklich zu erreichen. Hier zeichnet sich der Simple Injector aus.

+1

Danke, Steven, das ist ziemlich interessant. Ich schätze ich muss SimpleInjector eine Chance geben und es bei meinem nächsten Projekt ausprobieren. Ich mag die zweite Option mit generischen Typbeschränkungen - das war es, was ich im Hinterkopf hatte, als ich mit der Implementierung in Autofac begann. – trailmax

3

Um die manuelle Registrierung in @ Trailmax Antwort vermeiden Sie die folgende Erweiterung Methode definieren:

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

Und dann diese verwenden wie so:

 builder.RegisterDecorator<TestCommandHandler, TestCommandHandlerDecorator, ICommandHandler<TestCommand>>(
      s => s.InstancePerLifetimeScope(), 
      d => d.InstancePerLifetimeScope()); 
+0

Das sieht ziemlich cool aus. Ich werde es versuchen. Die ganze Frage war mehr wie ein Puzzle ohne reale Anwendung. – trailmax

Verwandte Themen