Factory Pattern In Order To Show Different UI Conditionally

Introduction

A requirement of a certain client was as follows:

  • Display a view that some of its content hide and show dynamically.
  • The show and hide is determined by a value selected from a drop-down list.

Example 

////////////////////////
<select id="ddlShowHideDiv">
    <option value="0">select </option>
    <option value="1">show div1</option>
    <option value="2">show div2</option>
</select>
    <div id="div1">
        div 1 content
    </div>
    <div id="div2">
        div 2 content
    </div>            
<script>
$("#ddlShowHideDiv").change(function() {
    if ($(this).val() == "1")) {
    $("#div1").css("display", "block")
    $("#div2").css("display", "none")
}
if ($(this).val() == "2")) {
    $("#div1").css("display", "none")
    $("#div2").css("display", "block")
  }
})
</script>

///////////////////////////////////

The problem in this implementation is that it,

  1. Breaks Single Responsibility Principle, since the rendering is the responsibility of the controller.
  2. It has broken the Open-Closed Principle when the Js Code has been modified to deal with a new value-added to the drop-down.

Solution Implementation

The design of the solution fixed the problem through the following,

  • Moving the rendering part to the controller
  • Applying Factory Pattern in order to maintain the application of Open Closed Principle

Solution implementation is illustrated through the following walkthrough.

Solution Implementation WalkThrough

Create a new ASP.NET MVC Core application in Visual Studio 2019 as follows - (Name the project ApplyingFactoryPatternToRepresentDifferentUI).

Under the Models folder, create a new Model Class called a base model.

The following code is to be placed in the class created,

public abstract class BaseModel {
    public string CommonField1 {
        get;
        set;
    }
    public string CommonField2 {
        get;
        set;
    }
}

The purpose of this class is to contain the common fields which always appear in the view.

Create the following 2 classes in the same place as the previous one,

public class DerivedModel1: BaseModel {
    public string Model1Field1 {
        get;
        set;
    }
    public string Model1Field2 {
        get;
        set;
    }
}
public class DerivedModel2: BaseModel {
    public string Model2Field1 {
        get;
        set;
    }
    public string Model2Field2 {
        get;
        set;
    }
}

The purpose of these 2 classes is to contain fields that show and hide conditionally.

Create a folder called creator with the interfaces and classes as below,

//The interface code
interface IModelCreator {
    public IActionResult Create();
}
//The DerivedModel1Creator class code
public class DerivedModel1Creator: Creator.Intrface.IModelCreator {
    public IActionResult Create() {
        PartialViewResult viewResult = new PartialViewResult();
        viewResult.ViewName = "DerivedModel1";
        return viewResult;
    }
}
//The DerivedModel1Creator class code
public class DerivedModel2Creator: Creator.Intrface.IModelCreator {
    public IActionResult Create() {
        PartialViewResult viewResult = new PartialViewResult();
        viewResult.ViewName = "DerivedModel2";
        return viewResult;
    }
}

The interface will act as an extension base that, when implemented, will render different content conditionally.

Create 2 new partial views named DerivedModel1, DerivedModel2 respectively under the views home folder.

The DerivedModel1 View code,

//// @model ApplyingFactoryPatternToRepresentDifferentUI.Models.DerivedModel1 @{ ViewData["Title"] = "DerivedModel1"; } <h1>View Model1</h1>
<hr />
<div class="row">
  <div class="col-md-4">
    <div class="form-group">
      <label asp-for="Model1Field1" class="control-label"></label>
      <input asp-for="Model1Field1" class="form-control" />
      <span asp-validation-for="Model1Field1" class="text-danger"></span>
    </div>
    <div class="form-group">
      <label asp-for="Model1Field2" class="control-label"></label>
      <input asp-for="Model1Field2" class="form-control" />
      <span asp-validation-for="Model1Field2" class="text-danger"></span>
    </div>
  </div>
</div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} } ////

The DerivedModel2 View code,

///// @model ApplyingFactoryPatterToRepresentDifferentUI.Models.DerivedModel2 @{ ViewData["Title"] = "DerivedModel2"; } <h1>View Model2</h1>
<hr />
<div class="row">
  <div class="col-md-4">
    <div class="form-group">
      <label asp-for="Model2Field1" class="control-label"></label>
      <input asp-for="Model2Field1" class="form-control" />
      <span asp-validation-for="Model2Field1" class="text-danger"></span>
    </div>
    <div class="form-group">
      <label asp-for="Model2Field2" class="control-label"></label>
      <input asp-for="Model2Field2" class="form-control" />
      <span asp-validation-for="Model2Field2" class="text-danger"></span>
    </div>
  </div>
</div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} } //////

 Replace the code in the index view under views\home\index.cshtml with the following code.This code allows you to choose the content you want to display from a dropdown list

@model ApplyingFactoryPatternToRepresentDifferentUI.Models.BaseModel
@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4">Welcome</h1>
    <p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
<div class="row">
    <div class="col-md-4">
        <form asp-action="View1">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="CommonField1" class="control-label"></label>
                <input asp-for="CommonField1" class="form-control" />
                <span asp-validation-for="CommonField1" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="CommonField2" class="control-label"></label>
                <input asp-for="CommonField2" class="form-control" />
                <span asp-validation-for="CommonField2" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label class="control-label">Type of object</label>
                <select onchange="" id="ddlObjectType">
                    <option value="0" selected>Select </option>
                    <option value="1">Type 1 </option>
                    <option value="2">Type 2 </option>
                </select>
            </div>
            <div id="divShowDifferentContent">

            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
    <script>
    $(document).ready(function () {
        handleDdlChange()
    })
    function handleDdlChange() {
        $("#ddlObjectType").change(function () {
            $.ajax({
                url: "@Url.Action("ShowDifferentContent","Home")",
                data: {"derivedModelType":$(this).val()}
            }).done(function (html) {
                $("#divShowDifferentContent").html(html);
            });
        })
    }
    </script>
}

 

Add the following methods to the end of the code in controlles\homecontroller.cs,

public IActionResult ShowDifferentContent(int derivedModelType) {
    return ShowContentAccordingToType(derivedModelType);
}
private IActionResult ShowContentAccordingToType(int derivedModelType) {
    //this dictionary is for mapping the parameter value to the specific implementation of IModelCreator class
    Dictionary ViewModels;
    ViewModels = new Dictionary();
    ViewModels.Add(1, new DerivedModel1Creator());
    ViewModels.Add(2, new DerivedModel2Creator());
    IModelCreator modelCreator = null;
    modelCreator = ViewModels.Where(S => S.Key == derivedModelType).First().Value;
    return modelCreator.Create();
}

The first method called showDifferentContent will be called by the drop-down list value change handler mentioned in the previous step.

The dictionary object in the second step simulates a structure that maps the value selected to the content. In the real world, this should be populated from a setting repository.

Finally, run the application and test the behavior when selecting a value from the drop-down list.

Conclusion and Points of Interest

This division has been totally created by me and neither suggested nor copied from anybody or anywhere.

This is the first practical example on which I have applied the design principle(s) mentioned. So feedbacks are mostly welcomed.

References

  • https://www.baeldung.com/java-open-closed-principle
  • https://refactoring.guru/design-patterns/factory-method/csharp/example