Given the example dependency tree below I would like to select the bottom level Config
instance based on the top level dependency being resolved from the container e.g. TopLevelMessageConsumer
would resolve the same IMessageService
& IMessageQueue
implementations as TopLevelMessageDispatcher
but each would have their own Config
instance.
- TopLevelMessageConsumer
- IMessageService
- IMessageQueue
- Config
- TopLevelMessageDispatcher
- IMessageService
- IMessageQueue
- Config
I know this is possible using Keyed
or Named
but this requires each dependency in the tree to be registered n times varying by the number of config variations. Resulting in:
containerBuilder.RegisterInstance(config1).Keyed<Config>(Key.One).SingleInstance();
containerBuilder.RegisterInstance(config2).Keyed<Config>(Key.Two).SingleInstance();
containerBuilder.RegisterType<MessageQueue>().Keyed<IMessageQueue>(Key.One).SingleInstance()
.WithParameter(ResolvedParameter.ForKeyed<Config>(Key.One));
containerBuilder.RegisterType<MessageQueue>().Keyed<IMessageQueue>(Key.Two).SingleInstance()
.WithParameter(ResolvedParameter.ForKeyed<Config>(Key.Two));
containerBuilder.RegisterType<MessageService>().Keyed<IMessageService>(Key.One).SingleInstance()
.WithParameter(ResolvedParameter.ForKeyed<IMessageQueue>(Key.One));
containerBuilder.RegisterType<MessageService>().Keyed<IMessageService>(Key.Two).SingleInstance()
.WithParameter(ResolvedParameter.ForKeyed<IMessageQueue>(Key.Two));
containerBuilder.RegisterType<TopLevelMessageConsumer>().AsSelf().SingleInstance()
.WithParameter(ResolvedParameter.ForKeyed<IMessageService>(Key.One));
containerBuilder.RegisterType<TopLevelMessageDispatcher>().AsSelf().SingleInstance()
.WithParameter(ResolvedParameter.ForKeyed<IMessageService>(Key.Two));
Is there a better/cleaner way to register them?
You can simplify this greatly by using generic interfaces instead of explicit DI registration. The only condition is that you need to use some sort of type to differentiate between the services. In this case, making separate configuration classes would be a natural fit.
Config
public class ConsumerConfig : IConfig { }
public class DispatcherConfig : IConfig { }
Interfaces
// Define interface of config here (you may opt for abstract class instead)
public interface IConfig { }
public interface IMessageService<TConfig> { }
public interface IMessageQueue<TConfig> { }
Services
public class MessageService<TConfig> : IMessageService<TConfig> where TConfig : IConfig
{
public MessageService(IMessageQueue<TConfig> messageQueue)
{
}
}
public class MessageQueue<TConfig> : IMessageQueue<TConfig> where TConfig : IConfig
{
public MessageQueue(TConfig config)
{
}
}
public class TopLevelMessageDispatcher
{
public TopLevelMessageDispatcher(IMessageService<DispatcherConfig> messageService)
{
}
}
public class TopLevelMessageConsumer
{
public TopLevelMessageConsumer(IMessageService<ConsumerConfig> messageService)
{
}
}
Usage
class Program
{
static void Main(string[] args)
{
// Begin composition root
var containerBuilder = new ContainerBuilder();
var config1 = new ConsumerConfig();
var config2 = new DispatcherConfig();
containerBuilder.RegisterInstance(config1).AsSelf().SingleInstance();
containerBuilder.RegisterInstance(config2).AsSelf().SingleInstance();
containerBuilder.RegisterGeneric(typeof(MessageQueue<>))
.As(typeof(IMessageQueue<>)).SingleInstance();
containerBuilder.RegisterGeneric(typeof(MessageService<>))
.As(typeof(IMessageService<>)).SingleInstance();
containerBuilder.RegisterType<TopLevelMessageConsumer>()
.AsSelf().SingleInstance();
containerBuilder.RegisterType<TopLevelMessageDispatcher>()
.AsSelf().SingleInstance();
var container = containerBuilder.Build();
// End composition root
var dispatcher = container.Resolve<TopLevelMessageDispatcher>();
var consumer = container.Resolve<TopLevelMessageConsumer>();
}
}
Note that the implementation of the services don't necessarily need to worry about the fact the type is generic - the only thing that needs to change is the constructor signature to make a more explicit call for the generic type.