K Khan
Jan 1, 2020
  1665
(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
Implementing EmbeddedLocalization in Optimizely CMS 12

My previous post on translation (Translating Optimizely CMS 12 UI components) gives an overview of how to implement the FileXmlLocalizationProvider...

Eric Herlitz | Jan 27, 2023 | Syndicated blog

Breaking changes in EPiServer.CMS.TinyMce 4.0.0

After upgrading to the latest version of EPiServer.CMS.TinyMce, the dropdown with formats disappears. Learn how to get it back!

Tomas Hensrud Gulla | Jan 27, 2023 | Syndicated blog

Translating Optimizely CMS 12 UI components

Optimizely CMS 12 have been out for a while now, but still some elements haven't been properly translated resulting in a GUI defaulting to english....

Eric Herlitz | Jan 26, 2023 | Syndicated blog

Image preview in Optimizely CMS12 all properties view

With these simple steps, you can now see an Image and its Metadata, including size and dimensions, when editing an Image property in Optimizely...

Tomas Hensrud Gulla | Jan 26, 2023 | Syndicated blog