0

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.
tjmn
  • 469
  • 1
  • 6
  • 11
  • Could you elaborate a bit on what exactly your expectations are? It's unclear to me what you are actually aiming for. Why do you need the same type injected twice? A [mre] to illustrate the problem would be nice. – Xerillio Mar 04 '21 at 17:25
  • @Xerillio - I want to register a Controller which has a Constructor shown below by injecting two different objects of type TestClient. In my case, the object created clientoftype1 is being injected for both the parameters. public DevController(TestClient clientoftype1, TestClient clientoftype2) – tjmn Mar 05 '21 at 07:05
  • 1
    That seems like an odd requirement. Why do you need two of the same type? Perhaps you should inject a factory instead and create the two objects through that? – Xerillio Mar 05 '21 at 17:42
  • Thanks for the input @Xerillio. The project that I am working on has a lot of legacy code and it certainly requires some cleaning up. – tjmn Mar 06 '21 at 05:19

1 Answers1

2

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.

Travis Illig
  • 23,195
  • 2
  • 62
  • 85
  • Thanks @travis. The version of autofac used in the project(it's legacy code!) is 4.6.1 doesn't seem to support WithAttributeFiltering(). However, as you rightly pointed out, this is a design problem and it is better to clean up the approach. – tjmn Mar 06 '21 at 05:19
  • For older Autofac, look for the Autofac.Extras.AttributeMetadata package. I think the filtering used to be there before moving into core Autofac. But, yeah, if you can fix the issue, do that instead. – Travis Illig Mar 06 '21 at 05:22