Strategy Pattern in ASP.NET Core

You are currently viewing Strategy Pattern in ASP.NET Core

Let’s say you are implementing an online eCommerce shop checkout page and you have been told to implement a feature that will allow customers to choose one of the multiple shipping methods. Each shipping method can have different logics to calculate shipping charges and there can also be a situation where different developers need to implement different algorithms. It will be a bad idea to write a single class with all the calculations and logic in it. You have to split your code in a way that is easy to maintain by different developers and easy to scale if more shipping methods are added in the future. In this tutorial, I will show you how to use a design pattern called Strategy Pattern to not only implement multiple algorithms independently but also switch from one algorithm to another during runtime.

What is a Strategy Pattern?

The strategy pattern (also known as the policy pattern) is a behavioural design pattern that allows us to implement a family of algorithms (strategies) into separate classes and then switch from one algorithm (strategy) to another during runtime. Using this pattern, developers can isolate the code, internal logic, and the dependencies of various algorithms from the rest of the application code which makes the code easy to maintain and easy to scale. This pattern also allows us to alter the behavior of the application at runtime by switching from one algorithm to another algorithm.

Strategy Pattern

Pros of Strategy Pattern

  1. We can isolate the implementation details of different algorithms from the business logic that uses these algorithms
  2. We can switch from one algorithm to another at runtime
  3. We can introduce or replace algorithms without changing the context

Cons of Strategy Pattern

  1. If you have only a couple of algorithms that rarely change, then you don’t have to overcomplicate your code by introducing new classes and interfaces that come along with this pattern.
  2. The client needs to know the difference between different strategies to select the most suitable one.
READ ALSO:  How To Support Multiple Versions of ASP.NET Core Web API

Setting Up an ASP.NET Core Demo App

Strategy pattern can be applied in many real-world use cases where we have multiple algorithms to handle such as

  1. An Ecommerce shop checkout page with multiple shipping methods.
  2. An Ecommerce shop checkout page with multiple payment methods.
  3. An application that generates images in multiple formats
  4. An application that authenticates users using Basic, OAuth, and OpenID authentication.

For this tutorial, I will implement a demo eCommerce checkout page that will allow the user to choose one of the shipping methods and I will show you how a Strategy pattern can help us in switching from one shipping method to another shipping method dynamically.

Create a new ASP.NET Core MVC 5 Web Application and create the following ShippingMethod class.

ShippingMethod.cs

public class ShippingMethod
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Next, create the following CheckoutModel class that will help us in creating a demo checkout page.

CheckoutModel.cs

public class CheckoutModel
{
    public int SelectedMethod { get; set; }
    public decimal OrderTotal { get; set; }
    public decimal FinalTotal { get; set; }

    public List<ShippingMethod> ShippingMethods { get; set; }
}

Open the HomeController and add the following private method in the controller. The GetShippingMethods method will return a list of shipping methods for our demo app. 

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)"
        }
    };
}

Create another private method GetOrderDetails that will build and return an object of CheckoutModel class.

private CheckoutModel GetOrderDetails()
{
    var model = new CheckoutModel()
    {
        OrderTotal = 100.00m,
        ShippingMethods = GetShippingMethods()
    };
    return model;
}

Call GetOrderDetails method inside the Index action of the HomeController as shown in the code snippet below. We are simply returning the model to the Razar view page.

public IActionResult Index()
{
    var model = GetOrderDetails();
    return View(model);
}

Following is the code of the razor view page in which we are creating a simple checkout form. The shipping methods are displaying in a dropdown so that the user can choose one of the shipping methods and we can run one of the shipping calculation algorithms based on the user selection.

Index.cshtml

@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>
                <th>[email protected]</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>
                    <th>[email protected]</th>
                </tr>
            }
        </table>
    }
</div>

Run this app and you should see a demo checkout page similar to the following screenshot.

READ ALSO:  Logging in ASP.NET Core 5 using Serilog

Getting Started with Strategy Pattern

The first step to implement the Strategy pattern is to declare a common interface that will be implemented by all the algorithms. For our demo app, let’s call it IShippingStrategy interface. The interface has a single method CalculateFinalTotal that accepts orderTotal as a parameter and returns the final total.

IShippingStragegy.cs

public interface IShippingStrategy
{
    decimal CalculateFinalTotal(decimal orderTotal);
}

The next step is to implement the above interface on all the algorithms. For our demo app, we need three algorithms Free Shipping, Local Shipping, and Worldwide Shipping to calculate the final total with different shipping rates. Let’s implement all three algorithms one by one.

FreeShippingStrategy.cs

public class FreeShippingStrategy : IShippingStrategy
{
    public decimal CalculateFinalTotal(decimal orderTotal)
    {
        return orderTotal;
    }
}

LocalShippingStrategy.cs

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;
    }
}

I implemented very simple shipping rates calculations in the above classes but in a real-world application, these classes can have all sorts of complex logics. They can also call third-party shipping APIs to calculate shipping rates for different destinations.

Let’s continue our Strategy pattern implementation and declare the following IShippingContext interface.

IShippingContext.cs

public interface IShippingContext
{
    void SetStrategy(IShippingStrategy strategy);

    decimal ExecuteStrategy(decimal orderTotal);
}

Next, implement the above interface on the following ShippingContext class.

ShippingContext.cs

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);
    }
}

We declared a field _strategy for storing the reference to a strategy object. The SetStrategy method will allow us to replace the strategy with the strategy passed into the parameter of the method. Finally, we have the implementation of the ExecuteStrategy method that executes the CalculateFinalTotal for the current strategy.

READ ALSO:  ASP.NET Core Localization from Database

Using Strategy Pattern in ASP.NET Core

We are now ready to use different shipping algorithms in our HomeController usingthe above ShippingContext class but first, we need to register it with .NET Core DI Container. We can achieve this by adding the following line in the ConfigureService method of Startup.cs file.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();

    services.AddScoped<IShippingContext, ShippingContext>();
}

Next, we need to inject the IShippingContext in our HomeController

public class HomeController : Controller
{
    private readonly IShippingContext _shippingContext;

    public HomeController(IShippingContext shippingContext)
    {
        _shippingContext = shippingContext;
    }
}

Finally, we can implement our Index action method that will be called when the user will click the Calculate button. Notice how we are using the SetStrategy method to associate the appropriate strategy according to the shipping method selected by the user. Once the strategy is set, the ExecuteStrategy will automatically use the appropriate shipping strategy to calculate the final total.

[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);
}

Run the demo app and try to calculate the final total by selecting different shipping methods. You will the final order total accordingly.

Strategy Pattern Example One
Strategy Pattern Example Two

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

Strategy pattern helps us in creating efficient, more readable, and easy to maintain software. It allows us to create software with reusable and interchangeable components which can be added/replaced dynamically at runtime. I hope you enjoyed this tutorial and the example I use to explain this pattern in a way that is easy to understand for everyone.

This Post Has One Comment

  1. Quiz Dev

    You got an example very clear and easy to understand. It helps me know what case I can use it. Thanks, many!

Leave a Reply