2013-08-01 13 views
5

Mamy wiele ogólnych procedur obsługi komend, które są rejestrowane przez Autofac w sposób otwarty generyczny. Mamy kilka dekoratorów, które zdobią wszystkie uchwyty. Teraz muszę zarejestrować dekorator tylko dla jednej procedury obsługi i nie wpływać na wszystkie inne procedury obsługi komend. Oto moja próba, ale wydaje mi się, że nie mam prawa do rejestracji.Zarejestruj dekorator Autofac tylko dla jednej ogólnej obsługi komend

Oto prosty kod test, który jest podobny do naszego kodu:

mamy setki poleceń roboczych tak:

class NormalCommand : ICommand { } 

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

i chciałbym zawinąć TYLKO TestCommandHandler w dekoratora 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 
    } 
} 

W ten sposób rejestruję swoje komponenty:

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

I tak staram się potwierdzić, że mam odpowiednie instancje wozy dowodzenia/dekoratorów:

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

Jako komentarz mówi dekorator nie jest stosowana coraz rejestracja dekorator jest ignorowany.

Jakieś sposoby rejestracji tego dekoratora? Co ja robię źle?

+0

Mogę zapewnić rozwiązanie przy użyciu innego kontenera DI czy jesteś uzależniony od Autofac? – Steven

+0

Jestem teraz uzależniony od Autofac, ale jeśli możesz podać przykłady w Mapie Struktury lub Windsor, również będę zainteresowany. Do celów edukacyjnych. – trailmax

Odpowiedz

3

Po bicie głową na klawiaturze tyle razy, mam jakieś rozwiązanie mojego problemu:

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

Tu nie używam funkcjonalność dekorator z Autofac i owijania dekorator ręcznie. Więc jeśli liczba zależności w dekoratorze rośnie, będę musiał zaktualizować kontener, aby rozwiązać wszystkie wymagane zależności.

Jeśli znasz lepsze rozwiązanie, proszę dać mi znać!

1

Nie mogę podać żadnych przykładów na Castle Windsor lub StructureMap, a z mojego doświadczenia jest bardzo trudno zastosować otwarte generyczne dekoratory za pomocą czegokolwiek innego niż Autofac i Simple Injector. Jeśli chodzi o warunkowe stosowanie otwartych generycznych dekoratorów (konkretny scenariusz) AFAIK Simple Injector jest jedynym kontenerem DI z obsługą zniżania dla tego.

z prostymi Injector, zarejestrować wszystkie programy obsługi poleceń w następujący sposób:

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

dekoratorzy mogą być rejestrowane w następujący sposób:

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

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

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

Dekoratorzy są dodawane w kolejności, w jakiej zostały zarejestrowane, co oznacza, że w powyższym przypadku CommandHandlerDecorator2<T> zawija TestCommandHandlerDecorator, który otacza CommandHandlerDecorator1<T>, który otacza dowolny konkretny moduł obsługi poleceń. Ponieważ TestCommandHandlerDecorator jest dla jednego konkretnego ICommandHandler<T>, jest tylko owinięty wokół takich typów. W twoim przypadku skończysz po poprzedniej rejestracji.

Ale twój przypadek jest w rzeczywistości prostym przypadkiem.Proste Injector obsługuje wiele bardziej interesujące scenariusze, takie jak warunkowo stosowanie dekoratorów na podstawie orzecznika lub na ogólny rodzaj przymusu:

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

dostarczając predykat do RegisterDecorator można kontrolować, czy dekorator stosowana jest do pewnego rejestracji .

Inną opcją jest zastosowanie ogólnych ograniczeń typu do dekoratora. Proste wtryskiwacz jest w stanie obsłużyć ograniczenia typu rodzajowego:

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

Funkcja ta jest przydatna, gdy masz obsługi poleceń, która obsługuje polecenia, które wynikają z TestCommand, ale często zobaczysz, że polecenia wdrożenia jednego lub wielu interfejsów i dekoratorów stosowane do procedur obsługi komend, które obsługują polecenia z jednym z tych interfejsów.

Ale tak czy inaczej, dekorator może po prostu być rejestrowane w następujący sposób:

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

Chociaż myślę, że w końcu można uzyskać tej pracy w każdym kontenerze, większość pojemników będzie to bardzo skomplikowane do osiągnięcia. To tutaj wyróżnia się Simple Injector.

+1

Dziękuję, Steven, to całkiem interesujące. Chyba będę musiał dać szansę SimpleInjector i wypróbować go w następnym projekcie. Bardzo podoba mi się druga opcja z ogólnym ograniczeniem typu - to właśnie miałem na myśli, gdy zacząłem wdrażać w Autofac. – trailmax

3

Aby uniknąć ręcznej rejestracji w odpowiedzi @ TRAILMAX może zdefiniować następujące metody rozszerzenie:

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

a następnie użyć tego tak:

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

To wygląda całkiem fajnie. Zrobię to. Całe pytanie było raczej zagadką bez aplikacji w świecie rzeczywistym. – trailmax

Powiązane problemy