Click here to Skip to main content
Click here to Skip to main content
Go to top

The OOP Approach on MVC UI - System.Web.UI.Controls

, 7 Dec 2010
Rate this:
Please Sign up or sign in to vote.
How to work with Controls as fully typed objects on the MVC .ascx FormControls

OOP Approach on the ASP.NET MVC Controls/Pages

Download example or take the Catharsis Guidance for your own project.

The Point of Interest

ASP.NET MVC is here for a few years. On our projects, we profit a lot from the Model-View-Controller pattern. In comparison with Web-forms, we succeed to split solution into working separated pieces. The separation of concern allows us to concentrate on Controller layer on the communication with the Business tier and filling the Model with results.

The View in the next steps only consumes the Model and renders pure HTML. When user selects action (link) or sends the filled form, we again start in Controller and manipulate the data, the circle starts again.

But the trouble which we (and I guess the same for many of you) have and why we suffer is the "standard" UI code. If you want to create a simple page, you sooner or later end up with spaghetti code, mixing HTML, <% C# %> syntax or even worse, using the HtmlHelper. Let's have a look at some examples:

<h3>Fill the form</h3>
<div>
<% if (isReadOnly) { %>
<%= Html.Action("Clear") %>
<% } %>
<%= Html.UseDropDown(r => item.Roles, "Key", "Value", "Administrator") %>
<a href="<%: Url.Action("back") %>" >go back</a>
<div> 

The above snippet shows how your .ascx syntax could look like. Not only is there a real mishmash, but the worst is that you cannot manipulate with about code. You can only rewrite it... Do not try to discuss code reuse. There are plenty of string results, and no fully typed objects.

The Code Syntax in .NET

