New information has been added to this article since publication.
Refer to the Editor's Update below.

The ASP Column

Determining Browser Capabilities in ASP.NET

George Shepherd

Contents

HTTP Headers and the User Agent
Determining Browser Capabilities
Mapping Headers to Browser Capabilities
Up-Level Versus Down-Level Browsers
Defining a New Browser
Testing
Creating Your Own Target
HtmlTextWriter and Html32TextWriter
Mobile Browser Capabilities
Conclusion

Web applications are different from applications that run in homogenous environments because they send their output to all kinds of platforms and Web browsers. Some browsers support client-side scripting, some support XHTML, and still others have limited screen real estate. So how will your Web app deliver content to browsers with limited capabilities or special requirements?

[ Editor's Update - 12/14/2004: The sidebar has been updated.]

HTTP Headers and the User Agent

The first step in determining the capabilities of the browser making a request to your Web site is to find out what kind of browser it is. Inside the payload sent in the HTTP Headers is the User Agent string, which describes the browser making the request. While you don't normally see the headers sent by your client browser to a remote Web site, you can view them using TCP tracing applications. I use TCPTrace (an application written by Simon Fell and Matt Humphrey) to examine headers. Figure 1 shows the header dump for one such Web request.

Figure 1 HTTP Request/Response Payloads in TCP Trace

Here's the text from the upper-right pane of the bitmap:

GET /BrowserCapabilitySite/WebForm1.aspx HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, 
  application/vnd.ms-excel, application/msword, application/x-shockwave-flash, */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; 
  .NET CLR 1.0.3705; .NET CLR 1.1.4322)
Host: localhost:8080
Connection: Keep-Alive
Cookie: ASP.NET_SessionId=cuqtb32q0i2slj45ygahvg45
Extension: Security/Remote-Passphrase

Here's the text from the lower-right pane:

HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Sat, 30 Oct 2004 17:20:54 GMT
X-Powered-By: ASP.NET
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Length: 1974
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
 <HEAD>
  <title>WebForm1</title>
  <meta content="Microsoft Visual Studio .NET 7.1" name="GENERATOR">
  <meta content="C#" name="CODE_LANGUAGE">
  <meta content="JavaScript" name="vs_defaultClientScript">
  <meta content="https://schemas.microsoft.com/intellisense/ie5" 
    name="vs_targetSchema">
 </HEAD>
 <body MS_POSITIONING="GridLayout">
  <form name="Form1" method="post" action="WebForm1.aspx" id="Form1">
<input type="hidden" name="__VIEWSTATE" 
  value="dDwxNjEyOTY2OTA2Ozs+LRBIlHeN0PLAvIp7Ptkm85kFdl4=" />
 •••

Notice the User Agent strings in Figure 2. In your ASP.NET application, you can easily fetch the string from the header collection programmatically. You can also look at the User Agent string when you turn tracing on in your application, as shown in Figure 3.

Figure 2 Common User Agent Strings

Browser User Agent String
Microsoft Internet Explorer 6.0 running on Windows 2000 with the .NET Framework 1.0 and 1.1 installed Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; CLR 1.1.4322)
Firefox 0.8 running on Windows 2000 Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.6) Gecko/20040206 Firefox/0.8
Netscape 7.2 running on Windows XP Mozilla/5.0 (Windows; U; Windows NT 5.1; en-us; rv:1.7.2) Gecko/20040804 Netscape/7.2
Opera 7.54 running on Windows XP Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) Opera 7.54

Figure 3 The HTTP_USER_AGENT String as It Appears

Figure 3** The HTTP_USER_AGENT String as It Appears **

That's great, but what do you do with that information? It's only useful if you know what capabilities are attached to each browser version. In both classic ASP and ASP.NET, the capabilities of various browsers are captured in secondary files.

Determining Browser Capabilities

In classic ASP the DLL %windir%\System32\inetsrv\browscap.dll includes a class that determines browser capabilities based on the incoming User Agent string. The browser capabilities object examines %windir%\System32\inetsrv\browscap.ini to match the User Agent string to a specific section in the .ini file, which describes the well-known capabilities of that browser. (ATL Server, which is a C++ template library for creating ISAPI DLLs, also uses browscap.ini to determine browser capabilities. For more information on ATL Server, take a look at The ASP Column in the November 2003 issue of MSDN®Magazine.)

