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
Post a Comment