WebForms And MVC In Harmony -- Almost...

What happens with the following snippet of ASP.NET code?

<% int number = 5; %>
<asp:PlaceHolder runat="server" >
<% =number %>    
</asp:PlaceHolder>

Prints the number 5? Nope. Maybe it equals zero? Sorta. How about this...

Compiler Error Message: CS0103: The name 'number' does not exist in the current context

Yikes...

I've complained before about the disconnect between WebControls and actual inline code. WebControls are still a very convenient way to write templates but because they exist in a different context than inline code they are effectively off limits. As cool as MVC is you're pretty much stuck throwing all your existing WebControls out the window. Or are you?

Using Extension Methods Instead of Controls

Extension Methods came in really at the best time possible. I can't see MVC working without them.

If you've never used one before, an Extension Method lets you create a static method else where in your project, do a couple fancy assignments and then it attaches that method onto the class you're targeting. LINQ heavily relies on Extension Methods to provide such a seamless programming experience.

One way that ASP.NET MVC uses Extension Methods is to make working with certain control types easier. For example there is a method to create the input tag, one to render a form tag, etc...

Below is an example of how you could create an Extension Method that is attached to the HtmlHelper.

public static class MyExtensionMethods {

    //example method - don't write things this ugly
    public static string BulletList(this HtmlHelper helper, params string[] items) {
        return string.Concat(
            "<ul><li>",
            string.Join("</li><li>", items),
            "</li></ul>"
            );
    }
}

In our example we create a static class to house our Extension Methods. We also create static methods with a strange argument at the start. This argument is actually the class were attaching the method to. Now we can use our code like so...

<% =this.Html.BulletList("Apple", "Orange", "Pear") %>

Cool. If you're not familiar on the things you can do with Extension Methods then I recommend you read about them some more before you start trying to add them to your project. You could also use delegates to simulate templating within an Extension Method.

public static class MyExtensionMethods {

    //example method - renders the content of each action
    public static void TwoColumns(this HtmlHelper helper, Action left, Action right) {
        HttpContext.Current.Response.Write("<div class='left'>");
        left();
        HttpContext.Current.Response.Write("</div>");

        HttpContext.Current.Response.Write("<div class='right'>");
        right();
        HttpContext.Current.Response.Write("</div>");
    }
}

Then you can use your "template" like so...

<% this.Html.TwoColumns(() => { /* Left Column */ %>
    I'm on the left!
<% }, () => { /* Right Column */ %>
    I'm on the right!
<% }); /* End Two Column */ %>

Code like this can get ugly in a hurry - so be conservative in your use.

Using IDisposable To Close Tags

Another way you can create a "WebControl" with ASP.NET MVC is to create a class that implements IDisposable. By placing markup in the constructor and the Dispose method you can essentially write your RenderBeginTag() and RenderEndTag() methods you normally find on CompositeControls!

public class StyledHeader : IDisposable {

    public StyledHeader(string color) {
        HttpContext.Current.Response.Write("<h1 style='color:" + color + "' >");
    }

    public void Dispose() {
        HttpContext.Current.Response.Write("</h1>");
    }
}

Naturally, StyledHeader should have been added to the core of the ASP.NET MVC library, but somehow it got missed @html.smile(). In any case, our class can be used with the using keyword to render our fancy new header.

<% using (new StyledHeader("#f00")) { %>
    Howdy - This is my header control!
<% } /* End StyledHeader */ %>

The Super Secret Final Method

As you noticed at the beginning of my post I mentioned about throwing away WebControls since they aren't any use to us anymore. Well, that isn't true -- We can still use WebControl with our inline code for the page!

If you've read any of my previous blog posts, you can see that I'm a big fan of overriding the Render() method for WebControls. In similar fashion, we're going to use the RenderControl() method to render our WebControls right when we need them.

using System.Reflection;
using System.IO;
using System.Web.UI;
using System.Web;
using System.Web.Mvc;

public static class MyExtensionMethods {

    //example method - renders a webcontrol to the page
    public static void RenderControl(this HtmlHelper helper, Control control) {

        //perform databinding if needed
        //MethodInfo bind = control.GetType().GetMethod("DataBind");
        //if (bind is System.Reflection.MethodInfo) {
        //    bind.Invoke(control, null);
        //}

        //Call a courtesy databind
        //Thanks for pointing it out Richard
        control.DataBind();

        //render the HTML for this control
        StringWriter writer = new StringWriter();
        HtmlTextWriter html = new HtmlTextWriter(writer);       
        control.RenderControl(html);

        //write the output 
        HttpContext.Current.Response.Write(writer.ToString());

        //and cleanup the writers
        html.Dispose();
        writer.Dispose();         
    }

}

You may notice the courtesy DataBind() call we're doing there -- Just in case something has a DataSource I was calling the method as well. Depending on how you use this you may want to change this some. But enough of that, how is it used?

<% int[] numbers = { 1, 2, 3, 4, 5 }; %>
<% this.Html.RenderControl(new DataGrid() { DataSource = numbers }); %>

You can also define your class before you pass it into the RenderControl method in case you need to do a little more to it than just assign some values to the properties.

Finally, WebForms and MVC In Harmony... Or Maybe Not...

Now I won't pretend that you can plug all of your WebControls into this and expect it to work like WebForms used to. A lot of things are missing that a lot of WebControls rely on (like the ViewState). But, if your mainly interested in the rendered output of a WebControl then you're in luck.

June 18, 2009

WebForms And MVC In Harmony -- Almost...

Post titled "WebForms And MVC In Harmony -- Almost..."