Figure 4 shows a section from the browscap.ini file reporting the capabilities of a browser identifying itself as Microsoft® Internet Explorer 2.0. This section of the .ini file indicates that Internet Explorer does not support client-side scripting, nor does it support Java applets.

Figure 4 Browscap.ini Section

[IE 2.0]
browser=IE
version=2.0
majorver=2
minorver=0
frames=FALSE
tables=TRUE
cookies=TRUE
backgroundsounds=TRUE
vbscript=FALSE
javascript=FALSE
javaapplets=FALSE
beta=False
Win16=False

To use this information in a classic ASP application, you'd create the BrowserType object in a script block (it's a COM object with the ProgID of "MSWC.BrowserType") and query its properties for information such as browser name, version, and whether cookies are supported. With that information, you can deliver your content appropriately.

ASP.NET includes the HttpBrowserCapabilities class for interrogating the browser's capabilities. The HttpRequest class includes a reference to an instance of the HttpBrowserCapabilties class, so it's always available throughout your application. You can fetch it from the current Page instance or from the current instance of the HttpContext class. HttpBrowserCapabilities includes a number of properties and their significance, as you can see in Figure 5.

Figure 5 Browser Capabilities Represented

Property/Method Type Purpose
ActiveXControls Bool Support for ActiveX controls
AOL Bool America Online browser
BackgroundSounds Bool Support for background sounds
Beta Bool Beta version
Browser String Name of the browser
CDF Bool Support for Channel Definition Format (CDF) for webcasting
ClrVersion Version Version number of the latest .NET Framework installed on the client
Cookies Bool Support for cookies (though not whether cookies are disabled)
Crawler Bool Client is a search engine Web crawler
EcmaScriptVersion Version Version number of ECMA Script supported by the client
Frames Bool Support for frames
JavaApplets Bool Support for Java applets
JavaScript Bool Support for JavaScript blocks
MajorVersion Integer Major version of the browser
MinorVersion Integer Minor version of the browser
MSDomVersion Version Version of the Microsoft HTML (MSHTML) Document Object Model (DOM) is supported by the client
Platform String Operating system running the browser
Tables Bool Support for tables
Type String Browser name and major version number
VBScript Bool Support for VBScript
Version String Full version number of the browser
W3CdomVersion Version Version of W3C XML DOM supported by the browser
Win16 Bool Running on Win16?
Win32 Bool Running on Win32?

The significance of most of the browser capabilities is obvious, and you can use the information provided to tailor your output. For example, if you have a script, you should send it to the client only if the browser on the other end understands it, which I demonstrate in the code in Figure 6.

Figure 6 Script Test

protected override void OnInit(EventArgs ea)
{
  string strClientSideScript = 
  @"
  <script langage="javascript">
    function DoClick() 
    {
      document.all['" + button1.ClientID + @"'].value='Button1 clicked';
    }
  </script>
  ";

  if (HttpContext.Current.Request.Browser.JavaScript)
  {
    Page.RegisterClientScriptBlock("ClientCode", strClientSideScript);
  }
}

Mapping Headers to Browser Capabilities

In the September 2004 installment of this column, I looked at ASP.NET configuration and how ASP.NET has specific handlers for interpreting individual sections within a configuration file. Let's now take a look at parts of the Machine.config file, which is the starting point for configuration in ASP.NET. Near the top of the file you'll see a node providing the name of the component that interprets a User Agent string to determine a browser's capabilities:

<sectionGroup name="system.web">
  <section name="browserCaps"
    type="System.Web.Configuration.HttpCapabilitiesSectionHandler, 
      System.Web, Version=1.0.5000.0, Culture=neutral,
      PublicKeyToken=b03f5f7f11d50a3a"/>
  ...
</sectionGroup>

