tjmn
tjmn

Reputation: 519

Inject two objects of same type using autofac

I have been trying to register and inject two different objects of the same type using autofac, but I am unable to get the second object injected successfully. The second object injected is always either the same instance of type 1 registered first when using SingleInstance() or another instance of type 1 when InstancePerLifetimeScope is used. InstancePerDependency doesn't work either. Any suggestions on how this can be achieved? Thanks!

Found a similar unsolved question - Inject multiple instances of same type - Autofac

    /* Register named object of type1 */
    builder.RegisterType<TestClient>()
             .As<TestClient>()
             .UsingConstructor(typeof(Settings), typeof(SampleEnum))
             .WithParameters(new[]{
                new ResolvedParameter((p, c) => p.ParameterType.IsAssignableTo<Settings>(), (p, c) => c.Resolve<Settings>()),
                new ResolvedParameter((p, c) => p.ParameterType == typeof(SampleEnum) && p.Name == "eventtype",(p, c) => SampleEnum.Type1),
                }).Named<TestClient>(clientoftype1).InstancePerDependency();

    /* Register named object of type2 */
    builder.RegisterType<TestClient>()
              .As<TestClient>()
             .UsingConstructor(typeof(Settings), typeof(SampleEnum))
             .WithParameters(new[]{
                new ResolvedParameter((p, c) => p.ParameterType.IsAssignableTo<Settings>(), (p, c) => c.Resolve<Settings>()),
                new ResolvedParameter((p, c) => p.ParameterType == typeof(SampleEnum) && p.Name == "eventtype",(p, c) => SampleEnum.Type2),
                }).Named<TestClient>(clientoftype2)).InstancePerDependency();

    /*Controller registration
        public DevController(TestClient clientoftype1, TestClient clientoftype2)
        {..}
     */         
    builder
    .RegisterType<DevController>()
    .As<DevController>()
    .WithParameters(new []{
        ResolvedParameter.ForNamed<TestClient>("clientoftype1"),
        ResolvedParameter.ForNamed<TestClient>("clientoftype2"),
    } ).InstancePerRequest();

   /*Check registered types*/
   var types = container.ComponentRegistry.Registrations
     .Where(r => typeof(TestClient).IsAssignableFrom(r.Activator.LimitType))
     .Select(r => r.Activator.LimitType);
   var countofobjects = types.ToList();//This has 2 objects.

Upvotes: 0

Views: 1132

Answers (1)

Travis Illig
Travis Illig

Reputation: 23894

Generally speaking what you're doing - requiring more than one of the same type in the constructor but as separate parameters is sort of a dependency injection no-no. That is, this is generally something to avoid doing:

public class Consumer
{
  public Consumer(Dependency a, Dependency b) { /* ... */ }
}

The reason for that is that DI, as you've found, primarily works on type-based injection. This isn't unique to .NET or Autofac, that's just how it is. From a design perspective, I'd probably ask why you don't, instead, do something like...

public class Consumer
{
  public Consumer(IEnumerable<Dependency> dependencies) { /* ... */ }
}

I'd ask that because if you can't treat the two dependencies the same it's sort of a violation of the Liskov substitution principle and you'd actually want to have different interfaces (even if they look the same) to differentiate the two things.

public class Consumer
{
  public Consumer(IDependencyA a, IDependencyB b) { /* ... */ }
}

However, assuming you can't redesign things, you can use named services along with the KeyFilterAttribute to get what you want.

Here's a complete, minimal console app showing how it works.

using System;
using Autofac;
using Autofac.Features.AttributeFilters;

namespace AutofacDemo
{
    public static class Program
    {
        public static void Main()
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<Consumer>().WithAttributeFiltering();
            builder.RegisterType<Dependency>().SingleInstance().AsSelf().Named<Dependency>("a");
            builder.RegisterType<Dependency>().SingleInstance().AsSelf().Named<Dependency>("b");
            using var container = builder.Build();

            var consumer = container.Resolve<Consumer>();
            consumer.WriteInfo();
        }
    }

    public class Consumer
    {
        private readonly Dependency _a;
        private readonly Dependency _b;

        public Consumer([KeyFilter("a")]Dependency a, [KeyFilter("b")] Dependency b)
        {
            this._a = a;
            this._b = b;
        }

        public void WriteInfo()
        {
            Console.WriteLine("A: {0}", this._a.Id);
            Console.WriteLine("B: {0}", this._b.Id);
        }
    }

    public class Dependency
    {
        public Dependency()
        {
            this.Id = Guid.NewGuid();
        }

        public Guid Id { get; }
    }
}

When you run this, you'll get two different IDs, like:

A: 542f8ae9-bd04-4821-a3e5-5eb3c41bbbc6
B: cb9b8245-c12e-4928-b618-0ecbf0a75a84

If you were to remove the filter attributes then you'd get the same ID because, as you saw, injecting by type will end up with the same instance each time - last in wins. The filtering is the magic that makes it work.

Upvotes: 3

Related Questions