Functional Construction for ASP.NET Web Forms

System.Xml.Linq (a.k.a. LINQ to XML) introduces a nifty approach to creating XML elements called functional construction. I’m not entirely sure why they call it functional given that constructing an object graph is a decidedly non-functional task in the traditional sense of the word, but I digress.

Functional construction has three key features:

  1. Constructors accept arguments of various types, handling them appropriately.
  2. Constructors accept a params array of type Object to enable creation of complex objects.
  3. If an argument implements IEnumerable, the objects within the sequence are added.

If you haven’t seen it in action, I encourage you to take a look at the examples on MSDN and elsewhere—it really is pretty slick. This post will show how a similar technique can be used to build control trees in ASP.NET web forms (and probably WinForms with minimal adjustment).

Basic functional construction can be implemented using two relatively simple extension methods:

public static void Add(this ControlCollection @this, object content)
{
    if (content is Control)
        @this.Add((Control)content);
    else if (content is IEnumerable)
        foreach (object c in (IEnumerable)content)
            @this.Add(c);
    else if (content != null)
        @this.Add(new LiteralControl(content.ToString()));
}

public static void Add(this ControlCollection @this, params object[] args)
{
    @this.Add((IEnumerable)args);
}

We handle four cases:

  1. Control? Add it.
  2. Sequence? Add each.
  3. Other value? Add literal.
  4. Null? Ignore.

And our params overload just calls its arguments a sequence and defers to the other.

In the time-honored tradition of contrived examples:

Controls.Add(
    new Label() { Text = "Nums:" },
    " ",
    from i in Enumerable.Range(1, 6)
    group i by i % 2
);

This would render “Nums: 135246”. Note that the result of that LINQ expression is a sequence of sequences, which is flattened automatically and converted into literals. For comparison, here’s an equivalent set of statements:

Controls.Add(new Label() { Text = "Nums:" });
Controls.Add(new LiteralControl(" "));
foreach (var g in from i in Enumerable.Range(1, 6)
                  group i by i % 2)
    foreach (var i in g)
        Controls.Add(new LiteralControl(i.ToString()));

Hopefully seeing them side by side makes it clear why this new method of construction might have merit. But we’re not done yet.

Expressions, Expressions, Expressions

Many language features introduced in C# 3.0 and Visual Basic 9 make expressions increasingly important. By expressions I mean a single “line” of code that returns a value. For example, an object initializer is a single expression…

var tb = new TextBox()
{
    ID = "textBox1",
    Text = "Text"
};

… that represents several statements …

var tb = new TextBox()
tb.ID = "textBox1";
tb.Text = "Text";

That single TextBox expression can then be used in a number of places that its statement equivalent can’t: in another object initializer, in a collection initializer, as a parameter to a method, in a .NET 3.5 expression tree, the list goes on. Unfortunately, many older APIs simply aren’t built to work in an expression-based world. In particular, initializing subcollections is a considerable pain. However, we can extend the API to handle this nicely:

public static T WithControls<T>(this T @this, params object[] content) where T : Control
{
    if(@this != null)
        @this.Controls.Add(content);
    return @this;
}

The key is the return value: Control in, Control out. We can now construct and populate a container control with a single expression. For example, we could build a dictionary list (remember those?) from our groups:

Controls.Add(
    new HtmlGenericControl("dl")
    .WithControls(
        from i in Enumerable.Range(1, 6)
        group i by i % 2 into g
        select new [] {
            new HtmlGenericControl("dt")
            { InnerText = g.Key == 0 ? "Even" : "Odd" },
            new HtmlGenericControl("dd")
            .WithControls(g)
        }
    )
);

Which would render this:

Odd
135
Even
246

Without the ability to add controls within an expression, this result would require nested loops with local variables to store references to the containers. The actual code produced by the compiler would be nearly identical, but I find the expressions much easier to work with. Similarly, we can easily populate tables. Let’s build a cell per number:

Controls.Add(
    new Table().WithControls(
        from i in Enumerable.Range(1, 6)
        group i by i % 2 into g
        select new TableRow().WithControls(
            new TableCell()
            { Text = g.Key == 0 ? "Even" : "Odd" },
            g.Select(n => new TableCell().WithControls(n))
        )
    )
);

In a future post I’ll look at some other extensions we can use to streamline the construction and initialization of control hierarchies.