As you can see, the type named "HttpCapabilitiesSectionHandler" is used to parse the <browserCaps> section of the configuration file. After the browser configuration settings have been parsed, for each request it looks at the HTTP headers and uses them to determine the client browser capabilities. The <browserCaps> element within Machine.config contains mappings between HTTP headers and specific browser capabilities. This information is used to populate the HttpBrowserCapabilities class for each request. Note that the browser capabilities feature matches more than just the User Agent string. Any inbound header can be matched and utilized as part of the values set for that HttpBrowserCapabilities object. Figure 7 shows some code from the <browserCaps> section of Machine.config.

Figure 7 <browserCaps>

<system.web>
  <browserCaps>
    <result type="System.Web.Mobile.MobileCapabilities,
       System.Web.Mobile, Version=1.0.5000.0, Culture=neutral,
       PublicKeyToken=b03f5f7f11d50a3a" />
    
    <use var="HTTP_USER_AGENT"/>
    
      browser=Unknown
      version=0.0
      majorversion=0
      minorversion=0
      frames=false
      tables=false
      ...
    
  </browserCaps>
</system.web>

The <browserCaps> section defines a set of key-value pairs representing capabilities. Further down in the Machine.config file, you'll see mappings of these variables to specific regular expressions in various User Agent strings. For example, between the <browserCaps> tags, you'll see regular expressions indicating the client version. If the HttpBrowserCapabilitiesHandler matches the platform strings in the User Agent string, HttpBrowserCapabilitiesHandler sets the platform property appropriately:

<case match="Windows NT 5.1|Windows XP">
  platform=WinXP
</case>
<case match="Windows NT 5.0|Windows 2000">
  platform=Win2000
</case>

Up-Level Versus Down-Level Browsers

Browsers as defined in Machine.config can be categorized into two main types: up-level and down-level. Up-level browsers support HTML 4.0, cascading style sheets, ECMAScript version 1.2 (JScript® and JavaScript), and a W3C-compatible Document Object Model. Down-level browsers and client devices support only HTML 3.2. The distinction between browsers is useful for evaluating how the appearance of your Web site will change when accessed in the most common use cases.

Most modern browsers are mentioned within Machine.config. If a browser is not defined there, you can easily add a definition for one to the Machine.config or to your application's Web.config.

Defining a New Browser

