A Look at ASP.NET's Adaptive Rendering
By Scott Mitchell
Introduction
ASP.NET Web pages, as you know, are made up of an HTML portion and a source code portion. The HTML portion contains
a mix of HTML markup along with Web controls. When the ASP.NET Web page is visited, the Web controls are rendered into
HTML markup. The static HTML markup, along with the rendered Web controls markup, is sent back to the visitor that
requested the page, and displayed in their browser.
The HTML rendered by Web controls, though, depends on the browser requesting the ASP.NET Web page. For this reason,
ASP.NET Web controls are called adaptive. For example, if a visitor requests a page using Internet Explorer 6.0,
they'll receive HTML 4.0-compliant markup; for example, a Label Web control whose ForeColor property were
set to Red would render the following markup:
If, however, the same visitor were to use Netscape 4.72, they'd receive
HTML 3.2-compliant markup. The HTML for the Label Web control would specify the color using a <font>
element rather than a style attribute in the <span>.
While adaptive controls are a nice concept in general, ASP.NET's default implementation can be a bit frustrating since for
modern browsers - such as recent versions of Mozilla, Firefox, Netscape, and Opera - ASP.NET controls render, by default, HTML 3.2-compliant
HTML rather than HTML 4.0-compliant HTML. Fortunately, you can configure your Web application (or the entire Web server)
to render HTML 4.0-compliant HTML for these modern, non-Microsoft browsers. In this article we'll see how ASP.NET decides
whether to render the Web control HTML as 4.0-compliant or 3.2-compliant. We'll also look at how to configure your Web
application so that modern versions of Netscape, Opera, and Mozilla cause ASP.NET to render 4.0-compliant markup.
How Web Controls are Rendered
Each and every ASP.NET Web page is derived either directly or indirectly from the System.Web.UI.Page class.
This class provides the base methods and properties that represent an ASP.NET Web page. Whenever a request is received
by the Web server for an ASP.NET Web page, Web server hands off the request to the ASP.NET engine, which creates an
instance of the requested page's Page class and calls this class's ProcessRequest() method.
The call to the ProcessRequest() method kicks off the page's life cycle, which involves loading the page's
control hierarchy, restoring the view state, firing the Load event, firing Web control events, saving the
view state, and rendering the page. The diagram below graphically illustrates the ASP.NET page's life cycle. (Click
here for a larger view...)
The ASP.NET Web page is rendered in the following manner: the Page class creates an instance of a
System.Web.UI.HtmlTextWriter class, and then recursively calls the RenderControl() method
of all of the controls in its control hierarchy, passing in the HtmlTextWriter instance. Each control,
then, appends its HTML markup to the end of the same HtmlTextWriter instance. After all controls have
been rendered, the Page class can return the entire page's HTML markup by returning the content in that
HtmlTextWriter instance.
Adaptive Rendering with HtmlTextWriter
The System.Web.UI namespace contains two classes for rendering Web controls: HtmlTextWriter, which
renders HTML 4.0-compliant markup, and Html32TextWriter, which renders HTML 3.2-compliant markup. (The
Html32TextWriter class is derived from the HtmlTextWriter class.)
When the Page class enters its render stage, it starts by creating a new HtmlTextWriter
instance. It determines what class instance to create - HtmlTextWriter or Html32TextWriter -
by inspecting the TagWriter property of the Browser object. The Browser object
contains read-only properties that provides information about the browser that was used to request the ASP.NET Web page.
A Web browser is identified by its User-Agent string.
Whenever a Web browser requests a page, it sends along
a User-Agent string in the HTTP headers that is used to identify the browser. Internet Explorer, for example, sends
the User-Agent string Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.1.4322).
Mozilla FireFox sends the User-Agent string Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.6) Gecko/20040206 Firefox/0.8.
Some browsers don't send a User-Agent string, or send a "fake" User-Agent string. For example, with Opera you can instruct it
to send the IE6 User-Agent string. There's an extension
for Mozilla that offers the same functionality as well. (Interested in seeing what User-Agent string your
browser is sending? Check out the
User-Agent live demo.)
The ASP.NET engine examines the User-Agent string to determine the values for the Browser's properties.
One of the properties is TagWriter, which, by default, is set to HtmlTextWriter for Internet
Explorer 4.0 and up. For all other browsers, it is set to Html32TextWriter. Fortunately, these settings
are configurable, so you can indicate that modern versions of Netscape, Mozilla, and Opera use the HtmlTextWriter
class as opposed to Html32TextWriter. These settings can be made through the <browserCaps>
section in either the maching.config or Web.config files. (Altering the settings in
maching.config will set the default behavior for all Web applications on the Web server. To control
the settings on an application-by-application basis, use the Web.config file.)
Examining the <browserCaps> Element
The <browserCaps> element contains a number of <case> elements. Each of these
<case> elements has a match attribute, which contains a regular
expression. If the regular expression matches with the User-Agent string sent by the browser, the properties inside
the <case> element are applied to the Browser object. If you look in the machine.config
you'll see that, by default, the TagWriter property is set to the Html32TextWriter class:
Further down, you'll find a <case> element that matches Internet Explorer's User-Agent. Here,
you'll find that the TagWriter property is set to the HtmlTextWriter class:
For non-Microsoft browsers, the TagWriter property remains the default - Html32TextWriter.
Configuring HTML 4.0-Compliant Rendering for Netscape, Mozilla, and Opera
Before we examine precisely how to configure the maching.config or Web.config files so that
modern, non-Microsoft browsers render HTML 4.0-compliant markup, let's first discuss the motivation behind this.
You may be wondering why go to this trouble. After all, while ASP.NET may render a Label with a red ForeColor
as <span id="controlID" style="color: red">Label Text</span>, and render the
same Label as <span id="controlID"><font color="red">Label Text</font></span>
for Netscape users, in the end, they see the same thing, so what's the big deal?
Well, for the ForeColor, yes, this is the case, but if you set the BackColor property on a
Label the HtmlTextWriter class uses the background-color CSS setting to specify a background color.
Since there is no way to set a background color for an HTML element without CSS (short of using a <table> or some
other trickery), the Html32TextWriter class simply ignores the BackColor property. In addition
to this reason, another motivation behind 4.0-compliant markup is that the HtmlTextWriter class can render
<div> elements; the Html32TextWriter class renders <div> as
<table>s. This can cause some oddities
for certain scenarios.
The following shows the <browserCaps> section you can add to your Web.config file
to specify that modern, non-Microsoft browsers cause the Web controls to render HTML 4.0-compliant markup. Notice that
in addition to setting the TagWriter property, the <browserCaps> section below also
sets a number of other properties for the Browser object. (This
<browserCaps> section was created by Rob
Eberhardt, and is available here.)
<!-- 2003-12-03, Rob Eberhardt - http://slingfive.com/demos/browserCaps/ -->
Moving Forward - Client-Side Validation
While configuring ASP.NET to render HTML 4.0-compliant markup for users surfing with modern versions of Netscape,
Opera, and Mozilla is a good first step, you might be disappointed to find out that even with this change, the
validation controls still won't render client-side validation script. There are a couple of (annoying) reasons for this,
but I'm going to save this discussion for a future article!
Until then, Happy Programming!
An Article on Client-Side Validation
I have authored an article discussing why client-side validation doesn't work in downlevel browsers, along with a discussion
on how to implement client-side validation. Learn more at Client-Side Validation in Downlevel Browsers