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.

Simplifying LazyLinq