Strategy Pattern in .NET 8.0 Minimal API with Dependency Injection

The Strategy pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. This pattern is particularly useful in scenarios where you need to switch between different algorithms at runtime. In this blog, we'll explore how to implement the Strategy pattern in .NET 8.0 using Minimal API with dependency injection.

Real-Time Example Payment Processing System

We'll create a Web API that supports multiple payment methods: Credit Card, PayPal, and Bitcoin. We'll use the Strategy pattern to encapsulate each payment method and switch between them dynamically.

Step-by-Step Implementation
 

1. Define the Strategy Interface

Create an interface IPaymentStrategy that declares a method ProcessPayment(decimal amount) to standardize the payment processing logic.

public interface IPaymentStrategy
{
    void ProcessPayment(decimal amount);
}

2. Implement Concrete Strategies

Implement the IPaymentStrategy interface in classes like CreditCardPaymentStrategy, PayPalPaymentStrategy, and BitcoinPaymentStrategy, each providing specific logic for processing payments.

public class CreditCardPaymentStrategy : IPaymentStrategy
{
    public void ProcessPayment(decimal amount)
    {
        // Implement credit card payment processing logic
        Console.WriteLine($"Processing credit card payment of {amount:C}");
    }
}

public class PayPalPaymentStrategy : IPaymentStrategy
{
    public void ProcessPayment(decimal amount)
    {
        // Implement PayPal payment processing logic
        Console.WriteLine($"Processing PayPal payment of {amount:C}");
    }
}

public class BitcoinPaymentStrategy : IPaymentStrategy
{
    public void ProcessPayment(decimal amount)
    {
        // Implement Bitcoin payment processing logic
        Console.WriteLine($"Processing Bitcoin payment of {amount:C}");
    }
}

3. Create an Interface for the Context

Define an interface IPaymentContext that declares methods SetPaymentStrategy(IPaymentStrategy paymentStrategy) and ExecutePayment(decimal amount) to manage and execute the selected payment strategy.

public interface IPaymentContext
{
    void SetPaymentStrategy(IPaymentStrategy paymentStrategy);
    void ExecutePayment(decimal amount);
}

4. Implement the PaymentContext Class

Implement the IPaymentContext interface in the PaymentContext class, which stores the current payment strategy and invokes its ProcessPayment method.

public class PaymentContext : IPaymentContext
{
    private IPaymentStrategy _paymentStrategy;

    public void SetPaymentStrategy(IPaymentStrategy paymentStrategy)
    {
        _paymentStrategy = paymentStrategy;
    }

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

5. Minimal API Configuration

  • Dependency Injection: Registers PaymentContext and the concrete strategy classes with the dependency injection container.
  • Endpoint: Defines a POST endpoint /pay that accepts the amount and method as parameters, sets the appropriate payment strategy based on the method, and processes the payment.
using Payment.Api.Implementations;
using Payment.Api.Interfaces;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

// Register services
builder.Services.AddSingleton<IPaymentContext, PaymentContext>();
builder.Services.AddSingleton<CreditCardPaymentStrategy>();
builder.Services.AddSingleton<PayPalPaymentStrategy>();
builder.Services.AddSingleton<BitcoinPaymentStrategy>();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.MapPost("/pay", async (decimal amount, 
                           string method, 
                           IPaymentContext paymentContext, 
                           IServiceProvider serviceProvider) =>
{
    IPaymentStrategy strategy = method.ToLower() switch
    {
        "creditcard" => serviceProvider.GetService<CreditCardPaymentStrategy>(),
        "paypal" => serviceProvider.GetService<PayPalPaymentStrategy>(),
        "bitcoin" => serviceProvider.GetService<BitcoinPaymentStrategy>(),
        _ => throw new NotSupportedException("Invalid payment method selected.")
    };

    paymentContext.SetPaymentStrategy(strategy);
    paymentContext.ExecutePayment(amount);

    return Results.Ok($"Payment of {amount:C} using {method} processed successfully.");
});

6. Running the Application

  1. Build and Run: Run the application using dotnet run.
  2. Test the Endpoint: You can test the payment processing by sending a POST request to /pay with JSON payloads.

JSON payloads

Conclusion

By integrating the Strategy pattern with a Minimal API in .NET 8.0 and using dependency injection, we've created a flexible and maintainable payment processing system. This approach allows you to easily switch between different payment methods at runtime and aligns with modern development practices in. NET.