Try our conversational search powered by Generative AI!

K Khan
Jan 1, 2020
  2110
(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
Optimizely and the never-ending story of the missing globe!

I've worked with Optimizely CMS for 14 years, and there are two things I'm obsessed with: Link validation and the globe that keeps disappearing on...

Tomas Hensrud Gulla | Apr 18, 2024 | Syndicated blog

Visitor Groups Usage Report For Optimizely CMS 12

This add-on offers detailed information on how visitor groups are used and how effective they are within Optimizely CMS. Editors can monitor and...

Adnan Zameer | Apr 18, 2024 | Syndicated blog

Azure AI Language – Abstractive Summarisation in Optimizely CMS

In this article, I show how the abstraction summarisation feature provided by the Azure AI Language platform, can be used within Optimizely CMS to...

Anil Patel | Apr 18, 2024 | Syndicated blog

Fix your Search & Navigation (Find) indexing job, please

Once upon a time, a colleague asked me to look into a customer database with weird spikes in database log usage. (You might start to wonder why I a...

Quan Mai | Apr 17, 2024 | Syndicated blog