Search code examples
c#dependency-injectionautofacioc-container

Get multiple instances of an object registered via Autofac module registration


I have a use case where I need to get two copies of objects registered from Autofac module and pass them to register different types.

In the below code sample,

  1. I want to pass an object of DataProtectionService with config dbConfig to register MyDbRepo.
  2. Similarly, I want to pass an object of DataProtectionService with config cacheConfig to register MyCacheRepo.

But, I am not sure how should I do that? Am I missing something really basic here?

I don't have an option to remove this DataProtectionServiceModule. Is there a way to achieve what I am expecting?

public class DataProtectionServiceModule : Module
{
    private readonly string config;

    public DataProtectionServiceModule(string config)
    {
        this.config = config;
    }

    protected override void Load(ContainerBuilder builder)
    {
        builder.Register<Token>(compContext =>
        { // a complex logic here
        }).OnRelease(instance => instance.Dispose());

        builder.Register(c =>
        {
            // a logic to generate an object of IDataProtectionService
            // this logic involves the use of config field of this class.
        }).As<IDataProtectionService>();
    }
}

public class MyDbRepo : IMyDbRepo
{
    IDataProtectionService dataProtectionService;
    public MyDbRepo(IDataProtectionService dataProtectionService)
    {
        this.dataProtectionService = dataProtectionService;
    }
}

public class MyCacheRepo : IMyCacheRepo
{
    IDataProtectionService dataProtectionService;
    public MyCacheRepo(IDataProtectionService dataProtectionService)
    {
        this.dataProtectionService = dataProtectionService;
    }
}

// Program.cs of my backend micro service
var builder = new ContainerBuilder();
builder.RegisterModule(new DataProtectionServiceModule("dbConfig"));
builder.RegisterModule(new DataProtectionServiceModule("cacheConfig")); // I have figured, this is wrong.
builder.RegisterType<MyDbRepo>().As<IMyDbRepo>().SingleInstance();
builder.RegisterType<MyCacheRepo>().As<IMyCacheRepo>().SingleInstance();


Solution

  • You can use Keyed Services. And then in your registration add a specific Resolve.

        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
    
    
            // Program.cs of my backend micro service
            var builder = new ContainerBuilder();
            builder.RegisterModule(new DataProtectionServiceModule("dbConfig", DeviceState.Db));
            builder.RegisterModule(new DataProtectionServiceModule("cacheConfig", DeviceState.Cache)); // I have figured, this is wrong.
            builder.RegisterType<MyDbRepo>().WithParameter(
                new ResolvedParameter(
                    (pi, ctx) => pi.ParameterType == typeof(IDataProtectionService),
                    (pi, ctx) => ctx.ResolveKeyed<IDataProtectionService>(DeviceState.Db)
                )
            ).As<IMyDbRepo>().SingleInstance();
            builder.RegisterType<MyCacheRepo>().WithParameter(
                new ResolvedParameter(
                    (pi, ctx) => pi.ParameterType == typeof(IDataProtectionService),
                    (pi, ctx) => ctx.ResolveKeyed<IDataProtectionService>(DeviceState.Cache)
                )
            ).As<IMyCacheRepo>().SingleInstance();
    
            var container = builder.Build();
    
            var cache = container.Resolve<IMyCacheRepo>();
            var db = container.Resolve<IMyDbRepo>();
    
        }
    }
    
    public enum DeviceState { Cache, Db }
    
    
    public class DataProtectionServiceModule : Module
    {
        private readonly string config;
        private readonly DeviceState _state;
    
        public DataProtectionServiceModule(string config, DeviceState state)
        {
            this.config = config;
            _state = state;
        }
    
        protected override void Load(ContainerBuilder builder)
        {
            builder.Register<Token>(compContext =>
            { // a complex logic here
            }).OnRelease(instance => instance.Dispose());
    
            builder.Register(c =>
            {
                // a logic to generate an object of IDataProtectionService
                // this logic involves the use of config field of this class.
                return new DataProtectionService(config);
            }).Keyed<IDataProtectionService>(_state);
        }
    }
    
    public class DataProtectionService : IDataProtectionService
    {
        public string Config { get; }
    
        public DataProtectionService(string config)
        {
            Config = config;
        }
    }
    
    
    public class MyDbRepo : IMyDbRepo
    {
        IDataProtectionService dataProtectionService;
        public MyDbRepo(IDataProtectionService dataProtectionService)
        {
            this.dataProtectionService = dataProtectionService;
        }
    }
    
    public interface IDataProtectionService
    {
    }
    
    public interface IMyDbRepo
    {
    }
    
    public class MyCacheRepo : IMyCacheRepo
    {
        IDataProtectionService dataProtectionService;
        public MyCacheRepo(IDataProtectionService dataProtectionService)
        {
            this.dataProtectionService = dataProtectionService;
        }
    }
    
    public interface IMyCacheRepo
    {
    }
    

    Alternatively you can use Named Services and the config-name dbConfig and cacheConfig. This way you do not need to change the constructor of DataProtectionServiceModule.

        builder.RegisterType<MyDbRepo>().WithParameter(
            new ResolvedParameter(
                (pi, ctx) => pi.ParameterType == typeof(IDataProtectionService),
                (pi, ctx) => ctx.ResolveNamed<IDataProtectionService>("dbConfig")
            )
        ).As<IMyDbRepo>().SingleInstance();
        builder.RegisterType<MyCacheRepo>().WithParameter(
            new ResolvedParameter(
                (pi, ctx) => pi.ParameterType == typeof(IDataProtectionService),
                (pi, ctx) => ctx.ResolveNamed<IDataProtectionService>("cacheConfig")
    

    And your module:

        protected override void Load(ContainerBuilder builder)
        {
            //builder.Register<Token>(compContext =>
            //{ // a complex logic here
            //}).OnRelease(instance => instance.Dispose());
    
            builder.Register(c =>
            {
                // a logic to generate an object of IDataProtectionService
                // this logic involves the use of config field of this class.
                return new DataProtectionService(config);
            }).Named<IDataProtectionService>(config);
        }