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.