Monday, September 30, 2013

Fluent MVC Extensions in Class Hierarchy


In my previous blog I showed on how to create fluent MVC extensions. In that blog I created a Label extension with two extension methods Text and Target. Both these extension methods are chained together using the fluent design pattern. This kind of fluent design pattern works well if we creating some small or standalone controls.

When we are creating an extension library which contains complex controls like that of Juime. We need to define a rich set of base class libraries which provide common functionality across all controls. Some of these base class functionalities can be CssClass, HtmlAttributes. If you look at the ASP.NET Web Form controls you can notice this base class hierarchy. In this blog I will show you how to create a base class hierarchy that participates in the fluent design pattern.

From my previous blog here is the code for defining a Label extension.

    public static class LabelExtensions
    {
        public static Label FluentLabel(this HtmlHelper helper)
        {
            return new Label();
        }
    }


    public class Label, IHtmlString
    {
        private string target, text;

        public Label Target(string target)
        {
            this.target = target;
            return this;
        }

        public Label Text(string text)
        {
            this.text = text;
            return this;
        }

        public override string ToString()
        {
            return ToHtmlString();
        }

        public string ToHtmlString()
        {
            return String.Format("<label for='{0}'>{1}</label>Fluent", target, text); ;
        }
    }

Here is how the above Label extension is consumed in the View.

@(Html.FluentLabel()
        .Target("firstName")
        .Text("First Name")
             )

Our goal is split the Label into a parent and child class without impacting how the extension is consumed in the View.

For this I define a base class for Label called Control. Into this control, I will move the Text method. Here is my code (which is not the final code)

    public class Label : Control, IHtmlString
    {
        protected string target;

        public Label Target(string target)
        {
            this.target = target;
            return this;
        }

        public Label Text(string text)
        {
            this.text = text;
            return this;
        }

        public override string ToString()
        {
            return ToHtmlString();
        }

        public string ToHtmlString()
        {
            return String.Format("<label for='{0}'>{1}</label>Fluent", target, text); ;
        }
    }

    public class Control
    {
        protected string text;

        public Control Text(string text)
        {
            this.text = text;
            return this;
        }
    }

The problem with this code is the Text method, it is returning the base class Control. In order to participate properly with the Label extension, the base class Control should return the Label datatype (derived class) from the Text method.

As Control is the base class, it will not have any idea of the derived classes. The only way Control knows about its child objects is that when they are passed to the Control class as inputs. As you know we can pass the class type as input by using generics. Hence the Control base class should be a generic class which takes the derived type as generic input and this passed in derived class is returned as the function output of the Text method. Here is the final code of the base class with generic derived input parameter.

    public class Control<T> where T : Control<T>
    {
        protected string text;

        public T Text(string text)
        {
            this.text = text;
            return (T) this;
        }
    }

Here is how the Label class is derived from the Control class

    public class Label : Control<Label>, IHtmlString

In the above code Label type is passed as the input to the Control class and this generic type is returned as the return type for the Text method. Also note that the where clause in the Control class definition is needed for type casting this to the derived class.
This generic trick will make our base class return the derived class type, Label, thereby allowing us to use fluent design pattern in control hierarchy. Here is the final code.

    public static class LabelExtensions
    {
        public static Label FluentLabel(this HtmlHelper helper)
        {
            return new Label();
        }
    }


    public class Label : Control<Label>, IHtmlString
    {
        protected string target;

        public Label Target(string target)
        {
            this.target = target;
            return this;
        }


        public override string ToString()
        {
            return ToHtmlString();
        }

         public string ToHtmlString()
        {
            return String.Format("<label for='{0}'>{1}</label>Fluent", target, text); ;
        }
    }


    public class Control<T> where T : Control<T>
    {
        protected string text;

        public T Text(string text)
        {
            this.text = text;
            return (T) this;
        }
    }

As you see we were able to extend the fluent design to the class hierarchy without impacting how the control is used in the View.

No comments:

Post a Comment