Lasse Christiansen
Lasse Christiansen

Reputation: 10325

Inject dependency with "constructor scope" - autofac

I am using autofac 3.5.x and I have a setup similar to this:

public class ServiceA : IServiceA { }
public class ServiceB : IServiceB { public ServiceB(IServiceA sa) { } }
public class ServiceC : IServiceC { public ServiceC(IServiceA sa) { } }
public class ServiceD : IServiceD { public ServiceD(IServiceA sa, IServiceB sb, IServiceC sc) {} }

In my container I have the following registrations:

builder.RegisterType<ServiceA>.As<IServiceA>();
builder.RegisterType<ServiceB>.As<IServiceB>();
builder.RegisterType<ServiceC>.As<IServiceC>();
builder.RegisterType<ServiceD>.As<IServiceD>();

Here is what I want: When I request a new IServiceD from the container, I want the same IServiceA instance to be injected into IServiceD and its dependencies IServiceB and IServiceC. I am not looking for a global singleton scope though. Next time I ask for an IServiceD instance it must be created with a new instance of IServiceA (I know you cannot create an instance of an interface, but I think you get the point).

To illustrate; when I ask the autofac container for an IServiceD I want the following to happen:

public class ServiceD : IServiceD
{
    public ServiceD(IServiceA sa, IServiceB sb, IServiceC sc)
    {
        // SA should be the same (but it is not)
        sa.GetHashCode() == sb.GetServiceA().GetHashCode() == sc.GetServiceA().GetHashCode()
    }
}

Please note that the GetServiceA() method is only included to illustrate my point.

So yeah, I guess I am looking for some way to tell autofac that when it resolves IServiceD it should create a singleton of ServiceA but only for the scope of ServiceD's constructor.

Right now, I use autofacs support for delegate factories, and ask for:

public ServiceD(IServiceA sa, Func<IServiceA, IServiceB> fb, Func<IServiceA, IServiceC> fc) : IServiceD
{
     var sb = fb(sa); // Manually inject the same instance of ServiceA
     var sc = fc(sa); // Manually inject the same instance of ServiceA

     // ServiceA is now the same instance
     sa.GetHashCode() == sb.GetServiceA().GetHashCode() == sc.GetServiceA().GetHashCode()
}

This gets me going, but I have a feeling it can be done better - which is why I am turning to the experts now.

Thanks in advance.

Upvotes: 4

Views: 2088

Answers (1)

Cyril Durand
Cyril Durand

Reputation: 16192

The InstancePerLifetimeScope may suit your need. It allows you to have a single instance per lifetime scope.

builder.RegisterType<ServiceA>().As<IServiceA>().InstancePerLifetimeScope();
builder.RegisterType<ServiceB>().As<IServiceB>().InstancePerLifetimeScope();
builder.RegisterType<ServiceC>().As<IServiceC>().InstancePerLifetimeScope();
builder.RegisterType<ServiceD>().As<IServiceD>().InstancePerLifetimeScope();

Then, each time you resolve IServiceD in a new scope you will have a single instance of IServiceA

using (ILifetimeScope scope = container.BeginLifetimeScope())
{
    // only an instance of IServiceA will be created for this scope
    scope.Resolve<IServiceD>(); 
}

If you can't create a new ILifetimeScope you can use the Owned<T> type which is a lightweight lifetimescope.

using (Owned<IServiceD> ownedD = container.Resolve<Owned<IServiceD>>())
{
    IServiceD serviceD = ownedD.Value;                 
}

Each time you resolve a Owned<IserviceD> a new ILifetimeScope will be created and only a single IServiceA will be created.

If it is not enough you can play with InstancePerMatchingLifetimeScope but it will make your code and registration quite difficult to read and debug. InstancePerLifetimeScope should be enough.

Another solution is to use this kind of registration.

builder.Register(ctx =>
{
    IServiceA serviceA = ctx.Resolve<IServiceA>();
    IServiceB serviceB = ctx.Resolve<IServiceB>(TypedParameter.From(serviceA));
    IServiceC serviceC = ctx.Resolve<IServiceC>(TypedParameter.From(serviceA));
    IServiceD serviceD = ctx.Resolve<IServiceD>(TypedParameter.From(serviceA), TypedParameter.From(serviceB), TypedParameter.From(serviceC));
    return serviceD;
}).As<IServiceD>();

But is not really elegant.

Upvotes: 3

Related Questions