Using a Decorator to Hook Into A WebControl's Rendering for Better XHTML Compliance

0 comments suggest edit

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.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

7 responses

  1. Avatar for Ghassan
    Ghassan January 18th, 2006

    This is really a smart idea, and it will help me in something I am doing right now

    That was very cool, and actually extremely cool



  2. Avatar for Haacked
    Haacked January 18th, 2006

    I appreciate the kind words, Ghassan. And I read your post. I like how you compare this code to a Hot woman. ;)

  3. Avatar for Milan Negovan
    Milan Negovan January 18th, 2006

    *Slapping myself on the head* Nice and easy! Thanks for the tip.

  4. Avatar for klevo
    klevo January 18th, 2006

    That`s a very useful article. But I think that this problem can be solved by using xhtmlConformance mode="Strict" in web.config. Then you can use valid Transitional doctype as well as Strict (which should be prefered if possible).

  5. Avatar for Haacked
    Haacked January 19th, 2006

    You can use that if you are lucky to be working on an ASP.NET 2.0 project.



    I, unfortunately, am still working on several ASP.NET 1.1 projects. That element is not available for ASP.NET 1.1.

  6. Avatar for Simone Chiaretta
    Simone Chiaretta January 19th, 2006

    I'll try and use this tip to remove the ugly align="Center" from all cells inside the asp:calendar control... that's the only thing that makes the Piyo skin not XHTML compliant

  7. Avatar for James Chen
    James Chen August 12th, 2006

    I've copied your code and pasted on my code behind of a user control. And then I ran the debug mode and found: Render() was called and CompliantHtmlTextWriter() was instantiated, but AddAttribute() was never called.
    Eventually I still got: <input language="javascript"... on my site http://www.realtorstown.com/