Skip to main content

Dependency Injection In .NET Core With Strategy Pattern

In the previous post, we gave an introduction and explained the basic concept of the Strategy Pattern. Now, we want to go a bit further and demonstrate how it works in practice alongside a dependency injection in .NET Core. Instead of manually instantiating strategies, we let the ASP.NET Core DI container inject the correct implementation at runtime.

Example

For the showing purposes, let us asume that we are implementing some payment service, and we want to use one service at the time, depending on user input.

For our strategy pattern, we would need following components:

Interface
public interface IPaymentStrategy
{
    void Pay(decimal amount);
}
Concrete Strategies
public class CreditCardPayment : IPaymentStrategy
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid ${amount} using Credit Card.");
    }
}

public class PayPalPayment : IPaymentStrategy
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid ${amount} using PayPal.");
    }
}

public class BitcoinPayment : IPaymentStrategy
{
    public void Pay(decimal amount)
    {
        Console.WriteLine($"Paid ${amount} using Bitcoin.");
    }
}
Context
public class PaymentContext
{
    private readonly IPaymentStrategy _paymentStrategy;

    // Inject the strategy via constructor
    public PaymentContext(IPaymentStrategy paymentStrategy)
    {
        _paymentStrategy = paymentStrategy;
    }

    public void ExecutePayment(decimal amount)
    {
        _paymentStrategy.Pay(amount);
    }
}

Next, we are implementing our controller:

using Microsoft.AspNetCore.Mvc;

[Route("api/[controller]")]
[ApiController]
public class PaymentController : ControllerBase
{
    private readonly IServiceProvider _serviceProvider;

    public PaymentController(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    [HttpPost("{paymentMethod}")]
    public IActionResult ProcessPayment(string paymentMethod, [FromBody] decimal amount)
    {
        IPaymentStrategy paymentStrategy = paymentMethod.ToLower() switch
        {
            "creditcard" => _serviceProvider.GetService<CreditCardPayment>(),
            "paypal" => _serviceProvider.GetService<PayPalPayment>(),
            "bitcoin" => _serviceProvider.GetService<BitcoinPayment>(),
            _ => throw new ArgumentException("Invalid payment method")
        };

        var paymentContext = new PaymentContext(paymentStrategy);
        paymentContext.ExecutePayment(amount);

        return Ok($"Payment of ${amount} processed using {paymentMethod}.");
    }
}

And, we register each payment strategy in Program.cs.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);

// Register concrete strategies
builder.Services.AddTransient<CreditCardPayment>();
builder.Services.AddTransient<PayPalPayment>();
builder.Services.AddTransient<BitcoinPayment>();

// Register controllers
builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

How It Works

  • The DI container registers each strategy (CreditCardPayment, PayPalPayment, BitcoinPayment).
  • The controller receives an IServiceProvider, which allows it to dynamically resolve the appropriate payment strategy.
  • When a user makes a payment request, the API dynamically selects the strategy based on the URL parameter (creditcard, paypal, bitcoin).
  • The selected strategy is injected into the PaymentContext and executed.

Conclusion

Finally, we can highlight some key points on why this approach offers significant benefits to the code structure.
  • Loose Coupling – The controller does not depend on concrete implementations, making the code more flexible.
  • Easy Maintenance – New payment methods can be added without modifying existing code.
  • Testability – The strategies can be mocked and tested independently.
  • Runtime Flexibility – The payment method can be selected dynamically at runtime.


Comments

Popular posts from this blog

Design Patterns: Singleton

Tyipically the first design pattern most people learn, often wrongly ☺ To give an introduction, we can say that singleton is one of the creational design patterns which ensures only one class instance with single point of access thru entire application.  Because it is relatively simple to implement, the Singleton pattern is sometimes misapplied in situations where it is not the most suitable choice. When to use it? Here are the few examples of corrent usage of singleton: Configuration Management  Centralized configuration settings for consistent use thru entire application Caching Maintaning  Single istance of cached objects for easy and fast acces Logging  Ensure unified mechanism to avoid duplication of log files, formats, etc Global State Management  Centralized management of the state which is needed to be shared accross the application Resource sharing  Thread pools, database connection, I/O operations When not to use it? On the other hand, here are fe...

Design Patterns: Builder

This is also, like a Singleton , one of the creational design patterns. It provides the way of creating complex objects step by step by simple chaining and every particular step is independent of other steps. Let us dive into the real example of usage. For showing purpose we have created an example in C# which creates simple SQL queries using described pattern.  using System; using System.Text; namespace BuilderPatternExample { public interface ISqlQueryBuilder { ISqlQueryBuilder Select(string columns); ISqlQueryBuilder From(string table); ISqlQueryBuilder Where(string condition); ISqlQueryBuilder OrderBy(string columns); string Build(); } public class SelectQueryBuilder : ISqlQueryBuilder { private readonly StringBuilder _queryBuilder; public SelectQueryBuilder() { _queryBuilder = new StringBuilder(); } public ISqlQueryBuilder Select(string columns) { ...

Why Do Employers Lie In Interviews?

This is a very common subject that many of us have already experienced. But when you realize that half of what has been said at interviews is actually a lie, you are already at least six months in the company, you have already started some project and it wouldn’t be appropriate to leave the company at that moment. Why is this happening? First of all, let us see how the usual interview process looks like in software development companies. First round interview in most of these companies is an interview with HR. This is the first insight about the company. A person who works in HR is usually someone who, in most cases, doesn’t understand what the software is and how the software development process goes. Big respect to those companies where HR knows these things. This phase usually contains some standard questions about your personality, what do you like about the company, how this company is something that you are actually looking for, where you see yourself in five/ten  years etc… ...