Dependency injection type-selection

I would suggest that you combine your IEmail and ISms interfaces into an IMessageService (provided that doesn’t violate the Liskov Substitution Principal) and to use a strategy pattern to enable your notification service to be able to select the type (or types) of IMessageService that are used.

Refactor to IMessageService

public interface IMessageService
{
    void Send(string subject, string body);
    bool AppliesTo(IEnumerable<string> providers);
}

public class EmailMessageService : IMessageService
{
    public EmailMessageService(/* inject dependencies (and configuration) here */)
    {
        // Set dependencies to private (class level) variables
    }

    public void Send(string subject, string body)
    {
        // Implementation - use dependencies as appropriate
    }

    public bool AppliesTo(IEnumerable<string> providers)
    {
        return providers.Contains("email");
    }
}

public class SmsMessageService : IMessageService
{
    public SmsMessageService(/* inject dependencies (and configuration) here */)
    {
        // Set dependencies to private (class level) variables
    }

    public void Send(string subject, string body)
    {
        // Implementation - use dependencies as appropriate
    }

    public bool AppliesTo(IEnumerable<string> providers)
    {
        return providers.Contains("sms");
    }
}

Implement the Strategy Pattern

public interface IMessageStrategy
{
    void Send(string message, string body, string provider);
    void Send(string message, string body, IEnumerable<string> providers);
}

public class MessageStrategy : IMessageStrategy
{
    private readonly IMessageService[] messageServices;

    public MessageStrategy(IMessageService[] messageServices)
    {
        if (messageServices == null)
            throw new ArgumentNullException("messageServices");
        this.messageServices = messageServices;
    }

    public void Send(string message, string body, string provider)
    {
        string[] providers = provider.Split(';').Select(p => p.ToLower().Trim()).ToArray();
        this.Send(message, body, providers);
    }

    public void Send(string message, string body, IEnumerable<string> providers)
    {
        foreach (IMessageService messageService in messageServices)
        {
            if (messageService.AppliesTo(providers))
            {
                messageService.Send(message, body);
            }
        }
    }
}

Usage

In your DI container, register all types that match IMessageService to be resolved as an array. For example, in StructureMap:

container.For<IMessageService>().Use<EmailMessageService>();
container.For<IMessageService>().Use<SmsService>();

Or alternatively you can use Scan to pickup new types automatically that are added after the fact.

var container = new Container(x => x.Scan(scan =>
{
    scan.TheCallingAssembly();
    scan.WithDefaultConventions();
    scan.AddAllTypesOf<IMessageService>();
}));

Either way, registering the types with the container is all you need to satisfy the IMessageService[] dependency.

Then it is just a matter of injecting IMessageStrategy into a class that requires messaging and passing the magic string to select which types of message services to use.

public class SomeService : ISomeService
{
    private readonly IMessageStrategy messageStrategy;

    public SomeService(IMessageStrategy messageStrategy)
    {
        if (messageStrategy == null)
            throw new ArgumentNullException("messageStrategy");
        this.messageStrategy = messageStrategy;
    }

    public void DoSomething()
    {
        // Send a message via email
        this.messageStrategy.Send("This is a test", "Hello", "email");

        // Send a message via SMS
        this.messageStrategy.Send("This is a test", "Hello", "sms");

        // Send a message via email and SMS
        this.messageStrategy.Send("This is a test", "Hello", "email;sms");
    }
}

Note that if you take this approach, your EmailStrategy class won’t need to change if you decide later to add or remove a IMessageService – you only need to change the DI configuration.

Leave a Comment