[ Editor's Update - 12/14/2004: At any given node, only one child browser element can match, or the runtime will stop the tree traversal at that point. Multiple gateway matches are permitted.] This restriction eliminates some of the previous ambiguity found when working to update the browserCaps section. The regular expressions must provide unique matches so that a definitive path through the tree can be found. A request does not necessarily need to match all the way to a leaf node. The resulting HttpBrowserCapabilities object is the result of the default settings specified in the root node and the cumulative effect of all the additional properties and overridden settings that are applied as the tree is traversed.

A browser element can either create a new node in the tree by specifying a parent browser node, or it can modify the settings for an existing node by referencing it. If a browser element is not modifying an existing node, it will include an identification section to indicate what capabilities or header values must exist for a browser match to be true. In addition to identifying a browser, the node can also contain a set of elements that capture data from the headers of the request. This information can then be used in the capabilities section to set properties of the HttpBrowserCapabilities object.

Another change to the browser capabilities system is the support for specifying control adapters. ASP.NET 2.0 features a pluggable control adapter architecture that allows you to take over or participate in many of the lifecycle behaviors of the controls in a page. The need to adapt a control's behavior or output is typically a function of the browser that is being targeted, so it makes sense to include the controlAdapters element in the browser definition as well.

A final key feature of the new browser capabilities system is how it works to provide support for browser-specific page customization. In version 2.0 of ASP.NET, you can customize property values declaratively based on the requesting browser. Simply prefix a control attribute with the ID of a browser. The most specific match from the tree traversal will be used as the final value of the property. For example, a label can declaratively set different text to be used based on what browser has made the request, as shown here:

<asp:label runat="server" text="some browser" IE:text="any IE browser"
  IE5:text="the IE 5 browser"  PIE:text="the Pocket PC browser" />

This makes it easy to tailor applications for the unique capabilities of the various browsers.

The enhancements to the browser capabilities feature in ASP.NET 2.0 should make it easier for you to keep up with the changing features of new browsers as they emerge to customize applications for varying browsers, and even to modify control behaviors through the use of adapters.

Figure 8 Web.Config

<system.web>
  <browserCaps>
    <filter>
      <case match="LameBrowser">
        browser=Lame-o-browser
        version=0.0
        majorversion=0
        minorversion=0
        frames=false
        tables=false
        cookies=false
        backgroundsounds=true
        vbscript=false
        tagwriter=System.Web.UI.Html32TextWriter
        ... 
      </case>    
    </filter>
  </browserCaps>
</system.web>

This tells ASP.NET about a new kind of browser named the "LameBrowser". Given the definition in the <browserCaps> section, this is a down-level browser that supports background sounds. If the User Agent includes the string "LameBrowser", ASP.NET will fill the current HttpBrowserCapabilities object with the values mentioned in Web.config. Note, though, that the matching of the regular expressions is additive, so a subsequent match further down in the application hierarchy could override these settings.

Testing

If you have copies of your target browsers in your test environment, it's pretty easy to see how your Web site will respond. If not, ASP.NET supports an aliasing technique that lets you force your application into thinking a certain browser made a request.

In Machine.config, you'll find a <clientTarget> section that defines aliases for Internet Explorer 4.0 and 5.0, up-level browsers, and down-level browsers:

<clientTarget>
  <add alias="ie5" userAgent=
    "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 4.0)"/>
  <add alias="ie4" userAgent=
    "Mozilla/4.0 (compatible; MSIE 4.0; Windows NT 4.0)"/>
  <add alias="uplevel" userAgent=
    "Mozilla/4.0 (compatible; MSIE 4.0; Windows NT 4.0)"/>
  <add alias="downlevel" userAgent="Unknown"/>
</clientTarget>

As you can see, the <clientTarget> section maps short names to specific User Agent strings. If you set your page's ClientTarget property to any of the aliases listed, the ClientTargetSectionHandler class will intercept the alias and use the specific User Agent string to determine the browser's capabilities. Here's an example of setting ClientTarget within the page directive (you can also set this property in the Visual Studio designer):

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" 
    Inherits="BrowserCapabilitySite.WebForm1" clientTarget="uplevel"%>

This allows you to render your page render in different browsers.

Creating Your Own Target

While ASP.NET gives you four built-in target aliases, you probably could benefit from more. For example, imagine you wanted to test your site against the Firefox browser or the LameBrowser mentioned previously. The following bit of XML placed in your Web.config file will do the trick (provided you have <browserCaps> sections set up for each of them):

<system.web>
  <clientTarget>
    <add alias="firefox" userAgent="Mozilla/5.0 (Windows; U; 
      Windows NT 5.0; en-US; rv:1.6) Gecko/20040206 Firefox/0.8" />
    <add alias="lamebrowser" userAgent="LameBrowser"/>
 </clientTarget>
<system.web>

HtmlTextWriter and Html32TextWriter

In addition to the properties shown in Figure 5, HttpBrowserCapabilities exposes another read-only property named TagWriter. The TagWriter property exposes the Type of the class to use to render tags from a control. If you've ever written a custom server-side control, you've undoubtedly seen the HtmlTextWriter class passed into the Render method.

You might think calling output.Write from within a server-side control is the same as calling Response.Write, but it's actually more flexible than that. The HtmlTextWriter contains methods for rendering specific HTML elements.

While it's often good enough to contain other ASP.NET server-side controls when writing a custom control, you sometimes need to work with tags manually. The HtmlTextWriter class includes methods to support stack-based rendering of tags and attributes.

In addition to helping you keep beginning and ending tags straight, the HtmlTextWriter helps you emit the right version of HTML. When ASP.NET gets a request, it examines the TagWriter property of the HttpBrowserCapabilities class and uses either HtmlTextWriter or Html32TextWriter (Html32TextWriter derives from HtmlTextWriter). For example, tables render slightly differently in HTML 3.2 than they do in HTML 4.0. The code snippet in Figure 9 (showing the Render method overridden in a server-side control) renders a simple table with one row and one column. HtmlTextWriter includes methods to render the attributes and tags based on the HTML version understood by the browser.

Figure 9 Rendering a Control

