Improving Code Flexibility with Strategy Pattern and Dependency Injection in .NET
Marian Salvan

Marian Salvan @marian_s

About: Full-stack developer with 7+ years' experience, specializing in .NET (C#, web APIs, microservices, Docker, cloud computing and React.js)

Joined:
Jan 24, 2025

Improving Code Flexibility with Strategy Pattern and Dependency Injection in .NET

Publish Date: Feb 7
8 3

Problem statement

Not sure about you, but many times when I had to implement different algorithms depending on specific input I ended up with ugly switch statements that made my code bloated and, in some cases, difficult to understand.

Practical example

Let's say that I need to create a Calculate API method that takes two double numbers and an operation type as input, then applies that operation to the two numbers.

In code this would look something like this:

  • Get method that implements the logic above (I am using minimal APIs to illustrate this)
app.MapGet("/calculate", ([FromQuery] double a, [FromQuery] double b, [FromQuery] string op) =>
{
   try
   {
       if (!Enum.TryParse<OperationType>(op, true, out var operationType))
       {
           return Results.BadRequest("Invalid operation");
       }
 
       return operationType switch
       {
           OperationType.ADDITION => Results.Ok(a + b),
           OperationType.SUBSTRACTION => Results.Ok(a - b),
           OperationType.MULTIPLICATION => Results.Ok(a * b),
           OperationType.DIVISION => b == 0 ? Results.BadRequest("Division by zero") : Results.Ok(a / b),
           _ => Results.BadRequest("Invalid operation type"),
       };
   }
   catch (Exception)
   {
       return Results.InternalServerError("Calculation error");
   }
})
.WithName("Calculate");
Enter fullscreen mode Exit fullscreen mode
  • OperationType enum
public enum OperationType
{
    ADDITION,
    SUBSTRACTION,
    MULTIPLICATION,
    DIVISION
}
Enter fullscreen mode Exit fullscreen mode

This doesn't look too bad, but imagine if the algorithms you need to implement were much more complex than a simple addition. Of course, you could move the code to separate classes, but the switch statement would still remain.

Proposed solution

This kind of problem can be easily solved using the Strategy Pattern, and this is no secret. However, we can leverage the power of dependency injection to implement this pattern in a very elegant way.

Here's how this can be done:

Step 1 - Create an ICalculationStrategy interface

Notice that the strategy interface also contains the OperationType — this will be used to identify the correct strategy for each case.

namespace DemoTechniques.Strategies
{
    public interface ICalculationStrategy
    {
        public OperationType OperationType { get; }
        public double Calculate(double a, double b);
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 2 - Create the concrete strategies

namespace DemoTechniques.Strategies
{
    public class AdditionCalculationStrategy : ICalculationStrategy
    {
        public OperationType OperationType => OperationType.ADDITION;

        public double Calculate(double a, double b) => a + b;
    }

    public class SubstractionCalculationStrategy : ICalculationStrategy
    {
        public OperationType OperationType => OperationType.SUBSTRACTION;

        public double Calculate(double a, double b) => a - b;
    }

    public class MultiplicationCalculationStrategy : ICalculationStrategy
    {
        public OperationType OperationType => OperationType.MULTIPLICATION;

        public double Calculate(double a, double b) => a * b;
    }

    public class DivisionCalculationStrategy : ICalculationStrategy
    {
        public OperationType OperationType => OperationType.DIVISION;

