How to use Autofac to inject decorator of a class in one constructor and the class itself in another?

1k views Asked by At

I have an IoCConfig where in the RegisterDependencies method first all Services (same assembly as ServiceBase) are registered, except for one service and one class called LastActivityUpdator and a decorator of this class called AnonymousUserLastActivityUpdator which both implement ILastActivityUpdator.

The goals I want to accomplish is to have the decorator registered properly, which already works following this answer. And I also asked for a way to for one class (UserService) use the LastActivityUpdator and for another (AnonymousUserService) use the AnonymousUserLastActivityUpdator, which was answered here. To be complete (I don't think it is relevant to this problem), I am using a keyed service following this answer to decide which class is injected in other times.

Unfortunately, when I combine the ideas I always see an AnonymousUserLastActivityUpdator injected, also in the case of UserService where I want a LastActivityUpdator to be provided.

Below you'' find the IocConfig which uses both the idea to register the decorator and to provide specific classes with one type:

builder.RegisterAssemblyTypes(typeof(ServiceBase).Assembly)
    .Except<AnonymousUserService>()
    .Except<LastActivityUpdator>()
    .Except<AnonymousUserLastActivityUpdator>()
    .AsImplementedInterfaces()
    .InstancePerRequest();

builder.RegisterType<LastActivityUpdator>()
    .Named<ILastActivityUpdator>("lastActivityUpdator");
builder.RegisterType<AnonymousUserLastActivityUpdator>()
    .Named<ILastActivityUpdator>("anonymousUserLastActivityUpdator");

builder.RegisterDecorator<ILastActivityUpdator>(
    (c, inner) => c.ResolveNamed<ILastActivityUpdator>("anonymousUserLastActivityUpdator", 
        TypedParameter.From(inner)),
    fromKey: "lastActivityUpdator")
    .As<ILastActivityUpdator>();

builder.Register(c => new UserService(
    c.Resolve<OogstplannerUnitOfWork>(),
    c.Resolve<CookieProvider>(),
    c.Resolve<ILastActivityUpdator>()));
builder.Register(c => new AnonymousUserService(
    c.Resolve<OogstplannerUnitOfWork>(),
    c.Resolve<CookieProvider>(),
    c.Resolve<AnonymousUserLastActivityUpdator>()));

builder.RegisterType<AnonymousUserService>()
    .Keyed<IUserService>(AuthenticatedStatus.Anonymous)
    .InstancePerRequest();
builder.RegisterType<UserService>()
    .Keyed<IUserService>(AuthenticatedStatus.Authenticated)
    .InstancePerRequest();

Unfortunately this does not work. Both the AnonymousUserService and the UserService get an instance of the LastActivityUpdator, while I want AnonymousUserService to get an instance of the AnonymousUserService.

Does anyone know why my RegisterDependencies method does not provide the LastActivityUpdator to UserService and how I can accomplish this?

/edit

Using the answer of Cyril and some adjustments the registering works for the UserService, but once the user logs out and a AnonymousUserService is used I get the error:

Circular component dependency detected:
Services.AnonymousUserService ->
Services.AnonymousUserService ->
Services.AnonymousUserLastActivityUpdator ->
Services.AnonymousUserLastActivityUpdator -> System.Object ->
Services.AnonymousUserLastActivityUpdator

I adjusted the code somewhat of Cyril's answer (fixed some typos and switched the anonymous- and normal UserService) and I might have made a mistake, here it is:

builder.RegisterAssemblyTypes(typeof(ServiceBase).Assembly)
    .Except<AnonymousUserService>()
    .Except<LastActivityUpdator>()
    .Except<AnonymousUserLastActivityUpdator>()
    .AsImplementedInterfaces()
    .InstancePerRequest();

builder.RegisterType<LastActivityUpdator>()
    .Named<ILastActivityUpdator>("lastActivityUpdator");
builder.RegisterType<AnonymousUserLastActivityUpdator>()
    .Named<ILastActivityUpdator>("anonymousUserLastActivityUpdator");

builder.RegisterDecorator<ILastActivityUpdator>(
    (c, inner) => c.ResolveNamed<ILastActivityUpdator>("anonymousUserLastActivityUpdator", 
        TypedParameter.From(inner)),
    fromKey: "lastActivityUpdator")
    .As<ILastActivityUpdator>();

builder.RegisterType<UserService>()
    .Keyed<IUserService>(AuthenticatedStatus.Authenticated)
    .WithParameter(
        (p, c) => p.Name == "lastActivityUpdator", 
        (p, c) => c.ResolveNamed<ILastActivityUpdator>("lastActivityUpdator"))
    .InstancePerRequest();
builder.RegisterType<AnonymousUserService>()
    .Keyed<IUserService>(AuthenticatedStatus.Anonymous)
    .WithParameter(
        (p, c) => p.Name == "anonymousUserLastActivityUpdator", 
        (p, c) => c.ResolveNamed<ILastActivityUpdator>("anonymousUserLastActivityUpdator"))
    .InstancePerRequest();

var container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
1

There are 1 answers

4
Cyril Durand On BEST ANSWER

You have 2 kind of registrations for UserService and AnonymousUserService.

In the first solution you register AnonymousUserService by explicitly calling the constructor and by asking Autofac to resolve a AnonymousUserLastActivityUpdator but there is no registration for this service. Instead of resolving this type, you can resolve a named ILastActivityUpdator

builder.Register(c => new UserService(
    c.Resolve<OogstplannerUnitOfWork>(),
    c.Resolve<CookieProvider>(),
    c.Resolve<ILastActivityUpdator>()));
builder.Register(c => new AnonymousUserService(
    c.Resolve<OogstplannerUnitOfWork>(),
    c.Resolve<CookieProvider>(),
    c.ResolveNamed<ILastActivityUpdator>("anonymousUserLastActivityUpdator")));

The second registration is a Keyed but you don't specify that the AnonymousUserService should use a anonymousUserLastActivityUpdater you could do it by using the WithParameter method

builder.RegisterType<AnonymousUserService>()
       .Keyed<IUserService>(AuthenticatedStatus.Authenticated)
       .InstancePerRequest();
builder.RegisterType<UserService>()
       .Keyed<IUserService>(AuthenticatedStatus.Anonymous)
       .WithParameter(
         (pi, c) => pi.Name == "updater", 
         (pi, c) => ResolveNamed<ILastActivityUpdator>("anonymousUserLastActivityUpdator"))
       .InstancePerRequest();

You don't have to have both registration, the last one should be enough for your scenario.