protected override void Render(HtmlTextWriter output)
{
  output.WriteFullBeginTag("h2");
  output.Write("Using the HtmlTextWriterClass ");
  output.Write(output.ToString());
  output.WriteEndTag("h2");
  output.WriteLine();

  output.AddAttribute(HtmlTextWriterAttribute.Width, "30%");
  output.AddAttribute(HtmlTextWriterAttribute.Border, "2");
  output.RenderBeginTag(HtmlTextWriterTag.Table); 
  output.RenderBeginTag(HtmlTextWriterTag.Tr); 
  output.AddAttribute(HtmlTextWriterAttribute.Align, "Right");
  output.AddStyleAttribute(HtmlTextWriterStyle.FontSize, "large");
  output.AddStyleAttribute(HtmlTextWriterStyle.Color, "red");
  output.RenderBeginTag(HtmlTextWriterTag.Td); 
  output.Write("Hi there...");
  output.RenderEndTag(); 
  output.RenderEndTag(); 
  output.RenderEndTag(); 
}

Here's how the Html32TextWriter renders the table

<table width="30%" border="2">
  <tr>
    <td align="Right">
      <font color="red" size="5">
      Hi there...
      </font>
    </td>
  </tr>
</table>

while the HtmlTextWriter class renders the table like this:

<table width="30%" border="2">
  <tr>
    <td align="Right" style="font-size:large;color:red;">
    Hi there...
    </td>
  </tr>
</table>

Remember, Page derives from the Control class and you may override the Render method to access the HtmlTextWriter from a Page. The ASP.NET standard server-side controls are built to render based upon the capabilities of the browser making the request.

Mobile Browser Capabilities

So far, I've looked only at the basic set of browser capabilities. A whole new set of mobile browsers are supported as of ASP.NET 1.1. A tour through the Machine.config file of an ASP.NET 1.1 installation reveals settings for devices and systems such as Windows® CE, Nokia, Ericsson, and AvantGo. These are the browsers you'll find built into modern cell phones and PDAs.

ASP.NET determines these capabilities in exactly the same way it does for normal browsers. However, as you look through the various devices, you'll see that determining browser capabilities for these devices is a bit more involved because there are just a lot more capabilities to figure out.

Determining the mobile capabilities of a device is a mere type cast away from the normal device capabilities. That is, just cast the Request.Browser field to the MobileCapabilities class and you can examine the device capabilities (MobileCapabilities derives from HttpBrowserCapabilities). The code in Figure 10 determines the nature of the markup language of the browser/device (WML versus HTML), the size of the user's screen (in characters), and whether or not the device can deal with e-mail.

Figure 10 Determining Mobile Browser Capabilities

protected void ShowMobileCapabilities()
{
  MobileCapabilities mobileCapabilities =
    (MobileCapabilities)Request.Browser;

  if(mobileCapabilities.PreferredRenderingMime == "text/html")
  {
    Response.Write("This is an HTML device.");
  } 
  else if(mobileCapabilities.PreferredRenderingMime == 
    "text/vnd.wap.wml")
  {
    Response.Write("This is a WML device.");
  }
  Response.Write("Screen Width in characters: " + 
    mobileCapabilities.ScreenCharactersWidth);

  Response.Write("Screen Height in characters: " + 
    mobileCapabilities.ScreenCharactersHeight);

  Response.Write("Can send mail: " + mobileCapabilities.CanSendMail);
}

When you look at Machine.config you'll see more than 75 mobile device capabilities (in addition to the standard browser capabilities). The mobile capabilities of a device are determined by the MobileDeviceCapabilitiesSectionHandler configuration handler.

Conclusion

ASP.NET provides a number of features for determining the capabilities of the client making any given request. This provides you with a means of controlling your site's output by aliasing client targets to specific User Agent strings within your application.

Send your questions and comments to  asp-net@microsoft.com.

George Shepherd specializes in software development for the .NET Framework. He is the author of Programming with Microsoft Visual C++.NET (Microsoft Press, 2002), and coauthor of Applied .NET (Addison-Wesley, 2001). He is a contributing architect for Syncfusion's Windows Forms and ASP.NET tools.