Tags: , , , | Categories: Code Development, Web Posted by nurih on 7/7/2009 5:14 AM | Comments (0)

In this article we'll explore the use of classic ASP.NET controls in combination with extension methods in the MVC framework in order to facilitate development while remaining true to the MVC pattern and best practices.

A lot of hype around MVC these days. So, of course, yours truly is working on some project utilizing MVC. While brushing the dust off my raw HTML tag memory and designing the obvious: lists, grids, repeated item displays and the such, I thought "why not use the asp.net controls instead?"

For one, most of the ASP.NET data bound controls have a rich site of event post backs to hook up. MVC says: no dice! You can't rely on the event dispatch loop of asp.net. In fact, this is the whole point of going MVC – to create a simple, well separated delineation between the presentation and the BL. If you have all these states and post back logic pieces all in the code behind you really have a tightly coupled application.

What *is* available though, is the rendering of most controls. So instead of "for each" and "if" statements sprinkled in the "presentation" template, you can actually just use the plain old asp.net controls declaratively. I put "presentation" in quotation marks because the more code (branching is code!) you have in the View, the more brittle it becomes, the more tightly coupled to BL it is and the less testable it is.

The main advantages I find in using the old controls is that:

  1. Familiarity – My developers know them already
  2. Rich – Theming, localization behavior and layout aspects built in and available
  3. Well tested – These controls have been around a while, and are generally well behaved
  4. Modular – The controls isolate the presentation of a particular element and divorce it from the surrounding. In particular, it avoids issues of nesting and other inline logic in the presentation layer.

The main issue to overcome in a DataBoundControl is that with no code behind, how do you specify the data source declaratively? How do you do so with minimum code and maximum flexibility?

One solution is to use a data source object. Yes, they work. In most scenarios you can use them as you did before, but you must note that hooking up events (OnSelecting, OnSelected and the such) would again be challenging to do declaratively.

My approach for my project was to create an extension method for

DataBoundControl has a property "DataSource", to which you assign an object that will be data bound. The most permissive base object System.Object, so we simply create an extension method:

 

using System.Web.UI.WebControls;

public static class DataboundControlExtensions
{
    /// <summary>
    /// Extends <typeparamref name="System.Object"/> to bind it to a <typeparamref name="System.Web.UI.WebControls.DataList"/>
    /// </summary>
    /// <param name="data">The data source object to bind.</param>
    /// <param name="control">The control to bind the data source object to.</param>
    public static void BindTo(this object data, DataList control)
    {
        control.DataSource = data;
        control.DataBind();
    }

    /// <summary>
    /// Extends <typeparamref name="System.Object"/> to bind it to a <typeparamref name="System.Web.UI.WebControls.ListView"/>
    /// </summary>
    /// <param name="data">The data source object to bind.</param>
    /// <param name="control">The control to bind the data source object to.</param>
    public static void BindTo(this object data, ListView control)
    {
        control.DataSource = data;
        control.DataBind();
    }
}

Note that the control is passed in as the target and is strongly typed. As it turns out, you get a runtime error if you attempt to specify the control parameter typed as DataBoundControl – the base class for all data bound controls. In other words, this doesn't work:

public static void BindTo(this object data, DataBoundControl control) // Runtime error!

With the extension included in my project, I can now add the control and  bind it in one code line:

<% Model.BindTo(dataList); %>
<asp:DataList runat="server" ID="dataList" RepeatColumns="2" RepeatDirection="Horizontal">
    <ItemTemplate>
        <fieldset>
            <legend>
                <%# Eval("ID")%></legend>
            <p>
                Title:
                <%#Eval("Title") %>
            </p>
            <a href='<%#Eval("ImagePath") %>'>
                <img src='<%#Eval("ThumbPath") %>'  height="64" width="48" alt="pic"/></a>
            <p>
                Description:
                <%#Eval("Description") %>
            </p>
        </fieldset>
    </ItemTemplate>
</asp:DataList>

This is not as clean as completely declarative binding, but is much cleaner than having loop and branch constructs all over your view.

Most of you would note that the current MVC release includes HTML helpers. The Futures release is promising several more HTML helpers, which would provide with single line calls that would render lists, and other data bound presentation we are accustomed to. The reasons I didn't use the futures HTML helper methods are:

  1. The futures were not in release yet at time
  2. I don't love the mode in which the HTML helpers work
  3. The HTML helpers are not as rich as the asp.net components

The reason I don't love the HTML helpers is simple: they are string valued functions. As string valued functions, they must, internally generate strings and throw away memory. Even when you use StringBuilder, the repetitive appendage of strings creates discarded buffers in the underlying byte array. Asp.Net controls, on the other hand, have access to the underlying output stream via a TextWriter. This means that the appended string snippets do not create intermediary buffers but rather get copied more efficiently to the actual byte stream that would be shipped to the browser.

So if I need to add an HTML helper to MVC what I do is write an extension method that extends System.Web.MVC.ViewPage, like so

using System.Web.Mvc;
using System.Web.UI;

public static class HelloKittyHelper
{
    public static void Meow(this ViewPage page, string text)
    {
        HtmlTextWriter writer = new HtmlTextWriter(page.Response.Output);
        if (writer != null)
        {
            writer.RenderBeginTag(HtmlTextWriterTag.Pre);
            writer.Write(KITTY_ASCII);
            writer.Write(text);
            writer.RenderEndTag();
        }
    }
    /// <summary>
    /// ASCII art of a kitty - source unknown.
    /// </summary>
        private const string KITTY_ASCII = @"
  .-. __ _ .-.
  |  `  / \  |
  /     '.()--\
 |         '._/
_| O   _   O |_
=\    '-'    /=
  '-._____.-'
  /`/\___/\`\
 /\/o     o\/\
(_|         |_)
  |____,____|
  (____|____)
";
}

Then pass it the data from the Model or anything you like from the view:

<%
   1:  this.Meow("purrr.. meow");  
%>

With the ambient objects in the page, I just extend "this", and internally utilize the HtmlTextWriter to write directly to the stream – no intermediary StringBuilder and hopefully less wasted immutable objects. The other advantage is that HtmlTextWriter can do things like begin/end tag tracking, add attributes and encode HTML as necessary. Leveraging existing well tested framework again, reducing costs of testing and education to deliver value early and often.

 

In conclusion, we can use the new MVC concepts and remain true to the separation of view and logic by leveraging existing components and developing minimal custom extensions that help us avoid the pitfalls of "presentation logic".