DEV Community

Cover image for IEnumerable<IService> Dependency Injection with Strategy Pattern and Factory Method in .NET Core
marriea
marriea

Posted on

IEnumerable<IService> Dependency Injection with Strategy Pattern and Factory Method in .NET Core

In modern .NET Core development, combining the Strategy Pattern, Factory Method, and IEnumerable<IService> built-in Dependency Injection provides a flexible, scalable solution. This pattern combination is ideal for handling dynamic behaviors, such as choosing different algorithms at runtime without modifying core logic.

This article will break down how to implement this approach with an example of sending notifications using different strategies (e.g., Email, SMS).

Key Concepts:

  1. Strategy Pattern: Encapsulates a family of algorithms (or behaviors) and makes them interchangeable at runtime. Each algorithm is isolated within its own class.

  2. Factory Method: Provides a way to instantiate objects dynamically based on runtime conditions.

  3. IEnumerable<IService> in .NET Core Dependency Injection: This allows for the injection of multiple implementations of a service interface (IService) into a class. It provides a way to access all registered implementations, enabling the consumer to iterate over the services and select the appropriate implementation based on specific needs.

Step-by-Step Example: Notification System

Let’s assume we need to send notifications via different channels like Email, SMS, and Push Notifications. Based on user preference, we dynamically choose the appropriate strategy.

1. Define the Strategy Interface

public interface INotificationStrategy
{
    string Key { get; }  // String key to identify the strategy
    void Send(string message);
}
Enter fullscreen mode Exit fullscreen mode

Each notification strategy (Email, SMS, etc.) implements the INotificationStrategy interface.

2. Implement Concrete Strategies

public class EmailNotificationStrategy : INotificationStrategy
{
    public string Key => "Email";  // Unique key for Email strategy
    public void Send(string message) => Console.WriteLine($"Email sent: {message}");
}

public class SmsNotificationStrategy : INotificationStrategy
{
    public string Key => "SMS";  // Unique key for SMS strategy
    public void Send(string message) => Console.WriteLine($"SMS sent: {message}");
}
Enter fullscreen mode Exit fullscreen mode

Each strategy encapsulates a specific notification behavior.

3. Factory Method: Inject All Strategies with IEnumerable<IService>

The factory dynamically chooses the appropriate strategy at runtime using IEnumerable<INotificationStrategy>.

public class NotificationFactory
{
    private readonly IEnumerable<INotificationStrategy> _strategies;

    public NotificationFactory(IEnumerable<INotificationStrategy> strategies)
    {
        _strategies = strategies;
    }

    public INotificationStrategy GetStrategy(string notificationType)
    {
        // Use FirstOrDefault to find the strategy by key
        var strategy = _strategies.FirstOrDefault(s => s.Key == notificationType);
        return strategy ?? throw new ArgumentException("Invalid notification type");
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, IEnumerable<INotificationStrategy> injects all available strategies into the factory. This approach allows easy iteration over all strategies to pick the one that can handle the given notification type.

4. Register Services in DI Container

In the Startup.cs or wherever services are configured, register all concrete strategies and the factory:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<INotificationStrategy, EmailNotificationStrategy>();
    services.AddTransient<INotificationStrategy, SmsNotificationStrategy>();
    services.AddSingleton<NotificationFactory>();
}
Enter fullscreen mode Exit fullscreen mode

This ensures that each strategy is registered, and the NotificationFactory can access them via IEnumerable<INotificationStrategy>.

5. Service Using the Factory

public class NotificationService
{
    private readonly NotificationFactory _factory;

    public NotificationService(NotificationFactory factory)
    {
        _factory = factory;
    }

    public void SendNotification(string type, string message)
    {
        var strategy = _factory.GetStrategy(type);
        strategy.Send(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

Finally, the NotificationService uses the factory to send a notification based on the user’s preference.

How IEnumerable Enhances These Patterns

Typical Strategy Pattern with Factory Method before IEnumerable<IService>

// Strategy interface
public interface INotification
{
    void Send(string message);
}

// Concrete strategies
public class EmailNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine($"Email Notification: {message}");
    }
}

public class SmsNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine($"SMS Notification: {message}");
    }
}

// Factory for creating notifications
public static class NotificationFactory
{
    public static INotification Create(string type)
    {
        return type switch
        {
            "Email" => new EmailNotification(),
            "SMS" => new SmsNotification(),
            _ => throw new NotImplementedException()
        };
    }
}

// Context class
public class NotificationService
{
    private INotification _notification;

    public void SetNotification(string type)
    {
        _notification = NotificationFactory.Create(type);
    }

    public void Notify(string message)
    {
        _notification?.Send(message);
    }
}

// Client code
class Program
{
    static void Main(string[] args)
    {
        var notificationService = new NotificationService();

        // Set different notification strategies
        notificationService.SetNotification("Email");
        notificationService.Notify("Welcome to our service!");

        notificationService.SetNotification("SMS");
        notificationService.Notify("Your order has been shipped!");
    }
}
Enter fullscreen mode Exit fullscreen mode

Comparison

Feature Typical Strategy Pattern + Factory Method Using IEnumerable<INotification>
Strategy Resolution Factory method creates a specific strategy Automatically injects all strategies via DI container
Flexibility Limited flexibility; adding new strategies requires updating the factory More flexible; dynamically resolves strategies at runtime based on conditions
Extensibility Adding a new strategy requires modifying the factory code Easily extendable; simply register new strategies in DI without changing existing code
Adherence to Open/Closed Violates the Open/Closed Principle when adding new strategies Fully adheres to the Open/Closed Principle; new strategies can be added without modifying existing classes
Tight Coupling Factory is tightly coupled to specific strategy implementations Loosely coupled; context only depends on the interface

Conclusion

The combination of Strategy Pattern, Factory Method, and `.NET Core’s built-in DI with IEnumerable makes your system more flexible, extensible, and maintainable. The factory can dynamically choose from multiple strategies injected by DI, keeping your code clean and following SOLID principles.

This pattern is particularly useful in systems where behavior may change frequently, and new strategies need to be added without modifying core components.

Top comments (0)