Chainable control creation with extension methods

30. December 2009

Many times one need to create controls and add them to an ASP.NET page/control from code. Sharepoint is the master example when this need is more somewhat of a rule than exception.

Looking at the dull Sharepoint webpart code sample at MSDN (in my opinion) show the tedious task of constantly creating, configuring and adding controls to other control containers.

Knowing that Jquery has the abillity to chain methods one after another, and .NET’s abillity to write extension methods the ingridients are set up for some .NET chained method extension voodo ;)

About the sample

In this simple demo I want to show the same pattern for creating a simple UI with first creating control in the same manner as the MSDN example, and secondly with extension methods, after the demo I will show/talk about the solution.

“Msdn-style”

using System;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

public partial class _Default : System.Web.UI.Page {

    protected Button btnSubmit;
    protected TextBox txtUsername;

    protected void Page_Load(object sender, EventArgs e) {
        var t = new HtmlTable();
        plcControlContainer.Controls.Add(t);

        // row1
        var r = new HtmlTableRow();
        t.Rows.Add(r);

        var c = new HtmlTableCell(); // cell1
        r.Cells.Add(c);

        var ctrl = new Label();
        // set properties
        ctrl.Text = "Username";
        ctrl.ToolTip = "your selected username";
        c.Controls.Add(ctrl);

        c = new HtmlTableCell(); // cell2
        r.Cells.Add(c);

        txtUsername = new TextBox();
        c.Controls.Add(txtUsername);


        // row 2
        r = new HtmlTableRow();
        t.Rows.Add(r);

        c = new HtmlTableCell(); // cell1
        r.Cells.Add(c);

        c = new HtmlTableCell(); // cell2
        r.Cells.Add(c);

        btnSubmit = new Button();
        // set properties
        btnSubmit.Text = "Submit";
        btnSubmit.CssClass = "submit";
        btnSubmit.Click += new EventHandler(btnSubmit_Click);
        c.Controls.Add(btnSubmit);
    }
}

Chainable Extension style

using System;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

public partial class _Default : System.Web.UI.Page {

    protected Button btnSubmit;
    protected TextBox txtUsername;

    protected void Page_Load(object sender, EventArgs e) {
        var t = plcControlContainer.Controls.AddWithRef(new HtmlTable());

        // row1
        var r1 = t.Rows.Add();
        r1.Cells.Add().Controls.Add(new Label() { Text = "Username", ToolTip = "your selected username" });
        txtUsername = r1.Cells.Add().Controls.AddWithRef(new TextBox());

        // row2        
        var r2 = t.Rows.Add();
        r2.Cells.Add();

        btnSubmit = r2.Cells.Add().Controls.AddWithRef(new Button() { Text = "Submit", CssClass = "submit" });
        btnSubmit.Click += new EventHandler(btnSubmit_Click);

        // or alternative
        //var btn = t.Rows.Add().Cells.Add().ParentRow().Cells.Add().Controls.AddWithRef(new Button() { Text = "Submit", CssClass = "submit" });        
    }

About the solution

Somewhat less code to add controls to a sample page (don’t you think? )

The solution uses extension methods and some synthetic sugar from .net to be able to set properties within curly braces { here } directly.

The first part of the solution uses a method with the name AddWithRef and adds AND returns a back reference to the added control, thanks to this we can now create AND add the table directly (with the reference) and… it’s strongly typed! :D (t is a HtmlTable )

The second part of the solution is that (in my opinion) if I write code that says Add I would like to ADD something, and if it’s a strongly typed contract of what to add why not add it FOR me (in a convention over configuration manner)? For a row it’s the actual cell ; add backreference to it and we’re off to be able to chain the result like:
t.Rows.Add().Cells.Add().Controls.Add( some control here);

and with backreference for the control (to set up things like event handlers):

var ctrl = t.Rows.Add().Cells.Add().Controls.AddWithRef( some control here);

The code

Show us the code!?? relax here it is

TableExtensions.cs

using System.Web.UI;
using System.Web.UI.HtmlControls;


public static class TableExtensions {

    public static HtmlTableCell AddWithRef(this HtmlTableRow row, HtmlTableCell cell) {
        row.Cells.Add(cell);
        return cell;
    }


    public static HtmlTableRow AddWithRef(this HtmlTable table, HtmlTableRow row) {
        table.Rows.Add(row);                  
        return row;
    }

    public static HtmlTableRow Add(this HtmlTableRowCollection rows) {
        var r = new HtmlTableRow();
        rows.Add(r);                     
        return r;
    }

    public static HtmlTableCell Add(this HtmlTableCellCollection cells) {
        var c = new HtmlTableCell();
        cells.Add(c);                      
        return c;
    }

    public static HtmlTableRow ParentRow(this HtmlTableCell cell) {
        if (cell.Parent == null) return null;
        return (HtmlTableRow)cell.Parent;
        // or alternative with boxing ( as ) if  that rocks your boat (as it's return null if the parent is not of type HtmlTableRow
        //return cell.Parent as HtmlTableRow;
    }
}
 

CollectionExtensions.cs
It’s strongly typed AND limited to inherit/be of type control

public static class CollectionExtensions {

    /// <summary>
    /// Generic extension method
    /// </summary>
    /// <typeparam name="T">A generic control strongly typed</typeparam>
    /// <param name="collection"></param>
    /// <param name="control">Implementing interface of class Control <seealso cref="System.Web.UI.Control"/></param>
    /// <returns>A strongly typed reference back to the added control</returns>
    public static T AddWithRef<T>(this ControlCollection collection, T control) where T : Control {
        collection.Add(control);
        return control;
    }
}

// Hopefully this triggers some spinoff ideas for you :D

Just 1 word of caution, if you like me REALLY like extension methods you will soon end up with *many of your own creations ;) , to avoid unnecessary member signature spamming use namespaces for your extension methods so that only needed parts of your code gets affected/reached.

ASP.NET, Extension methods, SharePoint , , , ,

Comments are closed