11

Does Autofac support specifying the registration name in the components' constructors?

Example: Ninject's NamedAttribute.

John Merchant
  • 215
  • 1
  • 3
  • 10
  • 5
    The need for named injection is usually caused by ambiguity in your design and you should try to fix this root cause. Using those named attributes couples your code to the DI container and complicates your code, while you should normally keep this registration inside your Composition Root, or remove the ambiguity (and remove a possible [LSP violation](https://en.wikipedia.org/wiki/Liskov_substitution_principle)) by giving each abstraction its own unique interface. – Steven Jun 03 '14 at 08:10
  • This was required for a very specific use-case in the application, we had registered named `IReportService` components which took a dictionary of report parameters and returned `DataSet`s. The services were resolved in a ReportViewer.aspx page, with the `reportName` parameter passed in via the querystring. There was a case where we wanted to reuse a particular report's query in another service, I was aware at the time I was breaking OO design principles in order to reuse some code :) – John Merchant May 16 '20 at 06:07

2 Answers2

29

You need to use the Autofac.Extras.Attributed package on top to achieve this

So let's say you have one interface and two classes:

public interface IHello
{
    string SayHello();
}

public class EnglishHello : IHello
{
    public string SayHello()
    {
        return "Hello";
    }
}

public class FrenchHello : IHello
{
    public string SayHello()
    {
        return "Bonjour";
    }
}

Then you have a consumer class, which in you want to select which instance is injected:

public class HelloConsumer
{
    private readonly IHello helloService;

    public HelloConsumer([WithKey("EN")] IHello helloService)
    {
        if (helloService == null)
        {
            throw new ArgumentNullException("helloService");
        }
        this.helloService = helloService;
    }

    public string SayHello()
    {
        return this.helloService.SayHello();
    }
}

Registering and resolving:

ContainerBuilder cb = new ContainerBuilder();

cb.RegisterType<EnglishHello>().Keyed<IHello>("EN");
cb.RegisterType<FrenchHello>().Keyed<IHello>("FR");
cb.RegisterType<HelloConsumer>().WithAttributeFilter();
var container = cb.Build();

var consumer = container.Resolve<HelloConsumer>();
Console.WriteLine(consumer.SayHello());

Do not forget the AttributeFilter when you register such a consumer, otherwise resolve will fail.

Another way is to use a lambda instead of attribute.

cb.Register<HelloConsumer>(ctx => new HelloConsumer(ctx.ResolveKeyed<IHello>("EN")));

I find the second option cleaner since you also avoid to reference autofac assemblies in your project (just to import an attribute), but that part is a personal opinion of course.

mrvux
  • 8,523
  • 1
  • 27
  • 61
  • Excellent answer. Saves time digging through Autofac's reference docs. – crush Aug 22 '18 at 13:13
  • 3
    `[WithKey]` attribute is obsolete now. Use the `[KeyFilter]` attribute instead as per this [documentation](https://autofac.org/apidoc/html/9E1F2171.htm). `[ObsoleteAttribute("Use the Autofac.Features.AttributeFilters.KeyFilterAttribute from the core Autofac library instead.")] public sealed class WithKeyAttribute : ParameterFilterAttribute` – Sivaram Koduri Feb 18 '19 at 09:21
1
        var builder = new ContainerBuilder();

        builder.RegisterType<LiveGoogleAnalyticsClass>().Named<IGoogleAnalyticsClass>("Live");
        builder.RegisterType<StagingGoogleAnalyticsClass>().Named<IGoogleAnalyticsClass>("Staging");
        var container = builder.Build();

        var liveGAC = container.ResolveNamed<IGoogleAnalyticsClass>("Live");
        var stagingGAC = container.ResolveNamed<IGoogleAnalyticsClass>("Staging");

This just requires the standard Autofac dll, no reference of the Autofac.Extras.Attributed assembly is needed. I'm currently using Autofac 3.5.0 due to legacy requirements.