Imagine you are developing the checkout page for an online store where you sell goods and services, and you have been instructed to include a feature that will let customers select from a variety of shipping options. Each shipping method may use a different logic to determine shipping costs, and there may be instances where various developers must use various algorithms. Writing a single class that contains all the calculations and logic would be a bad idea. You must divide your code in a way that makes it simple for various developers to maintain it and simple to scale should more shipping methods ever be added. In this tutorial, I’ll demonstrate how to use a design pattern called the Strategy Pattern to implement several algorithms independently and switch between them as needed.
What is a Strategy Pattern?
A behavioral design pattern called the strategy pattern (also referred to as the policy pattern) enables us to implement a family of algorithms (or strategies) into distinct classes and then switch from one algorithm (or strategy) to another during runtime. Developers can isolate the code, internal logic, and dependencies of different algorithms using this pattern, making the application code simple to scale and maintain. By switching from one algorithm to another at runtime, this pattern also enables us to change the behavior of the application.

Pros of Strategy Pattern
- Different algorithms’ implementation specifics can be separated from the business logic that employs them.
- At runtime, we can change from one algorithm to another.
- Algorithms can be added or removed without changing the context.
Cons of Strategy Pattern
- You don’t need to overcomplicate your code by adding new classes and interfaces that go along with this pattern if you only have a few algorithms that rarely change.
- To choose the most appropriate strategy, the client needs to understand how various strategies differ from one another.
Setting Up an ASP.NET Core Demo App
When dealing with multiple algorithms, strategy patterns can be used in a variety of real-world use cases, including
- An online store checkout page with various shipping options.
- A checkout page for an online store that accepts various forms of payment.
- A program that produces images in a variety of formats.
- A program that authenticates users through OpenID, OAuth, and Basic authentication.
In order to demonstrate how a Strategy pattern can help us switch from one shipping method to another shipping method dynamically, I’ll implement a sample eCommerce checkout page that will let the user select one of the shipping methods.
Create the ShippingMethod class below in a new ASP.NET Core MVC 5 Web Application.
ShippingMethod.cs
public class ShippingMethod{ public int Id { get; set; } public string Name { get; set; }}public class CheckoutModel{ public int SelectedMethod { get; set; } public decimal OrderTotal { get; set; } public decimal FinalTotal { get; set; } public List<ShippingMethod> ShippingMethods { get; set; }}private List<ShippingMethod> GetShippingMethods(){ return new List<ShippingMethod>() { new ShippingMethod() { Id = 1, Name="Free Shipping ($0.00)" }, new ShippingMethod() { Id = 2, Name="Local Shipping ($10.00)" }, new ShippingMethod() { Id = 3, Name="Worldwide Shipping ($50.00)" } };}private CheckoutModel GetOrderDetails(){ var model = new CheckoutModel() { OrderTotal = 100.00m, ShippingMethods = GetShippingMethods() }; return model;}public IActionResult Index(){ var model = GetOrderDetails(); return View(model);}@model CheckoutModel@{ ViewData["Title"] = "Home Page";}<br /><div> <h3 class="display-4 m-3 text-center">Checkout</h3> <br /> @using (Html.BeginForm("Index", "Home", null, FormMethod.Post)) { @Html.HiddenFor(x => x.OrderTotal); <table cellpadding="0" cellspacing="0" class="table"> <tr> <th>Order Total: </th> </tr> <tr> <th>Shipping Method: </th> <td> @Html.DropDownListFor(x => x.SelectedMethod, new SelectList(Model.ShippingMethods, "Id", "Name"), "- Select -", new { @class = "form-control" }) </td> <td class="text-left"> <input class="btn btn-primary" type="submit" value="Calculate" /> </td> </tr> @if (Model.FinalTotal > 0) { <tr> <th>Final Total: </th> </tr> } </table> }</div>Getting Started with Strategy Pattern
Declaring a common interface that will be implemented by all the algorithms is the first step in putting the Strategy pattern into practice. Let’s call the interface for our test app the IShippingStrategy interface. The interface only has one method, CalculateFinalTotal, that receives as a parameter the orderTotal and returns the final sum.
IShippingStragegy.cs
public interface IShippingStrategy{ decimal CalculateFinalTotal(decimal orderTotal);}public class FreeShippingStrategy : IShippingStrategy{ public decimal CalculateFinalTotal(decimal orderTotal) { return orderTotal; }}public class LocalShippingStrategy : IShippingStrategy{ public decimal CalculateFinalTotal(decimal orderTotal) { return orderTotal + 10; }}WorldwideShippingStrategy.cs
public class WorldwideShippingStrategy : IShippingStrategy{ public decimal CalculateFinalTotal(decimal orderTotal) { return orderTotal + 50; }}Continue implementing the Strategy pattern by declaring the IShippingContext interface below.
IShippingContext.cs
public interface IShippingContext{ void SetStrategy(IShippingStrategy strategy); decimal ExecuteStrategy(decimal orderTotal);}public class ShippingContext : IShippingContext{ private IShippingStrategy _strategy; public ShippingContext() { } public ShippingContext(IShippingStrategy strategy) { this._strategy = strategy; } public void SetStrategy(IShippingStrategy strategy) { this._strategy = strategy; } public decimal ExecuteStrategy(decimal orderTotal) { return this._strategy.CalculateFinalTotal(orderTotal); }}Using Strategy Pattern in ASP.NET Core
With the help of the aforementioned ShippingContext class, we are now prepared to use various shipping algorithms in our HomeController, but first we must register it with the.NET Core DI Container. This can be done by including the following line in the Startup.cs file’s ConfigureService method.
public void ConfigureServices(IServiceCollection services){ services.AddControllersWithViews(); services.AddScoped<IShippingContext, ShippingContext>();}The IShippingContext must then be added to our HomeController.
public class HomeController : Controller{ private readonly IShippingContext _shippingContext; public HomeController(IShippingContext shippingContext) { _shippingContext = shippingContext; }}The Index action method, which will be invoked when the user clicks the Calculate button, can now be put into practice. Take note of how we are utilizing the SetStrategy method to associate the proper strategy with the user-selected shipping method. Once the strategy is chosen, the ExecuteStrategy will automatically determine the final cost using the appropriate shipping strategy.
[HttpPost]public IActionResult Index(CheckoutModel model){ model.ShippingMethods = GetShippingMethods(); switch (model.SelectedMethod) { case 1: _shippingContext.SetStrategy(new FreeShippingStrategy()); break; case 2: _shippingContext.SetStrategy(new LocalShippingStrategy()); break; case 3: _shippingContext.SetStrategy(new WorldwideShippingStrategy()); break; } model.FinalTotal = _shippingContext.ExecuteStrategy(model.OrderTotal); return View(model);}Start the demo app and experiment with calculating the final sum by choosing various shipping options. Your final order total will reflect this.


