K Khan
Jan 1, 2020
  2169
(6 votes)

Use of strategy pattern to change page behaviour

There are scenarios where we have to change our page behavior or algorithms at run time based on Epi Configurations. Generally, a developer approaches with if-else statements to deal with this by compromising design principals. We can get help from the Strategy Pattern to adhere to SOLID principals. Below is an Illustrations for a fake scenario to demonstrate implementation where editors will set a recipe filter to change the results of the page or selecting the right dataset for the page.

EPiServer Page and configurations

public enum LayoutType
    {
        Diabitic,
        HighProtein,
        LowFat,
        HighFibre
    }

    /// <summary>
    /// Create an interface for configuration
    /// </summary>
    public interface IStrategyConfiguration
    {
        LayoutType LayoutType { get; set; }
    }

    /// <summary>
    /// Implement Configuration in your page or block
    /// </summary>
    public class RecipeLandingPage : PageData, IStrategyConfiguration
    {
        [Display(GroupName = SystemTabNames.Settings, Name = "Layout", Description = "Select page layout")]
        public LayoutType LayoutType { get; set; }
    }

PageController/factory where we need to change the algorithm at runtime.

/// <summary>
    /// Page Controller
    /// </summary>
    public class RecipeLandingPageController : PageController<RecipeLandingPage>
    {
        /// <summary>
        /// IRecipeFilterSelector is a resolver that will select right IRecipeFilter for us
        /// </summary>
        private readonly IRecipeFilterSelector _recipeFilterSelector;

        public RecipeLandingPageController(IRecipeFilterSelector recipeFilterSelector)
        {
            _recipeFilterSelector = recipeFilterSelector;
        }

        public async Task<ActionResult> Index(RecipeLandingPage currentPage)
        {
            //Select a filter based on epi configurations
            var filter = _recipeFilterSelector.GetRecipeFilter(currentPage.LayoutType);
            var pageModel = YourPageModel(currentPage);
            pageModel.Advertisements = filter.GetAdvertisements();
            pageModel.Suggestions = filter.GetSuggestions();
            return View(model);
        }
    }

We need IRecipeFilterSelector that could return us correct IRecipeFilter.

    /// <summary>
    /// will resolve correct IRecipeFilter
    /// </summary>
    public interface IRecipeFilterSelector
    {
        IRecipeFilter GetRecipeFilter(LayoutType layoutType);
    }

    /// <summary>
    /// Select IRecipeFilter based on EPiCongigurations
    /// </summary>
    public class RecipeFilterSelector : IRecipeFilterSelector
    {
        private readonly IEnumerable<IRecipeFilter> _recipeFilters;
        // structuremap will push all implementations of IRecipeFilter
        public RecipeFilterSelector(IEnumerable<IRecipeFilter> recipeFilters)
        {
            _recipeFilters = recipeFilters;
        }

        public IRecipeFilter GetRecipeFilter(LayoutType layoutType)
        {
            return _recipeFilters.FirstOrDefault(x => x.LayoutType == layoutType);
        }
    }

We need separate implementations for IRecipeFilter

/// <summary>
    /// Main service
    /// </summary>
    public interface IRecipeFilter
    {
        LayoutType LayoutType { get; }
        IList<Advertisement> GetAdvertisements();
        IList<Suggestion> GetSuggestions();
    }

    /// <summary>
    /// Separation of concerns achieved, implementation for specific scenarios
    /// </summary>
    public class DiabiticRecipeFilter : IRecipeFilter
    {
        public LayoutType LayoutType => LayoutType.Diabitic;

        public IList<Advertisement> GetAdvertisements()
        {
            throw new NotImplementedException();
        }

        public IList<Suggestion> GetSuggestions()
        {
            throw new NotImplementedException();
        }
    }
    /// <summary>
    /// Separation of concerns achieved, implementation for specific scenarios
    /// </summary>

    public class HighProteinRecipeFilter : IRecipeFilter
    {
        public LayoutType LayoutType => LayoutType.HighProtein;

        public IList<Advertisement> GetAdvertisements()
        {
            throw new NotImplementedException();
        }

        public IList<Suggestion> GetSuggestions()
        {
            throw new NotImplementedException();
        }
    }

Join these pieces in structuremap to work together

//Collect all implementations of IRecipeFilter
    c.Scan(s =>
    {
    s.AssemblyContainingType(typeof(IRecipeFilter));
    s.AddAllTypesOf(typeof(IRecipeFilter));
    });
    //pass this to selector
    c.For<IRecipeFilterSelector>().Use<RecipeFilterSelector>().Singleton();

Be SOLID in the new year! Happy new year!

Jan 01, 2020

Comments

Drew Douglas
Drew Douglas Jan 2, 2020 05:11 PM

Great idea! Strategy is one of the design patterns that should be widely used. I find that over-application of design patterns is a code-smell for small teams, but I love strategy for simplifying behavior choices.

K Khan
K Khan Jan 2, 2020 05:58 PM

Glad you liked! Thanks!

K Khan
K Khan Jan 2, 2020 05:59 PM

Glad you liked! Thanks!

Anders Jacobsen
Anders Jacobsen Jan 3, 2020 08:10 AM

We try to use similar aproach when deailing with complex logic. It will make your logic much more solid as it does not depend on complex "if structures" and it gives the opotonity to target your unit test to each strategy implemented as they are seperated from each other.

Please login to comment.
Latest blogs
Integrating Optimizely DAM with Your Website

This article is the second in a series about integrating Optimizely DAM with websites. It discusses how to install the necessary package and code t...

Andrew Markham | Sep 28, 2024 | Syndicated blog

Opticon 2024 - highlights

I went to Opticon in Stockholm and here are my brief highlights based on the demos, presentations and roadmaps  Optimizely CMS SaaS will start to...

Daniel Ovaska | Sep 27, 2024

Required fields support in Optimizely Graph

It's been possible to have "required" properties (value must be entered) in the CMS for a long time. The required metadata haven't been reflected i...

Jonas Bergqvist | Sep 25, 2024

How to write a bespoke notification management system

Websites can be the perfect vehicle for notifying customers of important information quickly, whether it’s the latest offer, an operational message...

Nicole Drath | Sep 25, 2024