Autofac pick nested implementation based on parent

2019-08-24 19:50发布

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?

1条回答
干净又极端
2楼-- · 2019-08-24 20:29

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.

查看更多
登录 后发表回答