Removing inline CSS styles from ASP.Net controls
One thing that always stuck in my craw about ASP.Net 1.0 was the XHTML markup that its server controls emitted. I am admittedly punctilious with regards to my pages' markup, and the XHTML that was being produced in ASP.Net 1.0 was verbose, clumsy and non-semantic. Worse, my browser of choice was considered a 'down-level' browser, and the framework would feed it all sorts of slapdash code.
With ASP.Net 2.0, we were given better tools to take control of how our controls are rendered. By default, our controls may emit wonky markup, but by using control adapters, we are empowered to render the controls any way we wish.
One thing I always take care to do when building a website is to ensure that inline CSS styles are always excised before it is pushed to production. Inline styles have ultimate specificity, and will overrule any styles defined in an embedded or external stylesheet. By using them, you lose the benefits of having all your presentation information in a single, central location.
Some ASP.Net server controls emit inline styles. This always bugged me, and with the help of Russ Helfand, who lurks on some of the same message boards I do, there is now a fairly elegant solution to clobbering all those inline styles. (For those not in the know, Helfand developed the CSS Friendly Control Adapters, which I would recommend for every site. Once installed, many of those hairy server controls will start emitting clean, semantic XHTML.)
Now, let's say we place an ASP.Net image control on a page:
<asp:Image runat="server" AlternateText="My Alt Text" ImageUrl="~/myImage.gif" />
When rendered, the control emits the following:
<img src="/myImage.gif" alt="My Alt Text" style="border-width:0px;" />
By default, an inline style is inserted declaring the image's border width to be 0. Now imagine if we had the following rule in our CSS:
img { border:1px dashed #000; }
This style will not be expressed because it is overruled by the inline style in the markup. Not cool.
What we're going to do is introduce a control adapter that will be applied to any control that inherits from the WebControl class (all the ASP.Net server controls,
in other words). The adapter will intercept the rendering of the control and tweak it a smidge to make it more palatable. Place the following in App_Code/Adapters:
1: using System;
2: using System.IO;
3: using System.Text.RegularExpressions;
4: using System.Web;
5: using System.Web.UI;
6:
7: namespace CustomAdapters
8: {
9: public class UmbrellaAdapter : System.Web.UI.WebControls.Adapters.WebControlAdapter
10: {
11: protected override void RenderBeginTag(HtmlTextWriter writer)
12: {
13: StringWriter sw = new StringWriter();
14: HtmlTextWriter hw = new HtmlTextWriter(sw);
15: base.RenderBeginTag(hw);
16: hw.Flush();
17: hw.Close();
18: string origTag = sw.ToString();
19: string newTag = Regex.Replace(origTag, "\\s*style=[\"'][^\"']*[\"']",
"", RegexOptions.Multiline | RegexOptions.IgnoreCase);
20: writer.Write(newTag);
21: }
22:
23: protected override void RenderEndTag(HtmlTextWriter writer)
24: {
25: }
26:
27: protected override void RenderContents(HtmlTextWriter writer)
28: {
29: }
30: }
31: }
I am not going to delve too deeply into the adapter's voodoo here (for a more thorough treatment, please see Microsoft's Architectural Overview of Adaptive Control Behavior). Just be aware that this adapter gets a copy of the default rendering markup for
a control and applies a regular expression to strip out any inline style.
Now that we have an adapter, we need to apply it to our server controls. In Visual
Studio, Click File -> New File, and create a new Browser File. Name it Umbrella.browser and save it in the /App_Browsers folder. We're going to use this browser file to register the adapter with any object that inherits from the WebControl object:
1: <browsers>
2: <browser refID="Default">
3: <controlAdapters>
4: <adapter controlType="System.Web.UI.WebControls.WebControl"
adapterType="CustomAdapters.UmbrellaAdapter" />
5: </controlAdapters>
6: </browser>
7: </browsers>
That's all there is to it. Now all server side controls will be run through our adapter and have their inline styles removed.
You may be asking what happens if you create another adapter and register it to a specific server control. Well, if we create an adapter for the image server control, we would register it like so:
1: <browsers>
2: <browser refID="Default">
3: <controlAdapters>
4: <adapter controlType="System.Web.UI.WebControls.WebControl"
adapterType="CustomAdapters.UmbrellaAdapter" />
5: <adapter controlType="System.Web.UI.WebControls.Image"
adapterType="CustomAdapters.ImageAdapter" />
6: </controlAdapters>
7: </browser>
8: </browsers>
Because the Image control is more specific in the inheritance chain, the WebControl's UmbrellaAdapter is ignored in favour of the ImageAdapter. A control will only be intercepted by an adapter once, so you can create adapters for more specific controls without worrying about their rendering logic being compromised by the more general UmbrellaAdapter.
Sound off