Suppose we are in the .NET 4.0 (C#). We have some collection and we would like to get the somthing from the List<User>:

var result = MyList.Where(i => i.Age > 18)
                   .Where(i => i.Country.Code.Equals("CS"))
                   .Select<User, string>(i => i.Login)
                   .FirstOrDefault();
// the result is string referencing some value ("Radim") or null 

The above snippet shows one of the edges of our current .NET environment. We can use fluent syntax to convert one object into another. And not only that. We are at any place of the code fully informed about the type, which we are accessing. The result has intended type and can be processed as needed.

Won't it be nice to have the same on the UI of our MVC controls?

But do not stop with overview of current possibilities we have in .NET.

var list = new List<string>
{
    "Administrator",
    "User",
    "Viewer",
}.AsReadOnly()
// or
var userDescription = new User { Login = "Radim", FirstName="Radim", Surname="Köhler" }
                          .ToDisplay();

This syntax not only allows effective code on a few rows, but also results in constructed object which is ready to be used for the next fluent handling (e.g. ToDisplay() call).

The Wish

So won't it be nice if we could:

  • Do the same on MVC UI (have the same syntax)
  • Work with fully typed objects Controls
  • Use fluent approach
  • DO NOT depend on any third-party contrib-libraries!

To make it clear, there is an example how the UI (.ascx) code should look like:

<%@ Control Language="C#"
Inherits="Firm.Example.Web.Controls.Home" %>
<%= AddControls
    (
        new Fieldset
            .SetLegend("User")
            .AddControls
            (
                new Div
                {
                    new Label("FirstName")
                        .SetFor("FirstName")
                    new Input(InputType.Text)
                        .SetName("FirstName")
                        .SetValue(item.FirstName)
                    ...
                }
                ...
            )
        ...
    )
%>

And of course, there should be a way how to create UserControls (some compounds), which encapsulates the inner Controls and publishes few easy to understand fluent Set() methods. The final syntax, based on reusable Controls could look like this:

<%= AddControls(
    new Fieldset("w70p mh100 ", "User")
    {
        new DefinitionList
        {      
            new TextOrInput().SetSourceProperty(() => Model.Item.FirstName),
            
            new TextOrInput().SetSourceProperty(() => Model.Item.Surname),
            
            new CheckBox()
                .SetSourceProperty(() => Model.Item.IsVisible)
                .SetCssClassName(Str.Align.Left),
            
            new TextOrInput(true)
                .SetSourceProperty(() => Model.Item.ID)
        }
    })
%>

resulting in this HTML syntax for READ state:

 <fieldset class="w70p mh100 "><legend>User</legend><div>
    <dl class=" p40">
        <dt><label>First name</label></dt> // localized FirstName
        <dd><div>Radim</div></dd>
    ...

and this for WRITE state:

 <fieldset class="w70p mh100 "><legend>User</legend><div>
    <dl class=" p40">
        <dt><label>First name</label></dt> // localized FirstName
        <dd><input type="text" value="Radim" name="FirstName" /></dd>
    ...

Where We Are?

In the next section, I would like to show you how easy and simple it is to introduce the System.Web.UI.Controls into the ASP.NET MVC page/control. We will only reuse, what is already implemented in System.Web! With few lines of code (injected into our application base controls), we will gain the power of OOP regardless the rendering engine. C# will be our language...

The next description comes from the Catharsis Framework, which is an Open Source. There is an example (zip) or take the Catharsis Guidance for your own new project. You will need only Visual Studio 2010 with C# to run example (only!). To use Guidance, you will need the GAX 2010 extension in your VS 2010.

The approach is common, so you do not have to use any components mentioned above. Just to create few base classes, with few methods. And that's the point.

The Origins

First of all, an example. Let's have the requirement for an Input html element, which would result in this HTML syntax:

 <input type="text" name="FirstName" value="Radim" /> 

The ASP.NET MVC built in solution could look like this:

    // I. Html syntax with inplace injecting runtime values
    <input type="text" name="FirstName" value="<%: item.FirstName %>" />

    // II. Extension method over the HtmlHelper
    <%= Html.TextBox("FirstName", item.FirstName) %>

The issue of both approaches stays in the static results. We cannot take these elements and do more with them, because they in fact are not objects. These are static marks rendering strings.

As soon as we would like to manage the rendering based on external switch, e.g. isReadonly bool value, we have to go this way: 

<% if(isReadOnly) { %>
    <%: item.FirstName %>
<% } else { %>
    ... // I. or II. markup mentioned above
<% } %>
OOP Approach

Imagine how we could do it in the OOP world, where we have two objects: Input and Literal:

<%= AddControls
    (
        isReadonly
        ? new Literal(item.FirstName)
        : new Input(InputType.Text)
              .SetName("FirstName")
              .SetValue(item.FirstName)
    )
%> 

Prerequisites for OOP

What we would need are two objects which will be able to render themselves as HTML controls and provide some fluent syntax. The next snippet shows the implementation of both controls. There is no but. The main part is shared in the base class ContentControl (as we will see it later):

Literal
    public class Literal : ContentControl<ICoreModel>
    {
        // constructor
        public Literal(string text = null)
        {
            SetText(text);
        }
        // manage render
        public override void RenderControl(HtmlTextWriter writer)
        {
            writer.WriteLine
            (
                HttpContext.Current.Server.HtmlEncode(Text)
            );
        }
        // Set
        public Literal SetText(string text, bool doLoclize = false)
        {
            if (text.Is())
            {
                Text = text;
            }
            return this;
        }
        // properties
        public virtual string Text { get; set; }
    }

There is everything we need. We have the setter for a Text value. And we also have the mechanism how to render result. And this is original, very effective HtmlTextWriter, and a RenderControl() method, derived from the TOP object System.Web.UI.Control.

Text is such a trivial property that could be also set in constructor, but also public SetText() fluent method is available.

The RenderControl() expects the HtmlTextWriter, and writes the content of this control there.

That's it. Nothing else. Now we have Literal control, which in fact represents any Text, even the Text for example in the <span></span> element (we will go deeper later). Other words, we have object representing InnerText for any other Control.

Input Element
    public class Input : ContentControl<ICoreModel, Input>
    {
        // constructor
        public Input(InputType type, string cssClassName = null)
            : base(cssClassName)
        {
            SetType(type);
        }
        // manage render
        public override void RenderControl(HtmlTextWriter writer)
        {
            writer.Write(Environment.NewLine);
            writer.WriteBeginTag(TagName);
            Attributes.Render(writer);
            writer.Write(HtmlTextWriter.SelfClosingTagEnd);
        }
        // Set
        public virtual Input SetValue(string value)
        {
            SetAttribute(Attr.Value, value);
            return this;
        }
        public virtual Input SetName(string name)
        {
            SetAttribute(Attr.Name, name);
            return this;
        }
        public virtual Input SetType(InputType type)
        {
            // skipped to reduce snippet, see the example for details...
            return this;
        }
        // properties
        protected override string TagName { get { return Tag.Input; } }
    }

Input element in HTML is represented by self closing tag with some attributes.

Constructor demands the type of the Input (text, hidden...) and supports also quick way to set CSS class.

Both constructor arguments are either mandatory (InputType) or again trival (CSS).

The other settings (which are optional) are provided via fluent syntax SetName(), SetValue() ...

Where We Are, Having Literal and Input?

The point until now is, that we created C# objects, which are derived from the System.Web.UI.Control. So we are introducing nothing new. We are simply profiting from the ages proven Server controls known from ASP.NET. So what in fact are we using?

  1. The RenderControl() method building the result with the most efficient HtmlTextWriter (and its underlying StringBuilder)
  2. The Attributes collection! Yes, every control has its own collection of attributes which we are setting in SetAttribute("attrName", "attrValue");
  3. The native HTML Encoding! Where? When working with the mentioned Attribute collection, the ASP.NET will Encode every attribute value for us!
  4. The events (used mainly in the Compound Controls like TextOrInput. E.g. OnPreRender() could help us to decide if the Input or Span will be used
  5. The Controls collection. Wow! Every Control already has its own child control collection. It can be used or not. Literal does not provide any smart (fluent) way how to add child, because any item in Controls will be swallowed anyway (RenderControl does not call RenderChildren(). But as we will see, other (almost every other, DIV, SPAN, A...) will profit from this collection a lot
  6. Any possible properties: Page, Parent, Url, Model ... are available!
  7. The last but not least! No third party library, no ASP.NET MVC HtmlHelper ... pure C#, OOP and efficiency

What Must Be Said! Set() phase, Render() phase

The control should be handled the correct way, due its specific LifeCycle (the natural System.Web.UI.Control behaviour). In the first Set() phase when we are using fluent syntax, we must be aware of the fact, that we are in some OnVerySoonPreInitPhase. Other words, in fluent Set() we should only gather information (in Attribute collection or member fields) and cannot touch the Properties as Page, Parent, Url, Model which will be injected in the right time - later.

On the other hand, we have the events, for example OnPreRender() which are the right places where we can summarize information and decide what to render! There is no other BUT. Just do things correctly in the right time.

What could be surprising or a bit hidden until know is the LifeCycle of these objects. The native methods/events of the System.Web.UI.Control (such as OnPreRender()) are back in play.

ContentControl

The base class ContentControl<TModel> is the immediate parent of these controls: 

    public class ContentControl<TModel> : VisualControl<TModel>, IContentControl
        where TModel : class, ICoreModel
    {
        // Attributes
        protected virtual string GetAttribute(string attributeName)
        {
            if (attributeName.IsNotEmpty())
            {
                return AttributesattributeName];
            }
            return string.Empty;
        }
        protected virtual void SetAttribute(string attributeName, string value)
        {
            if (attributeName.IsNotEmpty()
              && value.Is())
            {
                Attributes.Add(attributeName, value);
            }
        }
        // Render
        public override void RenderControl(HtmlTextWriter writer)
        {
            RenderBeginTag(writer);
            RenderContent(writer);
            RenderEndTag(writer);
        }
        protected virtual void RenderBeginTag(HtmlTextWriter writer)
        {
            if (TagName.IsNotEmpty())
            {
                writer.Write(Environment.NewLine);
                writer.WriteBeginTag(TagName);
                Attributes.Render(writer);
                writer.Write(HtmlTextWriter.TagRightChar);
            }
        }
        protected virtual void RenderContent(HtmlTextWriter writer)
        {
            foreach (Control control in Controls)
            {
                RenderChild(writer, control);
            }
        }
        protected virtual void RenderEndTag(HtmlTextWriter writer)
        {
            if (TagName.IsNotEmpty())
            {
                writer.WriteEndTag(TagName);
            }
        }
        protected virtual void RenderChild(HtmlTextWriter writer, Control control)
        {
            control.RenderControl(writer);
        }
        // Element rendering switch,
        // when TagName is in the derived control overriden 
        // (and returns value as "DIV")
        // the basic Render() events are triggered
        protected virtual string TagName { get { return string.Empty; } }
    }

There are two sections of the ContentControl<TModel> class implementation.

Fluent Set() Attributes

The first provides a smart way to the underlying Attribute collection. In fact there is much more then only wrapper. There is a fluent if! If method is called with empty or null attribute name, nothing will happen. If the value of the attribute is null, nothing will happen. This will be evaluated in practice or on the below example:

string title = "FirstName";
string localizedTitle = null;
var input = new Input(InputType.Text)
                .SetTitle(GetLocalized(title)) // now the Title is set 
					// using the title!
                .SetTitle(localizedTitle) 	// only if there was intended 
					// localized phrase - use it

Render() Support

The second feature comes with the basic Render() implementation. RenderControl() is split into few methods, and child Controls can decide, if they will Override the TOP RenderControl() (and hide the rest) or only adjust one of the protected methods/events.

Because many element Controls (div, span, button...) will mostly do the same.

Where We Are?

At this moment, we have ContentControls, which:

  • Have and use the Attribute collection (which takes cares about HTML Encoding)
  • Have the base for fluent syntax SetAttribute()
  • Manages the Render() to meet the control aims.

What We Need (topics for the next article)

The missing, and relatively simple parts should cover the rest:

  • Put fluent syntax into play
  • Extend ContentControls with the AddControls() for easy Control nesting
  • Extend ContentControl to support "Property/List initialization" (e.g. new Div { new Span(), new Literal() })
  • Show how to create compound controls
  • AddControls into the templated .ascx controls (FormControl)
  • Set the Models, Parents, Pages, Url... to children
  • Show how to pass the sub model (ViewDataKey="ListModel" for e.g. Model.ListModel)

Still lot to do in next article.... but as you will see, very little code.

Catharsis Base Controls

... mean while you can take a look at the below links, where you can find more.

There is an application base class in Catharsis for every control: VisualControl. It had two main branches of children:

  1. The FormControl, the base class for any code-behind class used for every .ascx (templated) control (see the details here).
  2. The ContentControl class provides very basic, straightforward and sufficient implementation for any Control we will need in the application. (see the details here).

And the example is here.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Radim Köhler
Web Developer
Czech Republic Czech Republic
Web developer since 2002, .NET since 2005
 
Competition is as important as Cooperation.

___

Comments and Discussions

 
GeneralBetter not to over use fluent styles PinmemberRajeesh.C.V24-Feb-11 0:40 
GeneralVery good, but ... Pinmemberstevenlauwers2213-Dec-10 20:47 
GeneralRe: Very good, but ... PinmemberRadim Köhler13-Dec-10 21:07 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140926.1 | Last Updated 7 Dec 2010
Article Copyright 2010 by Radim Köhler
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid