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!
Comments