1

I'm having a little bit of trouble wrapping my head around IoC - specifically using Unity.

Suppose I have an application that I want to use to send emails. I would model it like this:

public interface IEmailSender
{
  void SendEmail();
}

And then create some implementations of the interface:

public class GmailEmailSender : IEmailSender
{
  public void SendEmail()
  {
      //code to send email using Gmail
  }
}

public class YahooEmailSender : IEmailSender
{
  public void SendEmail()
  {
      //code to send email using Yahoo
  }
}

I also have a class to actually send the Emails

public class EmailSender
{
   IEmailSender _emailSender;
   public EmailSender(IEmailSender emailSender)
   {
       _emailSender= emailSender;
   }

   public void Send()
   {
      _emailSender.SendEmail();
   }
}

So I understand how to configure Unity to always use one of the implementations:

IUnityContainer container = new UnityContainer().RegisterType<IEmailSender, GmailEmailSender>());

but what I'm not quite understanding is if I want to choose the YahooEmailSender based on some criteria and the GmailEmailSender based on other criteria, where do I code the logic to make that determination, and in turn inject the appropriate concrete implementation to the EmailSender constructor, without using

EmailSender emailSender = new EmailSender(new YahooEmailSender());
CodeFuller
  • 30,317
  • 3
  • 63
  • 79
MillinMo
  • 395
  • 1
  • 7
  • 21
  • https://stackoverflow.com/questions/23669502/unity-container-multiple-implementations-of-same-interface, probably duplicate... Or maybe you are looking for registering factory that automatically picks named registration - sample usage could help... – Alexei Levenkov Jan 19 '18 at 05:07
  • Possible duplicate of [Unity Container Multiple Implementations of same interface](https://stackoverflow.com/questions/23669502/unity-container-multiple-implementations-of-same-interface) – Backs Jan 19 '18 at 06:20
  • Thanks for your replies. I don't think either of those links are what I'm looking for. I may just be generally misunderstanding the purpose of Unity. I understand how it is helpful for easily passing around logging type dependencies, or standard configurations that don't change after the container is configured, I'm asking more about where it fits in as far as if you can't commit to a concrete implementation at configuration time. – MillinMo Jan 19 '18 at 13:32
  • 3
    See [Dependency injection type-selection](https://stackoverflow.com/a/34331154/) and [Dependency Injection Unity - Conditional resolving](https://stackoverflow.com/a/32415954). DI containers are for *resolving object graphs* at application startup, not for *controlling runtime behavior*. In case of the latter, you should use one or more [design patterns](https://sourcemaking.com/design_patterns) to solve the problem as it is clearly not a DI problem, but an application design problem. – NightOwl888 Jan 20 '18 at 04:53

1 Answers1

7

Your question is fair enough. Resolving dependencies at runtime, based on some user input or configuration settings, is a well-known problem. Mark Seemann devotes separate section of his great book Dependency Injection in .NET to this problem - "6.1 Mapping runtime values to abstractions". And I can't fully agree with NightOwl888 that this is not a DI problem.

There are 2 main solutions for this problem.

First one, described by Mark Seeman in his book, is to have the factory that takes indication of selected implementation as parameter. Here is basic description how it works.

First of all, you should pass somehow which implementation you want to use. It could be just a string (e.g. "gmail", "yahoo"), but it's better to do it via enum:

public enum EmailTarget
{
    Gmail,
    Yahoo,
}

Then you should define the factory itself. In common, it will look like this:

public interface IEmailSenderFactory
{
    IEmailSender CreateSender(EmailTarget emailTarget);
}

Then you should provide implementation for the factory. It could be as simple as:

public class EmailSenderFactory : IEmailSenderFactory
{
    public IEmailSender CreateSender(EmailTarget emailTarget)
    {
        switch (emailTarget)
        {
            case EmailTarget.Gmail:
                return new GmailEmailSender();

            case EmailTarget.Yahoo:
                return new YahooEmailSender();

            default:
                throw new InvalidOperationException($"Unknown email target {emailTarget}");
        }
    }
}

However, in more complex cases, instances of IEmailSender should also be created via DI container. In this case you could use factory based on IUnityContainer:

public class EmailSenderFactory : IEmailSenderFactory
{
    private readonly IUnityContainer diContainer;

    public EmailSenderFactory(IUnityContainer diContainer)
    {
        this.diContainer = diContainer;
    }

    public IEmailSender CreateSender(EmailTarget emailTarget)
    {
        switch (emailTarget)
        {
            case EmailTarget.Gmail:
                return diContainer.Resolve<GmailEmailSender>();

            case EmailTarget.Yahoo:
                return diContainer.Resolve<YahooEmailSender>();

            default:
                throw new InvalidOperationException($"Unknown email target {emailTarget}");
        }
    }
}

Then you should adjust EmailSender and inject IEmailSenderFactory in it. Send() method should be extended with value of EmailTarget enum that specifies selected sender:

public class EmailSender
{
    private readonly IEmailSenderFactory senderFactory;

    public EmailSender(IEmailSenderFactory senderFactory)
    {
        this.senderFactory = senderFactory;
    }

    public void Send(EmailTarget emailTarget)
    {
        var sender = senderFactory.CreateSender(emailTarget);
        sender.SendEmail();
    }
}

The last thing is proper composition root:

IUnityContainer container = new UnityContainer();
container.RegisterType<IEmailSender, GmailEmailSender>("gmail");
container.RegisterType<IEmailSender, YahooEmailSender>("yahoo");
container.RegisterType<IEmailSenderFactory, EmailSenderFactory>();

And finally when you need to send e-mail:

var sender = container.Resolve<EmailSender>();
sender.Send(EmailTarget.Gmail);

The second approach is more simple. It doesn't use the factory and is based on Unity named dependencies. With this approach your classes could be left as is. Here is the composition root:

IUnityContainer container = new UnityContainer();
container.RegisterType<IEmailSender, GmailEmailSender>("gmail");
container.RegisterType<IEmailSender, YahooEmailSender>("yahoo");
container.RegisterType<EmailSender>("gmail", new InjectionConstructor(new ResolvedParameter<IEmailSender>("gmail")));
container.RegisterType<EmailSender>("yahoo", new InjectionConstructor(new ResolvedParameter<IEmailSender>("yahoo")));

Sender is created in the following way:

var emailSender = container.Resolve<EmailSender>("gmail");
emailSender.Send();

It's up to you to decide which of these approaches to use. Purists will say that the first one is better because you don't mix specific DI container with your application logic. Actually you do, if the factory is based on DI container, but it's concentrated in one place and could be easily replaced. The second approach is however much simpler and could be used for simplest scenarios.

CodeFuller
  • 30,317
  • 3
  • 63
  • 79
  • I hope it's ok to bring up this older thread. I'm having the same problem, but with WPF + EF Core using MVVM, I need to change connection string on runtime. I understand your solution and I think I know how to implement it for myself - but where should I change it? I am registering my containers in App.xaml.cs, but then I need to change it in ViewModel, right? How should I get that containers instance there so I can use Resolve<>()? Thank you in advance! – TheSpixxyQ Mar 02 '20 at 07:10