The following is the complete source code of our HomeController.
HomeController.cs
public class HomeController : Controller{ private readonly IShippingContext _shippingContext; public HomeController(IShippingContext shippingContext) { _shippingContext = shippingContext; } public IActionResult Index() { var model = GetOrderDetails(); return View(model); } [HttpPost] public IActionResult Index(CheckoutModel model) { model.ShippingMethods = GetShippingMethods(); switch (model.SelectedMethod) { case 1: _shippingContext.SetStrategy(new FreeShippingStrategy()); break; case 2: _shippingContext.SetStrategy(new LocalShippingStrategy()); break; case 3: _shippingContext.SetStrategy(new WorldwideShippingStrategy()); break; } model.FinalTotal = _shippingContext.ExecuteStrategy(model.OrderTotal); return View(model); } private CheckoutModel GetOrderDetails() { var model = new CheckoutModel() { OrderTotal = 100.00m, ShippingMethods = GetShippingMethods() }; return model; } private List<ShippingMethod> GetShippingMethods() { return new List<ShippingMethod>() { new ShippingMethod() { Id = 1, Name="Free Shipping ($0.00)" }, new ShippingMethod() { Id = 2, Name="Local Shipping ($10.00)" }, new ShippingMethod() { Id = 3, Name="Worldwide Shipping ($50.00)" } }; }}Summary
We can create software that is more effective, readable, and simple to maintain by using strategy patterns. It enables us to build software with reusable and interchangeable parts that can be dynamically added to or taken away at runtime. I hope you’ve enjoyed this tutorial and the example I used to clearly explain this pattern to everyone.

Andriy Kravets is writer and experience .NET developer and like .NET for regular development. He likes to build cross-platform libraries/software with .NET.

