Search code examples
c#dependency-injectionsimple-injector

SimpleInjector: how to register proxy class for several interfaces at once


I'm developing a Telegram bot. And I'm struggling with SimpleInjector class registration. I need to register a class ProxyBotMesssageSender as a proxy for BotMesssageSender class that implements 3 interfaces: IMessageSender,IMessageForwarder,IMessageCopier

The whole structure:

//Provides sending of new messages
public interface IMessageSender{
  IObservable<Message> ObservableSend(SendMessage message);
  IObservable<Message> ObservableSend(MessageBuilder messageBuilder);
}

//Provides forwarding of existing messages
public interface IMessageForwarder{
  IObservable<Message> Forward(ForwardMessage message);
  IObservable<MessageIdObject[]> Forward(ForwardMessages messages);
}

//Provides copying of existing messages
public interface IMessageCopier{
  IObservable<MessageIdObject> Copy(CopyMessage message);
  IObservable<MessageIdObject[]> Copy(CopyMessages messages);
}

//Wrapper for third party library
public class BotMesssageSender : IMessageSender, IMessageForwarder, IMessageCopier{
    public BotMesssageSender(TelegramBot bot)
    {
      this.bot = bot;
    }
...
}

//Proxy that provides processing of sending limits
//It creates inner disposable object that provides information 
//for the class when it's allowed to send next message
public class ProxyBotMesssageSender : AbstractBotMessageSender, IMessageSender, IMessageForwarder, IMessageCopier, IDisposable{

  public BotMessageSenderTimerProxy(IMessageSender sender, IMessageCopier messageCopier, IMessageForwarder messageForwarder)
  {
    this.sender = sender;
    this.messageCopier = messageCopier;
    this.messageForwarder = messageForwarder;
...
  }
}

My current registration instructions:

  public override void Install()
  {
    Container.Register<IMessageSender,BotMesssageSender>(Lifestyle.Singleton);
    Container.Register<IMessageForwarder,BotMesssageSender>(Lifestyle.Singleton);
    Container.Register<IMessageCopier,BotMesssageSender>(Lifestyle.Singleton);
    
    Container.RegisterDecorator<IMessageSender,BotMessageSenderTimerProxy>(Lifestyle.Singleton);
    Container.RegisterDecorator<IMessageForwarder,BotMessageSenderTimerProxy>(Lifestyle.Singleton);
    Container.RegisterDecorator<IMessageCopier,BotMessageSenderTimerProxy>(Lifestyle.Singleton);
...
    }

But as I expected, the injector tries to decorate each interface one by one. Is it possible to register proxy somehow, that injector will apply it as a decorator for the interfaces without breaking the DI principle?


Update for @Steven

void Method(){
  var telegramBot = new TelegramBot();
  var sender = new BotMesssageSender(telegramBot);
  var generalSender = new BotMessageSenderProxy(sender, sender, sender);
  IMessageSender messageSender = generalSender;
  IMessageCopier messageCopier = generalSender;
  IMessageForwarder messageForwarder = generalSender;
} 

Solution

  • You are trying to use BotMessageSenderProxy as a decorator, while it doesn't fit Simple Injector's definition of a decorator. This is because you are trying to register BotMessageSenderProxy as decorator for three interfaces, while, from Simple Injector's perspective, a decorator class should only be used to decorate on specific interface.

    In your current design and registration, that would lead to a cyclic dependency, as the other two interfaces would receive the decorator as well. This is what the object graph would look like when resolving the IMessageSender:

    var bot = new TelegramBot();
    var botSender = new BotMesssageSender(bot);
    IMessageSender sender =
        new BotMessageSenderTimerProxy(
            sender: botSender,
            copier: new BotMessageSenderTimerProxy(
                sender: new BotMessageSenderTimerProxy(
                    sender: botSender,
                    copier: new BotMessageSenderTimerProxy(
                        sender: new BotMessageSenderTimerProxy(
                            sender: botSender,
                            copier: new BotMessageSenderTimerProxy(
                                sender: new BotMessageSenderTimerProxy(
                                    sender: botSender,
                                    copier: new BotMessageSenderTimerProxy(
                                        sender: new BotMessageSenderTimerProxy(
                                            sender: botSender,
                                            copier: new BotMessageSenderTimerProxy(...),
                                            ...                                    
    

    As this dependency graph is cyclic, it has no end. It would go on in depth forever. That that's clearly not a pursuable goal.

    Instead, I suggest changing your BotMessageSenderTimerProxy into a proxy on merely the BotMesssageSender. In other words, change its definition to the following:

    public class ProxyBotMesssageSender : AbstractBotMessageSender
       , IMessageSender, IMessageForwarder, IMessageCopier, IDisposable
    {
      public BotMessageSenderTimerProxy(BotMesssageSender sender)
      {
        this.sender = sender;
      }
      ...
    }
    

    When you do this, you can change your registrations to the following:

    Container.Register<TelegramBot>();
    Container.Register<BotMesssageSender>();
    Container.Register<IMessageSender, BotMessageSenderProxy>();
    Container.Register<IMessageCopier, BotMessageSenderProxy>();
    Container.Register<IMessageForwarder, BotMessageSenderProxy>();
    

    This construct the following object graphs:

    var bot = new TelegramBot();
    var botSender = new BotMesssageSender(bot);
    var proxy = new BotMessageSenderProxy(botSender);
    
    IMessageSender sender = proxy;
    IMessageCopier copier = proxy;
    IMessageForwarder forwarder = proxy;