        public double Calculate(double a, double b)
        {
            if (b == 0)
            {
                throw new ArgumentException("Cannot divide by zero", nameof(b));
            }

            return a / b;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3 - Register the strategies inside the DI container

Now that we have all the strategies, we can injected them inside the DI container, like this:

var builder = WebApplication.CreateBuilder(args);

//other services

builder.Services
    .AddTransient<ICalculationStrategy, AdditionCalculationStrategy>()
    .AddTransient<ICalculationStrategy, SubstractionCalculationStrategy>()
    .AddTransient<ICalculationStrategy, MultiplicationCalculationStrategy>()
    .AddTransient<ICalculationStrategy, DivisionCalculationStrategy>();
Enter fullscreen mode Exit fullscreen mode

Step 4 - Apply the strategies inside the Calculate method

These services can be injected into the method, and OperationType can be used to identify the correct calculation service based on the user input. This allows us to eliminate the switch statement and delegate the responsibility of identifying the correct service to the DI container.

The Calculate method can be refactored like this:

app.MapGet("/calculate", ([FromQuery] double a, [FromQuery] double b, [FromQuery] string op,
    [FromServices] IEnumerable<ICalculationStrategy> calculationStrategies) =>
{
    try
    {
        if (!Enum.TryParse<OperationType>(op, true, out var operationType))
        {
            return Results.BadRequest("Invalid operation");
        }

        var calculationStrategy = calculationStrategies
            .FirstOrDefault(s => s.OperationType == operationType);

        if (calculationStrategy is null)
        {
            return Results.BadRequest("Invalid operation type");
        }

        return Results.Ok(calculationStrategy.Calculate(a, b));
    }
    catch (Exception)
    {
        return Results.InternalServerError("Calculation error");
    }
})
.WithName("Calculate");
Enter fullscreen mode Exit fullscreen mode

Bonus tip

If you are using a controller or any other service, you can create a dictionary from the IEnumerable<ICalculationStrategy> inside the constructor and then use this dictionary to quickly lookup the correct strategy throughout your controller/service methods.

Example implementation:

using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;

[ApiController]
[Route("api/[controller]")]
public class CalculatorController : ControllerBase
{
    private readonly Dictionary<OperationType, ICalculationStrategy> _strategyDictionary;

    public CalculatorController(IEnumerable<ICalculationStrategy> calculationStrategies)
    {
        // Create a dictionary for quick lookup
        _strategyDictionary = calculationStrategies.ToDictionary(s => s.OperationType, s => s);
    }

    [HttpGet("calculate")]
    public IActionResult Calculate([FromQuery] double a, [FromQuery] double b, [FromQuery] string op)
    {
        try
        {
            if (!Enum.TryParse<OperationType>(op, true, out var operationType))
            {
                return BadRequest("Invalid operation");
            }

            if (!_strategyDictionary.TryGetValue(operationType, out var calculationStrategy))
            {
                return BadRequest("Invalid operation type");
            }

            return Ok(calculationStrategy.Calculate(a, b));
        }
        catch (Exception)
        {
            return StatusCode(500, "Calculation error");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusions

In this brief article, we tackled the problem of bloated switch statements for algorithm selection based on user input. We introduced the Strategy Pattern, a solution that helps decouple the logic, making the code cleaner and more maintainable. By combining this pattern with Dependency Injection, we effectively eliminate the switch statement, delegating the responsibility of choosing the correct strategy to the DI container.

This approach aligns with SOLID principles, particularly the Open/Closed Principle (allowing easy addition of new operations without modifying existing code) and the Dependency Inversion Principle (relying on abstractions rather than concrete implementations). By using a dictionary for quick lookup of strategies, the design becomes even more modular and scalable.

Comments 3 total

  • Martin Humlund Clausen
    Martin Humlund ClausenFeb 10, 2025

    What an great article, Strategy Pattern is one of my favorites! :D

    I love to combine the strategy Pattern with a Abstract Factory, which makes construction and identification of strategies into a separate service. For this case specifically its alright to find the correct strategy using linq, but could you imagine a world where either finding or constructing the strategy is not always easy?

    Anyway, that was just a few thoughts, great article :)

    • Marian Salvan
      Marian SalvanFeb 10, 2025

      Thanks for your comment, Martin! You are right - in some cases it would be a bit more difficult to find/construct a strategy. However, for more straightforward cases (such as this one) I believe this is could be a good option for code simplification.

  • АнонимFeb 13, 2025

    [hidden by post author]

Add comment