Ever since ASP.NET was released the markup it renders has been awful. Any web designer that's worked with ASP.NET will know this. Before the release of version 2.0 the best way to get close to valid markup was to render the page into a string in memory and then use regular expressions to remove/replace unwanted markup. That's not really a good enough solution so I've been trying to come up with another way of doing it.
My solution is to create a new HTML writer class deriving from XhtmlTextWriter. The only methods to override here are OnTagRender, OnAttributeRender and OnStyleAttributeRender because they indicate whether the specified elements and/or attributes can be rendered. These three methods are called when an element/attribute is about to be written to the output stream and determine what will be rendered.
using System;
using System.Data;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
namespace FuriousAngel
{
public class XhtmlTextWriter : System.Web.UI.XhtmlTextWriter
{
public XhtmlTextWriter(TextWriter writer)
: base(writer)
{
}
protected override bool OnStyleAttributeRender(string name, string value, HtmlTextWriterStyle key)
{
return false;
}
protected override bool OnTagRender(string name, HtmlTextWriterTag key)
{
return base.OnTagRender(name, key);
}
protected override bool OnAttributeRender(string name, string value, HtmlTextWriterAttribute key)
{
return base.OnAttributeRender(name, value, key);
}
}
}
As you can see OnStyleAttributeRender always false because I never want any inline styles rendered to the page. This makes for much cleaner markup and forces yourself (and/or other developers) to use a CSS class (CssClass property) if they want to style a WebControl. Right now the other two methods are just calling the base functionality provided by System.Web.UI.XhtmlTextWriter.
Next I've created two private static fields which are arrays of invalid elements and invalid attributes.
public class XhtmlTextWriter : System.Web.UI.XhtmlTextWriter
{
private static readonlyHtmlTextWriterAttribute[] _invalidAttributes = new HtmlTextWriterAttribute[] {
HtmlTextWriterAttribute.Align, HtmlTextWriterAttribute.AutoComplete, HtmlTextWriterAttribute.Background,
HtmlTextWriterAttribute.Bgcolor, HtmlTextWriterAttribute.Border, HtmlTextWriterAttribute.Bordercolor,
HtmlTextWriterAttribute.Cellpadding, HtmlTextWriterAttribute.Cellspacing, HtmlTextWriterAttribute.Height,
HtmlTextWriterAttribute.Nowrap,
HtmlTextWriterAttribute.Style, HtmlTextWriterAttribute.Valign, HtmlTextWriterAttribute.VCardName,
HtmlTextWriterAttribute.Width, HtmlTextWriterAttribute.Wrap
};
private static readonlyHtmlTextWriterTag[] _invalidTags = new HtmlTextWriterTag[] {
HtmlTextWriterTag.B, HtmlTextWriterTag.Basefont, HtmlTextWriterTag.Bgsound, HtmlTextWriterTag.Big, HtmlTextWriterTag.Font,
HtmlTextWriterTag.Frame, HtmlTextWriterTag.Frameset, HtmlTextWriterTag.I, HtmlTextWriterTag.Iframe,
HtmlTextWriterTag.Marquee, HtmlTextWriterTag.Menu, HtmlTextWriterTag.Nobr, HtmlTextWriterTag.Noframes,
HtmlTextWriterTag.S, HtmlTextWriterTag.Strike,
HtmlTextWriterTag.U
};
...
I can now use these arrays to determine what elements and attributes to render.
protected override bool OnTagRender(string name, HtmlTextWriterTag key)
{
return (Array.IndexOf(_invalidTags, key) == -1);
}
protected override bool OnAttributeRender(string name, string value, HtmlTextWriterAttribute key)
{
return (Array.IndexOf(_invalidAttributes, key) == -1);
}
The XhtmlTextWriter will now only render elements and attributes that are valid. It's that simple and now all I have to do is tell ASP.NET to use my writer instead of the default one. There are a couple of ways of doing this. The first is to create a PageAdapter class and override the Render method to create the new XhtmlTextWriter (note that this is the best way to go if you have already implemented a PageAdapter).
public override void Render(HtmlTextWriter writer)
{
FuriousAngel.XhtmlTextWriter xhtmlWriter = new FuriousAngel.XhtmlTextWriter(writer.InnerWriter);
base.Render(xhtmlWriter);
}
Then register the page adapter in a .browser file under the App_Browsers ASP.NET folder.
<browsers>
<browser refID="Default">
<controlAdapters>
<adapter controlType="System.Web.UI.Page" adapterType="MyPageAdapter" />
<controlAdapters>
</browser>
</browsers>
The second way to make ASP.NET use my XhtmTextWriter is to simply have a .browser file set the markupTextWriterType attribute of the controlAdapters element to FuriousAngel.XhtmlTextWriter.
<browsers>
<browser refID="Default">
<controlAdapters markupTextWriterType="FuriousAngel.XhtmlTextWriter">
<controlAdapters>
</browser>
</browsers>
Once this is in place I'm good to go and I'll have a much cleaner and more valid markup. ASP.NET 2 is XHTML compliant (although like I say it's markup is horrid) so I recommend using the XHTML Strict doctype and using this code just helps ensure the output is valid. However, the biggest benefit of using this code is that there are no more style attributes lying around your markup.
This is example code from a custom WebControl that is trying to render some invalid markup.
public override void Render(HtmlTextWriter writer)
{
writer.AddAttribute(HtmlTextWriterAttribute.Nowrap, "true"); // Attribute will not be rendered to the page
writer.RenderBeginTag(HtmlTextWriterTag.Div);
writer.RenderEndTag();
// This wont be rendered
writer.RenderBeginTag(HtmlTextWriterTag.Marquee);
writer.RenderEndTag();
}
The only output from this will be a div element if ASP.NET is using the XhtmlTextWriter. The code file is available for those who want it.