Using a Decorator to Hook Into A WebControl's Rendering for Better XHTML Compliance
Man! What a mouthful of a title, but I think it succinctly describes
what this post is about. I will demonstrate how to hook into the
rendering of a control that inherits from
System.Web.UI.WebControls.WebControl using a
Decorator. In
particular, I am going to hook into the rendering of a Button control
to stop it from emitting the language="javascript" attribute.
Why?
Because I am a bit anal about XHTML compliance. The Button control
renders an input tag with the language attribute. But according to the
XHTML 1.0 transitional spec, this is an invalid attribute.
More than just being anal, I also thought it would serve as a nice demonstration of this technique in case you want to build custom controls that modify the rendering of other controls just slightly without having to rewrite a lot of code.
First Naive Attempt
My first attempt to handle this was to simply try and remove the
language attribute via the following code placed in the OnPreRender
method of my page:
btnSubmit.Attributes.Remove("language");
That didn’t work because the button control doesn’t explicitly add the
language attribute to the attributes collection. Instead, the attribute
is added within the Render method which is called by the page when it
is time for a control to render its contents to HTML.
Examining The Rendering Process
The Render method is passed an instance of HtmlTextWriter used to
render the page. One of the methods on this class is AddAttribute
which has several overrides. Using
Reflector I found that the method
that adds the language attribute has the signature
AddAttribute(string name, string value);.
The Decorator
Now if only I had some way to override that method to discard attributes with the name “language”. That’s where the decorator pattern comes in.
The class I want to decorate is the HtmlTextWriter. Fortunately the
authors of this class did a good job of making it extensible and easy to
decorate. HtmlTextWriter has a constructor that takes in an instance
of TextWriter. Methods on the HtmlTextWriter use the specified
TextWriter to write to the underlying stream. The good news is that
HtmlTextWriter inherits from TextWriter. So if I want to hook into
the rendering process, I just need to implement my own HtmlTextWriter
and override the specific methods I need.
The CompliantButton class
The first step is to create a CompliantButton class that inherits from
Button. Within that class I created a private internal class named
CompliantHtmlTextWriter like so:
private class CompliantHtmlTextWriter : HtmlTextWriter
{
internal CompliantHtmlTextWriter(HtmlTextWriter writer) : base(writer)
{
}
/// <summary>
/// Ignores the language attribute for the purposes of a submit button.
/// </summary>
public override void AddAttribute(string name, string value, bool fEndode)
{
if(String.Compare(name, "language", true, CultureInfo.InvariantCulture) == 0)
return;
base.AddAttribute (name, value, fEndode);
}
}
This is the decorator. Notice that the constructor takes in another
HtmlTextWriter which it will forward method calls to. The
AddAttribute method simply forwards calls to the base class unless the
attribute name is “language”.
Redecorating
Now all that is left is to use the decorator within the render method of
the CompliantButton class. Here is the render method:
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
base.Render(new CompliantHtmlTextWriter(writer));
}
Notice that I am wrapping (decorating) the HtmlTextWriter parameter
with my CompliantHtmlTextWriter decorator before passing it along to
the base Render method. As far as the base Render method is
concerned, it is dealing with an HtmlTextWriter. It doesn’t need to
know any specifics about the decorator class. But via decoration, the
behavior has been slightly modified. No more language attribute.
Comments
7 responses