Andrei M
Andrei M

Reputation: 3439

How to resolve interface based on service where it's passed to

I have an interface.

public interface ISomeInterface {...}

and two implementations (SomeImpl1 and SomeImpl2):

public class SomeImpl1 : ISomeInterface {...}
public class SomeImpl2 : ISomeInterface {...}

I also have two services where I inject ISomeInterface (via contructor):

public class Service1 : IService1 
{
   public Service1(ISomeInterface someInterface)
   {
   }
...
}

and

public class Service2 : IService2 
{
   public Service2(ISomeInterface someInterface)
   {
   }
...
}

I'm using Autofac as my IoC tool. The question. How can I configure Autofac registrations so SomeImpl1 will be automatically injected into Service1, and SomeImpl2 will be automatically injected into Service2.

Thank you!

Upvotes: 27

Views: 24207

Answers (3)

Ivan
Ivan

Reputation: 312

Four variants of doing this are described in autofac documentation:

Option 1: Redesign Your Interfaces

When you run into a situation where you have a bunch of components that implement identical services but they can’t be treated identically, this is generally an interface design problem.

From an object oriented development perspective, you’d want your objects to adhere to the Liskov substitution principle and this sort of breaks that.

By doing some interface redesign, you don’t have to “choose a dependency by context” - you use the types to differentiate and let auto-wireup magic happen during resolution.

If you have the ability to affect change on your solution, this is the recommended option.

Option 2: Change the Registrations

You can manually associate the appropriate type with the consuming component in that way:

var builder = new ContainerBuilder();
builder.Register(ctx => new ShippingProcessor(new PostalServiceSender()));
builder.Register(ctx => new CustomerNotifier(new EmailNotifier()));
var container = builder.Build();

// Lambda registrations resolve based on the specific type, not the
// ISender interface.
builder.Register(ctx => new ShippingProcessor(ctx.Resolve<PostalServiceSender>()));
builder.Register(ctx => new CustomerNotifier(ctx.Resolve<EmailNotifier>()));
var container = builder.Build();

Option 3: Use Keyed Services

builder.RegisterType<PostalServiceSender>()
           .As<ISender>()
           .Keyed<ISender>("order");
    builder.RegisterType<EmailNotifier>()
           .As<ISender>()
           .Keyed<ISender>("notification");

builder.RegisterType<ShippingProcessor>()
           .WithParameter(
             new ResolvedParameter(
               (pi, ctx) => pi.ParameterType == typeof(ISender),
               (pi, ctx) => ctx.ResolveKeyed<ISender>("order")));
    builder.RegisterType<CustomerNotifier>();
           .WithParameter(
             new ResolvedParameter(
               (pi, ctx) => pi.ParameterType == typeof(ISender),
               (pi, ctx) => ctx.ResolveKeyed<ISender>("notification")));

Option 4: Use Metadata

builder.RegisterType<PostalServiceSender>()
           .As<ISender>()
           .WithMetadata("SendAllowed", "order");
    builder.RegisterType<EmailNotifier>()
           .As<ISender>()
           .WithMetadata("SendAllowed", "notification");

builder.RegisterType<ShippingProcessor>()
           .WithParameter(
             new ResolvedParameter(
               (pi, ctx) => pi.ParameterType == typeof(ISender),
               (pi, ctx) => ctx.Resolve<IEnumerable<Meta<ISender>>>()
                               .First(a => a.Metadata["SendAllowed"].Equals("order"))));
    builder.RegisterType<CustomerNotifier>();
           .WithParameter(
             new ResolvedParameter(
               (pi, ctx) => pi.ParameterType == typeof(ISender),
               (pi, ctx) => ctx.Resolve<IEnumerable<Meta<ISender>>>()
                               .First(a => a.Metadata["SendAllowed"].Equals("notification"))));

Upvotes: 2

Steven
Steven

Reputation: 172646

If you can switch from constructor injection to property injection, and let both services derive from the same base class (or implement the same interface), you can do the following:

builder.RegisterType<ServiceBase>().OnActivating(e =>
{
    var type = e.Instance.GetType();

    // get ISomeInterface based on instance type, i.e.:
    ISomeInterface dependency =
        e.Context.ResolveNamed<ISomeInterface>(type.Name);

    e.Instance.SomeInterface = dependency;
});

For this to work you need to define the property on the base type (or interface). This solution is very flexible and would even allow you to complex things such as injecting a generic type, based on the parent type, as can be see below:

builder.RegisterType<ServiceBase>().OnActivating(e =>
{
    var type = 
       typeof(GenericImpl<>).MakeGenericType(e.Instance.GetType());

    e.Instance.SomeInterface = (ISomeInterface)e.Context.Resolve(type);
});

This approach has a few downsides:

  1. We need property injection.
  2. We need a base type or interface that contains that property.
  3. We need reflection to build the type, which might have an impact on performance (instead of the container building an efficient delegate) (although we might be able to speed things up by caching the type).

On the upside, this design is simple and works for almost any container.

Upvotes: 4

bentayloruk
bentayloruk

Reputation: 4101

Autofac supports identification of services by name. Using this, you can register your implementations with a name (using the Named extension method). You can then resolve them by name in the IServiceX registration delegates, using the ResolveNamed extension method. The following code demonstrates this.

var cb = new ContainerBuilder();
cb.Register(c => new SomeImpl1()).Named<ISomeInterface>("impl1");
cb.Register(c => new SomeImpl2()).Named<ISomeInterface>("impl2");
cb.Register(c => new Service1(c.ResolveNamed<ISomeInterface>("impl1"))).As<IService1>();
cb.Register(c => new Service2(c.ResolveNamed<ISomeInterface>("impl2"))).As<IService2>();
var container = cb.Build();

var s1 = container.Resolve<IService1>();//Contains impl1
var s2 = container.Resolve<IService2>();//Contains impl2

Alternative using RegisterType (as opposed to Register)

You can achieve the same result using the RegisterType extension method in combination with WithParameter and ResolvedParameter. This is useful if the constructor taking a named parameter also takes other non-named parameters that you don't care to specify in a registration delegate:

var cb = new ContainerBuilder();
cb.RegisterType<SomeImpl1>().Named<ISomeInterface>("impl1");
cb.RegisterType<SomeImpl2>().Named<ISomeInterface>("impl2");
cb.RegisterType<Service1>().As<IService1>().WithParameter(ResolvedParameter.ForNamed<ISomeInterface>("impl1"));
cb.RegisterType<Service2>().As<IService2>().WithParameter(ResolvedParameter.ForNamed<ISomeInterface>("impl2"));
var container = cb.Build();

var s1 = container.Resolve<IService1>();//Contains impl1
var s2 = container.Resolve<IService2>();//Contains impl2

Upvotes: 34

Related Questions