How to modify the enclosing tag of a web control

30 Jul 2008

In this post I’ll rework a piece of code that changes the enclosing tag of a web control from a span to a div. A substitution of particular value with SharePoint, where adding web controls is a common way to extend the out of box experience. With SharePoint, and Asp.Net in general, a page is typically composed of reusable controls, each responsible for rendering its part of the page.

To showcase anti-patterns of API usage, intermediate code is included, i.e., code that may render the correct tags, although not necessarily in the true spirit of the .Net framework.

To set the stage, imagine writing a web control that inherits from ASP.Net WebControl. The goal is then to modify the behavior of the control in such a way that it (1) encloses its content in a div tag and (2) adds a CSS class for styling, and (3) can be added to a page like so:

<MyControls:MyControl CssClass="myCssClass" runat="server" />

After making its way through the page rendering pipeline, the control tag gets transformed to:

<div class="myCssClass">
    Hello World
</div>

Within the control, a straight-forward way of achieving the intended output may be to hand craft the html and render it in RenderContents of WebControl. Rendering html by hand is but only a starting point when working with .NET. The next progression from there may be to override RenderBeginTag and RenderEndTag of WebControl:

// version 1
public class MyControl : WebControl {
    public override void RenderBeginTag(HtmlTextWriter w) {
        w.RenderBeginTag(string.Format("div class=\"{0}\"", CssClass));
    }
    
    public override void RenderEndTag(HtmlTextWriter w) {
        w.RenderEndTag();
    }

    protected override void RenderContents(HtmlTextWriter w) {
        w.Write("Hello World");
    }
}

Resulting in this piece of html:

<div class="myCssClass">
    Hello World
</div class="myCssClass">

Clearly, RenderBeginTag and RenderEndTag of HtmlTextWriter don’t know how to separate tag from attribute. Nonetheless, the code reveals the stack based approach taken by the Render-pair of methods.

Gleaning over the documentation of HtmlTextWriter, however, I encountered the AddAttribute method for setting attributes on a tag. Inside HtmlTextWriter, what AddAttribute does is add attributes and associated values to an array that gets rendered during the next call to RenderBeginTag. So besides introducing strongly typed attributes and tags, the next version contains a modified RenderBeginTag:

// version 2
public override void RenderBeginTag(HtmlTextWriter w) {
    w.AddAttribute(HtmlTextWriterAttribute.Class, CssClass);
    w.RenderBeginTag(HtmlTextWriterTag.Div);
}

Now the control renders as expected, although the code still has a certain bad smell to it. WebControl ought to know how to properly render its begin and end tag. After all it’s WebControl that exposes, among others, the CssClass property. So taking another dive into the documentation, I found the virtual, read-only TagKey property of WebControl. Strangely named as it may be, overriding TagKey makes WebControl call our override as it renders the control, adding in optional attributes, such as CssClass. Implementing the property also turns RenderBeginTag and RenderEndTag obsolete:

// version 3
protected override HtmlTextWriterTag TagKey {
    get {
        return HtmlTextWriterTag.Div;
    }
}

As a bonus, changing the enclosing tag can also be done calling the WebControl’s constructor, passing in the desired tag:

// version  4
public class MyControl : WebControl {
    public MyControl() : base(HtmlTextWriterTag.Div) { }

    protected override void RenderContents(HtmlTextWriter w) {
        w.Write("Hello World");
    }
}

As it turns out, calling the constructor is how WebControl derived classes get to use span in the first place. The default WebControl constructor passes control onto another constructor that sets the value of what gets returned by the base class TagKey implementation. In essence, overriding TagKey makes it a player in the template design pattern:

// .Net framework code
protected WebControl() : this(HtmlTextWriterTag.Span) { }

public WebControl(HtmlTextWriterTag tag) {
    tagKey = tag;
}

Also worth noting is that for controls deriving from WebControl derived classes, such as CompositeControl, the constructor cannot be used to set the enclosing tag. The constructor exposed by CompositeControl and friends doesn’t call up the constructor chain passing in the tag, making TagKey the choice to go.

Finally, deep down the inhertitance chain below WebControl lives WebPart, a type of control popular with SharePoint. Interestingly enough, WebPart defaults to using div as its enclosing tag, because walking up the chain, we come across the constructor of Panel:

// .Net framework code
public Panel() : base(HtmlTextWriterTag.Div) { }

So, using Panel provides another way to implement a web control that owns a distinct part of a page.