2013-08-01 11 views
5

Abbiamo molti gestori di comandi generici registrati da Autofac in modo generico aperto. Abbiamo un paio di decoratori che decorano tutte le maniglie. Ora ho bisogno di registrare un decoratore per un solo gestore di comandi e non influire su tutti gli altri gestori di comandi. Ecco il mio tentativo, ma non sembra che abbia ottenuto la registrazione giusta.Registrati Decoratore Autofac per un solo gestore di comandi generici

è semplice codice di prova che è simile al nostro codice:

Abbiamo centinaia di comandi che lavorano in questo modo:

class NormalCommand : ICommand { } 

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

E vorrei avvolgere SOLO TestCommandHandler in decoratore 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 
    } 
} 

Ecco come registro i miei componenti:

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

Ed è così che provo a confermare che ricevo corrette istanze di gestori di comando/decoratori:

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

Come il commento dice, decoratore non è sempre applicata, la registrazione decoratore viene ignorata.

Qualsiasi idi come registrare questo decoratore ?? Che cosa sto facendo di sbagliato?

+0

Posso fornire una soluzione che utilizza un altro contenitore DI o sei collegato ad Autofac? – Steven

+0

Al momento sono collegato ad Autofac, ma se riesci a fornire degli esempi in Structure Map o Windsor, sarò interessato anche a guardarlo. Per scopi educativi. – trailmax

risposta

3

Dopo smacking la testa contro tastiera abbastanza volte, ho un qualche tipo di soluzione al mio problema:

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

Qui non sto usando la funzionalità decoratore di Autofac e avvolgere il decoratore manualmente. Quindi, se il numero di dipendenze nel decoratore aumenta, avrò bisogno di aggiornare il contenitore per risolvere tutte le dipendenze richieste.

Se siete a conoscenza di una soluzione migliore, fatemelo sapere!

1

Non posso fornire esempi su Castle Windsor o StructureMap, e nella mia esperienza è molto difficile applicare decoratori generici aperti utilizzando qualcos'altro di Autofac e Simple Injector. Quando si tratta di applicare condizionatori generici aperti (lo scenario specifico) AFAIK Simple Injector è l'unico contenitore DI con supporto di discesa per questo.

Con Simple iniettore, si registra tutti i gestori di comando come segue:

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

decoratori possono essere registrati come segue:

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

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

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

Decoratori vengono aggiunti nell'ordine in cui sono registrati, il che significa quello nel caso precedente CommandHandlerDecorator2<T> include TestCommandHandlerDecorator che include CommandHandlerDecorator1<T> che include qualsiasi gestore di comandi concreti. Poiché lo TestCommandHandlerDecorator è per uno specifico ICommandHandler<T>, viene solo avvolto attorno a tali tipi. Quindi nel tuo caso, hai finito dopo aver fatto la registrazione precedente.

Ma il tuo caso è in realtà un caso semplice.Semplice iniettore supporta molto più interessanti scenari, come l'applicazione condizionatamente decoratori sulla base di un predicato o su un tipo di vincolo generico:

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

Fornendo un predicato al RegisterDecorator è possibile controllare se un decoratore è applicato ad una certa registrazione .

Un'altra opzione consiste nell'applicare vincoli di tipo generico al decoratore. Semplice iniettore è in grado di gestire vincoli di tipo generico:

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

Questo è utile quando si dispone di qualsiasi gestore di comando che gestisce i comandi che derivano da TestCommand, ma spesso vedrete che i comandi implementare una o più interfacce e decoratori sono applicato ai gestori di comandi che gestiscono un comando con una di queste interfacce.

Ma in entrambi i casi, il decoratore può semplicemente essere registrato come segue:

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

Anche se penso che alla fine si può ottenere questo lavoro in ogni contenitore, la maggior parte dei contenitori farà davvero complicato da raggiungere. Qui è dove Simple Injector eccelle.

+1

Grazie, Steven, è piuttosto interessante. Immagino che dovrò dare una possibilità a SimpleInjector e provarlo sul mio prossimo progetto. Mi piace molto la seconda opzione con il vincolo di tipo generico - questo è quello che avevo in mente quando ho iniziato ad implementare in Autofac. – trailmax

3

Per evitare la registrazione manuale, in risposta @ di Trailmax è possibile definire il seguente metodo di estensione:

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

E quindi utilizzare questo modo:

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

Sembra molto bello. Ci provo. L'intera domanda era più simile a un puzzle senza applicazione reale. – trailmax

Problemi correlati