Search code examples
c#dependency-injectionsimple-injector

Strategy Pattern with Simple Injector


I have a workflow in which I need to use particular service class based on some run time data.

For eg. I have a SendEmailCommand with a dependency of IEmailService with 2 implementations of MandrillEmailService and SmtpEmailService.

The SendEmailCommand will determine which implementation of IEmailService it uses at run time.

Each Email Service implementation has its own dependencies, which is wired up by Simple Injector.

I'm not sure how SendEmailCommand will determine what implementation (IEmailService) to use or how to wire up those services with run time data.

Some code:

SendEmailCommand.cs handler

public void Handle(SendEmail command) {
  IEmailService service;

  if(/* some condition */) {
    service = // What to do here?
  } else {
    service = // What to do here?
  }
}

Service can be substituted with a strategy I'm guessing.


Solution

  • The most obvious solution (to me) is to define a new abstraction that allows dispatching to the mail service implementation while supplying the runtime data to it. This might look like this:

    interface IMailServiceDispatcher
    {
        void Send([runtime data], [mail service arguments]);
    }
    

    This allows your command handler to become:

    private readonly IEmailServiceDispatcher dispatcher;
    public void Handle(SendEmail command) {
      var data = /* runtime data */
      this.dispatcher.Send(data, /* mail service parameters */);
    }
    

    Now you can create an implementation for IMailServiceDispatcher that might look as follows:

    class EmailServiceDispatcher : IEmailServiceDispatcher
    {
        IMailService man; IMailService smtp;
        public EmailServiceDispatcher(IMailService man, IMailService smtp) { ... }
    
        public void Send(runtimeData, parameters) =>
            GetService(runtimedData).Send(parameters);
    
        private IMailService GetService(runtimeData) =>
            /* some condition using runtime data */ ? man : smtp;
    }
    

    You can wire this all up as follows:

    container.Register<IMailServiceDispatcher, MailServiceDispatcher>();
    container.RegisterConditional<IMailService, MandrillEmailService>(WithParamName("man"));
    container.RegisterConditional<IMailService, SmtpEmailService>(WithParamName("smtp"));
    
    // Helper method
    private static Predicate<PredicateContext> WithParamName(string name) =>
        c => c.Consumer.Target.Name == name;