Authoring ASP.NET
Server Control Adapters - An Introduction
Vandana Datye
October 2003
Applies to:
Microsoft ASP.NET
Microsoft .NET
Microsoft Visual Studio .NET
Summary: Learn about the new adaptive rendering
functionality of ASP.NET server controls in the next version of the .NET
Framework, code-named 'Whidbey.' Learn how server controls use
pluggable adapter classes to render markup to many different browsers for
desktop and mobile devices. This article also demonstrates the main steps in implementing
a control adapter and shows how to use the tag rendering capabilities of markup
writer classes such as XhtmlTextWriter and WmlTextWriter. (42
printed pages)
Download CreatingAdaptersSample.msi.
Contents
Introduction
Architectural Overview
of Adaptive Rendering
Adapter Hierarchy
Walkthrough:
Implementing an Adapter and Mapping It to a Server Control
Adapter Participation in
the Control Life Cycle
Adaptive Rendering and
Markup Writer Classes
Browser Definition File
Syntax
Browser Definition Tree
and Browser Capabilities Object
Introduction
In the Microsoft pre-release version of the .NET
Framework code-named 'Whidbey,' Microsoft ASP.NET provides a unified
control architecture that enables a single set of server controls to work with
many different browsers for desktop and mobile devices. This functionality is
made possible by pluggable adapter classes, which render markup based on the
capabilities of the browser that accesses a page. For example, the ASP.NET Calendar
control has an accompanying CalendarAdapter class that adapts the
rendering of the Calendar control for the small screen size and limited
memory of a cell phone or a personal digital assistant (PDA).
In the .NET Framework versions 1.0 and 1.1, ASP.NET
mobile Web Forms pages were created using mobile controls. These controls
implemented rendering for mobile-device browsers and were distinct from the set
of standard ASP.NET server controls. In the new ASP.NET adaptive rendering
architecture, a separate set of mobile controls does not exist. ASP.NET server
controls encapsulate core user interface logic and provide default rendering,
which is HTML for desktop browsers. Rendering for other browsers is provided by
separate adapter classes, which are not controls. Adapters bind to controls at
run time and adapt the output of controls to the requesting browser. Adapters
call markup writer classes for creating WML, XHTML, cHTML, and other kinds of
markup. Mappings between controls and adapters are specified in configuration
files.
An important feature of adapters is that they operate
behind the scenes without any work on the part of the page developer. A page
developer can use a control without any knowledge of adapters, assuming that
the control itself adapts its rendering for different browsers. The ASP.NET
page framework enables page developers to use filters to customize control
properties for different browsers and devices. For example, in the tag <asp:Label
Text='Enter your name' Nokia:Text='Name'
runat='server' />, the prefix 'Nokia' is a filter (identifier) applied by
the page developer to the Text property of the Label control.
When a Nokia cell phone browser requests a page, ASP.NET uses the
'Nokia' filter to assign the string 'Name' to the label's Text
property. Property filtering is built into ASP.NET and does not require any
work on the part of the control or adapter developer. A page developer can use
property filtering with the standard ASP.NET controls as well as with custom
controls. This article describes how that filtering works. The documentation
for Microsoft Visual Studio 'Whidbey' shows how to use property
filtering in a page.
To enable your custom controls to render to different
browsers, in general you will have to implement adapters for them. Commonly,
server controls need adapters when the requesting browser does not interpret
HTML or when the requesting device requires a layout different from that
created by the control. It is likely that most adapters you implement will be
for rendering to mobile-device browsers. However, it is not only for mobile
browsers that adapters are needed. If browsers that accept non-HTML markup
(such as VoiceXML, for example) become available for desktop computers, your
controls might need adapters for those browsers too.
This article describes the adaptive rendering
architecture of ASP.NET and provides an end-to-end walkthrough that shows how
to implement a control adapter and map it to a control. It demonstrates how to
use the built-in markup writer classes, such as WmlTextWriter and XhtmlTextWriter.
In addition, it describes how to create configuration files that contain
control/adapter mappings and browser and device capabilities.
Architectural Overview of Adaptive
Rendering
Before examining the adaptive rendering architecture,
let's look at the effect of adaptive rendering in a simple page. The following
page (MyPage.aspx) uses the ASP.NET Login control.
<%@ page language='C#'%>
<html>
<head
runat='server'>
<title>Simple
Page</title>
</head>
<body>
<form
runat='server'>
<asp:Login
id='login1' runat='server'
backcolor='#ffcc66'
bordercolor='#333366' />
</form>
</body>
</html>
Figure 1 shows the page as viewed in Microsoft Internet
Explorer 6.0.
Figure 1. MyPage.aspx as viewed in Internet Explorer 6.0
Figure 2 shows the page as viewed in an Openwave 5.1
device emulator. The WmlLoginAdapter class, which the ASP.NET page
framework associates with the Login control, adapts the appearance and
behavior of the Login control for an Openwave 5.1 browser.
Figure 2. MyPage.aspx as viewed in an Openwave 5.1 device
emulator. Image courtesy Openwave Systems Inc.
When Internet Explorer requests the page, the Login
control renders HTML markup within (and including) the <table> tags shown in the
following excerpt.
<html>
<table
bordercolor='#333366' border='0' id='login1'
style='background-
color:#FFCC66;border-color:#333366;'>
<tr>
<td align='center'
colspan='2'>Log In</td>
</tr><tr>
<td
align='right'>User Name:</td>
<td><input
name='login1$UserName' type='text'
id='login1$UserName'
/></td></tr>
</table>
</html>
When an Openwave 5.1 browser requests the page, the WmlLoginAdapter
adapter renders WML markup within (and including) the <p> tags shown in the
following excerpt.
<wml>
<p>
Log In<br/>
User Name:<input
name='mcsv0' />
<br/>
Password:<input
name='mcsvepckf1' type='password' />
</p>
</wml>
Adaptive Rendering Architecture
To understand the ASP.NET adaptive rendering
architecture, it is useful to walk through the processing stages of a page. I
will examine how the ASP.NET page framework processes requests made to the MyPage.aspx
page by two different browsers-Openwave 5.1 and Internet Explorer 6.0.
Processing a request from an Openwave
WML browser
Figure 3 illustrates the ASP.NET adaptive rendering
architecture and shows how a page processes a request from an Openwave 5.1 browser.
While the figure shows processing for a specific page, the steps performed are
general and occur when the ASP.NET page framework responds to any page request.
When a block shows two classes, such as PageAdapter (WmlMyPageAdapter),
the first class is the base type and the second is the derived type
instantiated during the specific request. The elements primarily involved in
adaptive rendering are shown in color.
Figure 3. Processing a request from an Openwave 5.1
browser
The numbers in the figure represent the following steps:
1.
An Openwave 5.1 browser makes a request to
the MyPage.aspx page. If the page is not already compiled, the page framework
dynamically parses the page into the MyPage_aspx class and compiles the class.
2.
The ASP.NET page framework instantiates the
MyPage_aspx page. The page in turn creates its control tree and creates Login
and other controls.
3.
When the page accesses its Adapter
property it calls Request.Browser under the hood. This call causes
ASP.NET to make a request to the ASP.NET configuration system for a browser
capabilities (System.Web.HttpBrowserCapabilities) object corresponding
to an Openwave 5.1 browser. A browser capabilities object is an in-memory
representation of browser data stored in .browser configuration files. (At the
end of this section you will see how browser capabilities objects are created
and cached.)
4.
The configuration system returns a browser
capabilities object that represents browser data for an Openwave 5.1 browser.
This object contains data about the browser and device and can be accessed by
the page and its controls. The browser capabilities object also contains mappings
between control and adapter types, which tell ASP.NET which adapter (if any) to
instantiate for each control when the request is from an Openwave 5.1 browser.
5.
ASP.NET creates the WmlPageAdapter for
the page and the WmlLoginAdapter for the Login control, using the
adapter mappings in the browser capabilities object.
6.
The page, its adapter, the controls, and
their adapters execute life-cycle phases up to Render, such as Init, Load, and
PreRender. You will see how an adapter participates in the control's life cycle
in the Adapter Participation in
the Control Life Cycle section of this article.
7.
In the Render phase, the page creates an
instance of the WmlTextWriter class, using the type of the System.Web.UI.MarkupTextWriter
specified in the browser capabilities object for an Openwave 5.1 browser. WmlTextWriter
derives from the System.Web.UI.MarkupTextWriter class and is capable
of generating WML markup. The page passes the WmlTextWriter object to
its adapter, WmlPageAdapter, and invokes the adapter's rendering
methods. Similarly, the Login control passes the WmlTextWriter object
to its adapter, WmlLoginAdapter, and invokes its rendering methods. You
will see markup writers in more detail in the Adaptive Rendering and
Markup Writer Classes section later in this article.
8.
The WmlTextWriter instance writes WML
markup to the response stream.
Processing a request from Internet
Explorer
Figure 4 shows the processing of MyPage.aspx when it is
requested by Internet Explorer 6.0. Compare this figure with Figure 3 to see
the difference in request processing for Openwave 5.1 and Internet Explorer 6.0
browsers.
Figure 4. Processing a request from Internet Explorer 6.0
Steps 4, 5, and 7 show that different objects are instantiated
when the request comes from Internet Explorer 6.0 rather than from the
mobile-device browser shown in Figure 3. In step 4, The HttpBrowserCapabilities
object corresponds to the browser definition for Internet Explorer 6.0,
specified in the file Ie.browser. The default rendering of Login is
adequate for Internet Explorer, and-based on the mapping in the browser
capabilities object-the ASP.NET page framework does not create an adapter for
the Login control in step 5. In step 7, the page creates an HtmlTextWriter
instance using the markup text writer type specified in the HttpBrowserCapabilities
object for Internet Explorer 6.0. The page and its controls render HTML,
invoking the HtmlTextWriter instance. The .NET Framework creates the HtmlPageAdapter
as the page adapter in step 5, but this adapter does not play a significant
part in rendering and might not be mapped to the page in a future version.
Creation, caching, and retrieval of
browser capabilities objects
Figure 5 shows how the configuration system works behind
the scenes to create, cache, and retrieve browser capability objects.
Figure 5. Creation,
caching, and retrieval of browser capabilities objects
Browser data is stored in XML configuration files named
with a .browser file name extension. If you look in the ConfigBrowsers
directory of your .NET Framework installation, you will see files such
Ie.browser, Nokia.browser, and Openwave.browser.
ASP.NET compiles all the .browser files into a browser
capabilities factory object, as shown in the figure. The factory object can
create HttpBrowserCapabilities objects that correspond to browser
definitions in the .browser files. An HttpBrowserCapabilities object is
an in-memory representation of browser data for a specific browser, such as
Openwave 5.1 or Internet Explorer 6.0. The first time the configuration system
receives request headers from a browser, the configuration system invokes its
factory object to create an HttpBrowserCapabilities object for that
browser. The configuration system then caches the HttpBrowserCapabilities
object using some of the request headers as the cache key. For example, the
Openwave 5.1 HttpBrowserCapabilities object is cached with a subset of
the Openwave 5.1 headers. For a subsequent request from the same browser (same
request headers), the configuration system merely returns the corresponding
browser capabilities object from the cache.
Browser Definition Files and
Control/Adapter Mappings
Browser definition (.browser) files are analogous to the <browserCaps> section in the
Machine.config file in versions 1.0 and 1.1 of the .NET Framework. However,
.browser files contain more data than the <browserCaps> section, and they
allow a separation of browser data from other configuration data. A .browser
file is an XML file that contains one or more <browser> elements. Each <browser> element stores data
about a specific browser and device. By 'device,' I mean hardware
such as a desktop computer, cell phone, or PDA. Mobile device characteristics
can vary significantly among different devices. Because device characteristics
such as screen size and color support are important for rendering, they are
stored with browser information in .browser files. A <browser> element specifies how
to identify a browser/device combination, contains browser and device
capability information, and specifies mappings between controls and adapters. A
.browser file can be system-wide or application-specific. The system-wide files
are in the ConfigBrowsers subdirectory of the .NET Framework installation.
Application-specific .browser files are in the Browsers directory of the Web
application. You will see browser files in more detail in the section Browser Definition File
Syntax.
If you look in the <controlAdapters> section of the root <browser> element in the
Openwave.browser file, you will see that the WmlLoginAdapter is mapped
to the Login control, as follows:
<controlAdapters markupTextWriterType='System.Web.UI.WmlTextWriter'>
<adapter
controlType='System.Web.UI.WebControls.Login'
adapterType='System.Web.UI.WebControls.Adapters.WmlLoginAdapter'/>
</controlAdapters>
In the pre-release of the .NET Framework
'Whidbey', LoginAdapter is mapped to the Login control
in the root browser definition in the Openwave.browser file. However, in future
versions, the mapping could change so that WmlLoginAdapter would be
mapped to the Login control as shown above.
The browser definition for Internet Explorer inherits its
control/adapter mappings from the default browser definition in the
Default.browser file. This definition maps every control to the null
type (represented by an empty string):
<adapter controlType='System.Web.UI.WebControls.Login'
adapterType= ''/>
This causes the Adapter property of the Login
control to be null when the requesting browser is Internet Explorer, as
shown in Figure 4.
Adapter Hierarchy
Figure 6 shows a section of the hierarchy of adapter
classes in ASP.NET. This figure is illustrative; the actual adapter classes
might change in a future release of the .NET Framework. The adapter hierarchy
generally follows the same pattern as the control hierarchy. For example, the ControlAdapter
class corresponds to the Control class, WebControlAdapter to WebControl,
and CalendarAdapter to Calendar. In general, each control has a
corresponding generic adapter class that implements core adaptive rendering
logic. Adapters that render specific markup, such as XHTML or WML, are derived
from the generic adapter. For example, the Button control has a
corresponding ButtonAdapter class from which derive the HtmlButtonAdapter
and XmlButtonAdapter classes. (The control adapter classes whose names
begin with 'Html' render XHTML markup. However, among the page adapters,
the XhtmlPageAdapter renders XHTML)
Figure 6. ASP.NET adapter hierarchy
Notice that CalendarAdapter does not have markup-specific
adapters that derive from it. This is because the Calendar control is a
composite control, which delegates the task of rendering markup to its child
controls, and the child controls have markup-specific adapters mapped to them.
The Calendar control does need a generic adapter mainly to modify its
layout for mobile-device browsers. When you implement a composite control, you
might not need to implement an adapter for the (parent) control.
For the most part, you might not need to implement adapters
for specific devices; instead, you can just implement an adapter for a specific
type of markup. For example, the WmlCheckBoxAdapter adapts the CheckBox
control's rendering for a WML browser, be it in a Nokia cell phone or an
Ericsson cell phone. If you do need to implement a device-specific adapter, do
so by extending the markup-specific adapter. For example, you could implement a
NokiaWmlCheckBoxAdapter that derives from
WmlCheckBoxAdapter.
The convention for adapter namespaces is to add
'Adapters' to the control's namespace. For example, System.Web.UI.WebControls.Adapters
contains adapters for the controls in System.Web.UI.WebControls. If the
namespace for your controls is MyControls, name your adapter namespace MyControls.Adapters.
Adapter Resolution at Run Time
The adapter naming convention described above provides a
cue to control developers about which adapter(s) could be associated with a
control. However, naming an adapter using the convention does not automatically
create an adapter/control mapping. You must create the mapping in the <controlAdapters> section of a <browser> element. A browser
defintion inherits (and can override) the control/adapter mappings of its
parents. For example the Openwave.browser file does not have a <controlAdapters> entry explicitly for
an Openwave 5.1 browser. This browser's definition inherits mappings from the
root <browser> element specified in
the Openwave.browser file. You will see how control/adapter mappings are
inherited in the Browser Definition Tree
and Browser Capabilities Object section of this article.
The control/adapter mappings for a requesting browser are
available at run time through the browser capabilities object, as you saw in
the Adaptive Rendering
Architecture section. When a control accesses its Adapter
property, it invokes the browser capabilities object under the hood. If the
browser capabilities object finds an adapter type mapped to the exact control
type, it instantiates that adapter type. (ASP.NET does not instantiate a
control adapter when an empty string is mapped to the control type, as
described in the next subsection.) If ASP.NET does not find an adapter mapping,
it walks up the inheritance chain of the control until it finds a control with
an adapter mapping, and then instantiates that adapter. For example, suppose
you create a MyCheckBox control that derives from the CheckBox control, but do not provide
an adapter for it. If a cell phone with an Openwave 4.x or 5.x browser accesses
a page that uses your MyCheckBox control, ASP.NET will not find an adapter matched
exactly to MyCheckBox. Therefore, it moves up one level in the inheritance chain, where it
finds WmlCheckBoxAdapter as the adapter for CheckBox, so it
instantiates WmlCheckBoxAdapter as the adapter for your control. If
ASP.NET walks your control's inheritance chain and does not find any adapter
mapped to your control, it instantiates ControlAdapter as the adapter
for your control.
Null adapter property
When your control's rendering is adequate for a certain
browser, you can indicate that the control does not need an adapter for that
browser by specifying the adapter type name as an empty string ('') in the <controlAdapters> section of the
browser definition:
<browser id='someID' parentID='someParentID
'>
<controlAdapters>
<adapter
controlType='MyControl' adapterType='' />
</controlAdapters>
</browser>
When the specified browser makes a request, ASP.NET does
not create an adapter for the control; it returns null for the adapter
type. Specifying the adapter type name as an empty string is preferable to
omitting the adapter mapping in the browser definition. A missing mapping
causes ASP.NET to walk your control's inheritance chain and, if it finds no
other control with an adapter, to eventually instantiate ControlAdapter
as the adapter for your control. The functionality of a control with ControlAdapter
as its adapter is equivalent to that of a control with an Adapter
property that is null because ControlAdapter delegates each call
to the control, as you will see in the Adapter Participation in
the Control Life Cycle section of this article. However, when an adapter
is not needed, it is more efficient to avoid the creation and subsequent
invocation of the adapter altogether. In the <controlAdapters> section of the root
browser definition in Default.browser, all the ASP.NET controls are mapped to
an empty string. Subsequent browser definitions override the default mapping to
add adapter mappings as needed.
If you develop a control that is intended only for a
specific category of browsers (such as HTML browsers), and you do not implement
and map adapters for other browsers, you can exclude the control from the
adaptive rendering architecture by making it non-adaptable. To make a control
non-adaptable, override the read-only Adapter property and return null,
as shown in the following code:
protected override ControlAdapter Adapter
}
Walkthrough: Implementing an Adapter
and Mapping It to a Server Control
This walkthrough shows the key steps in authoring an
adapter. It implements a simple control, implements an adapter that adapts the
control's rendering for WML browsers, and then creates a configuration file
that maps the adapter to the control for a class of requesting browsers.
Implementing a Simple Server Control
The following code defines a simple MyLabel control that exposes
a Text property and renders
it by overriding the RenderContents methods of the base WebControl
class. The control uses the methods of the HtmlTextWriter class to
render HTML.
// MyLabel.cs
using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace MyControls
set
}
protected override void
RenderContents(HtmlTextWriter writer)
}
}
Implementing a Simple Adapter
The following code defines a WmlMyLabelAdapter class that adapts the
rendering of the MyLabel control for WML browsers.
// WmlMyLabelAdapter.cs
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.Adapters;
using MyControls;
namespace MyControls.Adapters
}
public override void
Render(MarkupTextWriter markupWriter)
}
}
In keeping with the naming convention for adapter
namespaces, the namespace name for WmlMyLabelAdapter, MyControls.Adapters appends .Adapters to the control's
namespace, MyControls.
WmlMyLabelAdapter derives from WebControlAdapter, the
adapter associated with WebControl, which is the base class for MyLabel. Because the adaptive rendering for
MyLabel does not require core
logic that is independent of markup language or device, I did not create a base
MyLabelAdapter class from which to
derive specialized adapters such as WmlMyLabelAdapter. If your control does
require core adaptive logic, you should create a generic adapter class that
implements the core logic and then derive specialized adapters from it. Or, you
might create a generic adapter class without any implementation merely to
provide a base adapter from which to derive adapters for derived controls.
To generate markup, WmlMyLabelAdapter uses the methods of
the WmlMarkupWriter object passed into its Render method. WmlMyLabelAdapter renders style tags by
invoking the EnterStyle and ExitStyle methods of WmlTextWriter,
as shown in the Render method.
Mapping an Adapter to a Control
To associate the WmlMyLabelAdapter with the MyLabel control when the
request is made by an Openwave browser, I'll create a MyMappings.browser file
that contains the following syntax:
<browsers>
<browser
refid='UP'>
<controlAdapters>
<adapter
controlType='MyControls.MyLabel'
adapterType=
'MyControls.Adapters.WmlMyLabelAdapter' />
</controlAdapters>
</browser>
</browsers>
The value of the refid attribute, 'UP', is the identifier
that ASP.NET uses for Openwave browsers. It is defined in the system-wide
Openwave.browser file. The MyMappings.browser file must be placed in the
Browsers directory of a Web application that uses the MyLabel control if the
adapter assembly is in the Bin directory. Alternatively, the .browser file
could be placed in the system-wide ConfigBrowsers directory if the adapter
assemblies are installed in the global assembly cache.
In a future release of the .NET Framework
'Whidbey', the refid attribute is likely to change to refID. (Since a .browser
file is an XML file, the case is important). For details about the syntax of
.browser files, see the Browser Definition File
Syntax section of this article.
Using the Control in a Page
The following example shows a page that uses the MyLabel control. The UP:Text attribute denotes the Text property of the MyLabel control filtered for
devices that use Openwave browsers. This kind of device filtering is built into
the ASP.NET page framework and does not require any additional code in either
the MyLabel control or its
adapter, WmlMyLabelAdapter.
<%@ page language='C#'%>
<%@ register tagprefix='aspSample'
namespace='MyControls'
assembly='MyControls'
%>
<html>
<head
runat='server'>
<title> Adapter Authoring
Demo </title>
</head>
<body>
<form
runat='server'>
<aspSample:MyLabel
UP:Text='Adapters 101'
Text='Introduction to
Adapter Authoring'
id='mylabel1'
font-bold='true' forecolor='#ff0066'
runat='server'
/>
<p style='FONT-SIZE:
x-small'>
This demo shows how to author
a control adapter and map it to a
control.
</p>
</form>
</body>
</html>
Rendering in different browsers
Figure 7 shows the page that uses the MyLabel control, as viewed in
Internet Explorer 6.0.
Figure 7. A page that uses the MyLabel control, as viewed
in Internet Explorer 6.0
Figure 8 shows the page as viewed in an Openwave 5.1
device emulator. Note that device filtering causes the string 'Adapters
101'
to be displayed in the Openwave browser for the value of the Text property of MyLabel, while the string 'Introduction to
Adapter Authoring' is displayed in Internet Explorer.
Figure 8. A page that uses the MyLabel control, as viewed
in an Openwave 5.1 device emulator. Image courtesy Openwave Systems Inc.
For a list of device emulators for testing adaptive
rendering, see
Device Simulators.
Building the Sample
The following sample files (available in the code
download for this article) are needed for this walkthrough:
MyLabel.cs The source code for
the MyLabel control
WmlMyLabelAdapter.cs The source code for
the WmlMyLabelAdapter adapter
MyMappings.browser The mapping between
the control type and the adapter type
AdapterDemo.aspx A page that uses the MyLabel control
Building the sample using Visual Studio
'Whidbey'
To compile and run the sample using Visual Studio
'Whidbey':
1.
Create a blank solution.
2.
Add a new C# project to the solution, add
MyLabel.cs to the project, and compile the project.
3.
Add another new C# project to the solution,
add WmlMyLabelAdapter.cs to the project, add a reference to the assembly
created in step 2, and compile the project.
4.
Add a new Web site to the solution.
5.
Add the AdapterDemo.aspx page to the Web
site.
6.
Add a reference to the assemblies created in
steps 2 and 3.
7.
Add a Browsers directory to the Web site and
add the MyMappings.browser file to it.
8.
Access AdapterDemo.aspx using Internet
Explorer and an Openwave 4.x or 5.x device emulator.
When creating controls for distribution, it is preferable
to create separate assemblies for controls and adapters as described in steps 2
and 3, so that you can easily provide adapter updates for new browsers or
devices.
When testing your controls and adapters, you can skip
precompilation (steps 2 and 3) when you have Visual Studio 'Whidbey.'
Simply place the source files for the controls and adapters in the Code
directory under your Web application. ASP.NET will compile the controls and
adapters into a single assembly and access it from any page in your Web
application. When you place source files in the Code directory, the page
directive for the registering the tag prefix does not need an assembly
attribute. (For example, assembly='MyControls' is not needed.)
Building the sample using the .NET
Framework 'Whidbey' SDK
Use the following steps to compile and run the sample
using the standalone .NET Framework 'Whidbey' SDK and command-line
tools:
1.
Compile the MyLabel control into an
assembly using the following command:
2.
csc /out:MyControls.dll /t:library
/r:System.dll /r:System.Web.dll
3.
MyLabel.cs
4.
Copy MyControls.dll into the directory that
contains WmlMyLabelAdapter.cs. Compile MyLabelAdapter into an assembly
using the following command:
5.
csc /out:MyControls.Adapters.dll /t:library
/r:System.dll
6.
/r:System.Web.dll /r:MyControls.dll
WmlMyLabelAdapter.cs
Note that the compiler needs to reference the control assembly
when compiling the adapter. Therefore controls must be compiled before the
adapters, and the control assembly must be in the same directory as the adapter
source code.
7.
Create an IIS virtual root and create a Bin
directory and a Browsers directory under the root.
8.
Copy the control and adapter assemblies
created in steps 1 and 2 into the Bin directory.
9.
Copy the MyMappings.browser file to the
Browsers directory.
10.
Copy AdapterDemo.aspx to the Web application
root (IIS virtual root.)
11.
Access AdapterDemo.aspx using Internet
Explorer and an Openwave 4.x or 5.x device emulator.
Packaging and Device Updates
If you want to use the MyLabel control in a page accessed by Openwave
4.x and 5.x browsers, you must have the MyControls assembly, the MyAdapters assembly,
and the MyMappings.browser configuration file.
When you create custom controls for distribution and want
to enable adaptive rendering for them, you must provide the following:
The assembly that contains the controls.
The assembly that contains adapters for the
controls.
The .browser files that contain mappings
between the control types and adapter types.
An installer or directions to the application
developer to install the assemblies and the .browser files in the application's
Bin and Browsers directories, respectively. Alternatively, the control and
adapter assemblies could be installed in the global assembly cache (GAC) and
the .browser files in the system-wide ConfigBrowsers directory.
When new browsers or devices become available, you can
add support for them by providing updates to your adapter assemblies and
.browser files, similar to the device updates that ASP.NET provides.
Adapter Participation in the Control
Life Cycle
As you saw in the Adaptive Rendering
Architecture section, ASP.NET creates a control's adapter when the
control first accesses its Adapter property. This generally happens in
the Init phase of the control. If ASP.NET creates an adapter for a control
(that is, if the Adapter property is not null), the adapter
participates in the life cycle of the control as illustrated in Figure 9.
Figure 9. Main life-cycle phases of a control and its
adapter
An adapter has methods such OnInit, OnLoad,
OnPreRender, and Render that are analogous to the control's
life-cycle methods. In each life-cycle phase, a control invokes the
corresponding method of its adapter. After the adapter completes its work, it
generally calls its base class, which eventually calls the life-cycle method of
the control. Both the adapter and the control can implement logic for each
phase, and execution flows from the control to the adapter and back to the
control. Commonly, in the Render phase, either the adapter or the control (but
not both) implements rendering logic. This is because, if the adapter exists,
it generally takes over rendering and generates a markup or layout different
from that of the control. Rendering is described in more detail in the next
section, Adaptive Rendering and
Markup Writer Classes.
Because adapter invocation is built into the Control
class, when you implement a custom control you do not have to explicitly invoke
your control's adapter.
The following code lists the properties and life-cycle methods
of the ControlAdapter class:
public class ControlAdapter
public virtual Page Page
public virtual PageAdapter
PageAdapter
protected
HttpBrowserCapabilities Browser
// Life-cycle methods.
public virtual void
OnInit(EventArgs e)
public virtual void
OnLoad(EventArgs e)
public virtual void
OnPreRender(EventArgs e)
public virtual void
Render(MarkupTextWriter writer)
public virtual void
OnUnload(EventArgs e)
public virtual void
BeginRender(MarkupTextWriter writer)
public virtual void
EndRender(MarkupTextWriter writer)
public virtual void
LoadAdapterState(object state)
public virtual object SaveAdapterState()
}
To implement an adapter, you directly or indirectly
extend the ControlAdapter class and override one or more of its
life-cycle methods. By default, each life-cycle method of ControlAdapter
merely invokes the corresponding method of its control. You can override any
life-cycle method and implement the logic that your adapter needs to execute at
that phase. Afterward, invoke the corresponding method of the base adapter
class. For example:
public override void OnInit(EventArgs e)
In each phase except Render, you must call the base
class's life-cycle method from your override to enable the base class to
execute its logic and the control to execute its life-cycle phase. For example,
if you did not invoke base.OnInit in the previous example, the control would
not be able to raise its Init event. Note that you should invoke base.OnInit, not Control.OnInit, even if the base
adapter does not provide any implementation. The next paragraph explains why.
A control can invoke its adapter's methods because the
life-cycle methods of ControlAdapter are public. Interestingly, an
adapter is also able to invoke its control's life-cycle methods, although those
methods are protected. OnInit, OnLoad, and the other life-cycle
methods of the Control class are protected internal and can be
accessed by derived classes and classes in the same assembly. Since ControlAdapter
and Control are in the same assembly, ControlAdapter can access
the life-cycle methods of Control. A custom adapter that you implement
might not be in the same assembly as its control, but calls to the adapter's
base class eventually resolve to ControlAdapter, which invokes the
life-cycle methods of Control. Each life-cycle method of Control
is virtual, and resolves to the method's override in the most-derived class.
Therefore a call to a life-cycle method of Control resolves to the
life-cycle method override (if any) in your custom control. An adapter is thus
able to invoke its control's protected life-cycle methods by making calls such
as base.OnInit
to its
base adapter.
An adapter can participate in any life-cycle phase of a
control, including state management and postback. To perform custom state
management in your adapter, you must override the SaveAdapterState and LoadAdapterState
methods, which allow an adapter to save state to and load state from the
control's view state. The implementation of these methods is similar to the
implementation of the SaveViewState and LoadViewState methods of
a control. To process postback data, you must implement the IPostBackDataHandler
interface. To handle the postback event, you must implement the IPostBackEventHandler
interface. I hope to describe adapter participation in state management and
postback in further detail in a future article.
An adapter can handle any event that the control exposes
by attaching an event handler to the event. For example, the adapter of the Button
control can handle the Click event of the Button control. Note,
however, that you should not attach event handlers to life-cycle events.
Instead, override the adapter's OnInit, OnLoad, and the other
life-cycle methods that I just described. The main reason for this is that when
you attach an event handler, the handler's order of invocation is not
predictable. On the other hand, when you override a life-cycle method, such as OnInit,
your adapter's method is always invoked before the control executes the code in
its own OnInit method and raises the Init event.
Adaptive Rendering and Markup Writer
Classes
If you have implemented custom controls, you are familiar
with the HtmlTextWriter class, which is used by controls to render HTML
markup. In 'Whidbey,' in addition to HtmlTextWriter, ASP.NET
provides additional classes for rendering other kinds of markup, such as XHTML
and WML. Figure 10 shows the hierarchy of the markup writer classes.
Figure 10. Hierarchy of markup writer classes
A control uses HtmlTextWriter for rendering
markup, while an adapter can use any markup writer. The MarkupTextWriter base
class defines generic methods for writing markup. Specific markup writers
derive from MarkupTextWriter and override its methods to render specific
types of markup. The markup writer classes greatly simplify the task of the
control and adapter developer by providing methods for generating markup tags
and attributes. The markup writers are extensible and customizable. You can
also implement a new markup writer (for a new kind of markup) by deriving from
the MarkupTextWriter base class.
To associate a markup writer type with a browser, specify
a value for the markupTextWriterType attribute in the <controlAdapters> section of a browser
definition:
<browser id='nokia' parentID='default'>
<controlAdapters
markupTextWriterType='System.Web.UI.WmlTextWriter'>
</controlAdapters>
</browser>
At run time, a page creates the MarkupTextWriter
specified in the <controlAdapters> section of the requesting browser's definition.
In 'Whidbey,' custom controls render markup
using HtmlTextWriter, as in earlier versions. Based on the requesting
browser, the exact HtmlTextWriter type that the page passes to the
control is HtmlTextWriter itself or a derived type such as XhtmlTextWriter
or ChtmlTextWriter. The HTML writers can render HTML 4.0, XHTML, HTML
3.2 or cHTML markup. A control automatically benefits from the
uplevel/downlevel rendering capabilities of the HtmlTextWriter types, as
you'll see in more detail at the end of this section. When the browser accepts
a subset of HTML, a control does not need an adapter merely to render the right
markup. However, the control might still need an adapter to adapt its layout or
behavior.
The Render method of ControlAdapter takes a
MarkupTextWriter instance as its argument:
public virtual void Render(MarkupTextWriter writer)
This allows an adapter to use any markup writer. A
control uses internal methods to invoke its adapter's Render method. The
adapter invocation is built into the base Control class and does not
require any work on the part of the control developer.
Composite Controls and Adapters
Composite controls may not need adapters in many
scenarios. In the Render phase, the parent (composite) control invokes the RenderControl
method of each child control. As you saw in Adapter Participation in
the Control Life Cycle, if an adapter exists, a control invokes its adapter
in each life-cycle phase. Each child control therefore invokes its adapter's Render
method, which renders appropriate markup for the requesting browser. The markup
is written sequentially, creating a basic flow layout. If this rendering is
adequate, you do not need to implement an adapter for the parent control.
However, if you want to provide a different layout for a
specific form factor, or change the order in which controls are rendered, or
enforce how controls are grouped for display, you will have to implement an
adapter for the composite (parent) control.
Simple HyperLink Control and Adapter
Example
This example implements a simple control and its adapter
to demonstrate how to use the markup writer classes. The MyHyperLink control in this example
is a simplified version of the ASP.NET Hyperlink control. The example
also shows how to render a softkey label for devices such as cell phones that
have soft (programmable) keys. Figure 11 shows a softkey label
('Click') displayed on the cell phone's screen above the physical
key.
Figure 11. The MyHyperlink control renders a
hyperlink and a softkey label. Image courtesy Openwave Systems Inc.
The MyHyperlink control
The following code shows the MyHyperlink control, which
renders a hyperlink and a softkey label. The NavigateUrl and SoftkeyLabel properties correspond
to the URL of the link and the text string displayed over the softkey,
respectively. The Text
property specifies the text displayed as the hyperlink.
// MyHyperlink.cs
using System;
using System.Web;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace MyControls
[
Bindable(true),
Category('Navigation'),
DefaultValue(''),
Description('URL for
the hyperlink')
]
public string NavigateUrl
set
}
[
Bindable(true),
Category('Appearance'),
DefaultValue(''),
Description('Label for
programmable soft key')
]
public string SoftkeyLabel
set
}
[
Bindable(true),
Category('Appearance'),
DefaultValue(''),
Description('Text to
display on the hyperlink')
]
public virtual string Text
set
}
protected override void
AddAttributesToRender(HtmlTextWriter
writer)
}
protected override void
RenderContents(HtmlTextWriter writer)
writer.Write(text);
}
}
}
MyHyperlink overrides the control's standard rendering
methods, which take an HtmlTextWriter instance as the argument to render
HTML. MyHyperlink overrides the AddAttributesToRender
method of WebControl to render the URL as an href attribute on the HTML
tag for the hyperlink, and overrides RenderContents to write the text of
the hyperlink.
Notice that the implementation of MyHyperlink in 'Whidbey' is practically the
same as in earlier versions of ASP.NET. The only difference is that MyHyperlink defines a SoftkeyLabel property, which, as
you will see, is used by the control's adapter to render a softkey label for
mobile-device browsers.
Note that MyHyperlink does not contain code to invoke its
adapter. Because controls participate in adaptive rendering by default, you do
not have to implement any logic in your control to enable this functionality.
WML adapter for the MyHyperlink control
The following code shows WmLMyHyperlinkAdapter, which is a WML
adapter for the MyHyperlink control.
// WmLMyHyperlinkAdapter.cs
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.Adapters;
using MyControls;
namespace MyControls.Adapters
}
// Renders the MyHyperLink
control for WML browsers.
public override void
Render(MarkupTextWriter markupWriter)
if (softkeyLabel.Length
== 0)
writer.EnterStyle(Control.ControlStyle);
targetUrl =
Control.ResolveClientUrl(targetUrl);
PageAdapter.RenderBeginHyperlink
(writer,targetUrl,false,softkeyLabel,Control.AccessKey);
writer.WriteEncodedText(text);
PageAdapter.RenderEndHyperlink(writer);
writer.ExitStyle(Control.ControlStyle);
}
}
}
WmlMyHyperlinkAdapter overrides the Control property to
cast the Control instance to its exact type. This enables the adapter to
access the control's complete object model via the Control property
without performing a cast each time.
In the Render method override, WmLMyHyperlinkAdapter casts the markup
writer passed into it to WmlTextWriter, the exact markup writer type
specified in the browser definition. This pattern allows the adapter to access
the complete object model of the specific writer without casting the writer
each time.
WmLMyHyperlinkAdapter invokes the EnterStyle and ExitStyle
methods of WmlMarkupTextWriter to render opening and closing style tags,
and renders the hyperlink and link text between those method calls so that
style markup is applied to the rendered content. It invokes the WmlPageAdapter
class to render the hyperlink tag and the softkey label.
XHTML and cHTML adapters?
The MyHyperLink control shown earlier does not need adapters
for XHTML or cHTML browsers. The HtmlTextWriter type that the page
passes to the control automatically generates the kind of HTML that is accepted
by the browser. This is similar to the automatic downlevel rendering of ASP.NET
controls in earlier versions of the .NET Framework. In 'Whidbey,'
controls are capable of uplevel XHTML rendering or downlevel HTML 3.2 (or
cHTML) rendering without any work on the part of the control developer. To use
automatic uplevel-downlevel rendering, your control should not write raw markup
but instead invoke HtmlTextWriter for generating markup.
Although the markup writers do most of the work when the
browser accepts a subset of HTML, a control might still need adapters for XHTML
and cHTML mobile-device browsers.
Control/adapter mapping in the .browser
file
The following syntax adds an empty string mapping for the
MyHyperlink
control
in the browser definition for Internet Explorer, since Internet Explorer does
not need an adapter for the MyHyperlink control.
<browser refid='IE'>
<controlAdapters>
<adapter
controlType='MyControls.MyHyperlink'
adapterType='' />
</controlAdapters>
</browser>
The following control/adapter mapping associates the WmlMyHyperlinkAdapter with the MyHyperlink control for Openwave
browsers.
<browser refid='UP'>
<controlAdapters>
<adapter
controlType='MyControls.MyHyperlink'
adapterType='MyControls.Adapters.WmlMyHyperlinkAdapter' />
</controlAdapters>
</browser>
The file MyMappings.browser, available with the code
download for this article, contains the above mappings.
Markup rendered by the MyHyperlink
control in different browsers
The following example uses the MyHyperlink control in a page.
The Font and Color style properties are set on the control so
that you can compare how styles render in different browsers.
<%@ page language='C#' %>
<%@ register tagprefix='sample'
namespace='MyControls' assembly='MyControls' %>
<html>
<head runat='server'>
<title>Test Page for the
MyHyperLink control</title>
</head>
<body>
<form
runat='server'>
<p>
MyHyperlink renders a hyperlink
to HTML and WML browsers.
</p>
<sample:MyHyperlink
id='myhyperlink1' Text='ASP.NET Home'
Font-Bold='true' ForeColor='Green'
NavigateUrl='https://www.asp.net'
SoftKeyLabel='Click'
runat='server' />
</form>
</body>
</html>
If you view the page in an HTML 4.0 browser such as
Internet Explorer 6.0, you will see the following markup rendered by the MyHyperlink control. MyHyperlink, like all Web
controls, renders the control's typed style properties via a CSS style
attribute on the control's tag.
<a href='https://www.asp.net'
style='color:Green;font-weight:bold;'>ASP.NET
Home</a>
If you view the page in a WML browser such as Openwave
5.1, you will see the following markup rendered by WmlMyHyperlinkAdapter:
<b><a href='https://www.asp.net'
title='Click'>ASP.NET Home</a></b>
The adapter renders the SoftkeyLabel property of the MyHyperlink control as the title attribute on the
hyperlink tag. The markup rendered by the WmlMyHyperlinkAdapter contains <b> tags but does not
contain tags for the font color. This is because WML contains a limited number
of tags, such as <b>, <i>, and <size>, for text formatting.
If the page developer sets style properties on the control that are not
supported in WML, WmlMarkupWriter ignores those properties.
If you view the page in an HTML 3.2 browser, you will see
the following markup rendered by the MyHyperlink control.
<a href='https://www.asp.net'><b><font
color='Green'>ASP.NET
Home</font></b></a>
Note that in this case down-level rendering is performed
by Html32TextWriter, and MyHyperlink does not need an
adapter. Because HTML 3.2 does not support CSS styles, Html32TextWriter renders
the style properties of the control as tags such as <b> and <font> around text.
Browser Definition File Syntax
A browser definition (.browser) file specifies how to
identify browsers, contains data about device and browser capabilities, and
specifies mappings between controls and adapters. A browser definition file is
an XML file named with a .browser file name extension. You can create a
.browser file using an XML authoring tool or a simple text editor. System-wide
browser definition files are in the $windirMicrosoft.NETFramework<version
number>ConfigBrowsers directory. Application-specific browser definition
files must be placed in the Browsers directory of an application. You can get
a feel for the syntax of .browser files by looking at the .browser files, such
as Nokia.browser or Openwave.browser, in the ConfigBrowsers directory.
The .browser file that I created earlier in the
walkthrough was very simple and had only a <controlAdapters> section. However,
when you want to enable adaptive rendering for new browsers, you might need to
create more complex .browser files. This section describes the detailed syntax
of a .browser file.
A .browser file has a <browsers> root element and one
or more child <browser> elements. A <browser> element, in turn, has child elements
that provide data about browser and device capabilities and specify
control/adapter mappings.
The following prototype shows the main sections of a
.browser file:
<browsers>
<browser
id='<unique identifier>' parentID='<identifier of
parent>'
<!-- Use refID instead of
parentID to add settings
to an existing node. -->
<identification>
<!-- Regular expression
match based on user agent or any header.-->
</identification>
<capture>
<!-- Capture capabilities
from user agent
or headers using regular
expression matches. -->
</capture>
<capabilities>
<!-- Browser and
device capabilities. -->
<!-- The following two
capabilities are hard-coded. -->
<capability
name='browser' value='<SomeName>' />
<capability
name='canInitiateVoiceCall' value='<SomeValue>'
/>
<!-- The following two
capabilities are obtained from regular
expression matches.-->
<capability
name='deviceID' value='$' />
<capability
name='deviceVersion' value='$' />
</capabilities>
<controlAdapters
markupTextWriterType='System.Web.UI.WmlTextWriter'>
<!-- Control/adapter
mappings. -->
<adapter
controlType='System.Web.UI.WebControls.Label'
adapterType='System.Web.UI.WebControls.Adapters.WmlLabelAdapter'
/>
</controlAdapters>
</browser>
<!-- Other <browser>
elements. -->
</browsers>
The id, parentID, and refID Attributes
of a <browser> Element
A <browser> element either defines a new browser or adds
settings (capabilities and adapter mappings) to an existing browser definition.
The syntax for the two cases is different.
To define a new browser, create a <browser> element that has id and parentID attributes and an <identification> section. The
following example shows a browser definition from the Nokia.browser file:
<browser id='Nokia' parentID='default'>
<identification>
<userAgent
match='Nokia.*' />
</identification>
<browser>
For the id value you must choose a unique string, which ASP.NET
assigns to the browser/device combination. The device filter that you see in
the ASP.NET property filtering syntax (Nokia:Text='Some Text') is the value of the id attribute in the
browser definition. To the parentID attribute you must assign the id of the browser's
parent. The parentID is described in more detail in the Browser Definition Tree
and Browser Capabilities Object section of this article. The parentID value 'default' refers to the default
browser definition (defined in the ConfigBrowsersDefault.browser file), which
is at the root of all browser definitions.
In the system-wide .browser files in the ConfigBrowsers
directory, you will see examples of browser definitions with id values such as
'Ericsson', 'Nokia', and 'UP'. When you create a
new browser definition, you must choose an id value that has not been previously used in
system-wide or application-specific .browser files.
To add settings to an existing browser definition, create
a <browser> element that has a refID attribute (instead of
the id and parentID attributes) and do
not provide an <identification> section. The refID must match the id of the browser to
which you are adding settings. For example, to add capabilities to a Nokia
browser (whose id='Nokia') create the following element:
<browser refID='Nokia'>
<!-- Must not have an
<identification> section. -->
<capabilities>
</capabilities>
</browser>
The MyMappings.browser file you saw in the walkthrough
has <browser> elements that use the
refID attribute to add control/adapter
mappings to existing browser definitions. (In the pre-release version of the
.NET Framework code-named 'Whidbey,' use refid instead of refID.)
Child Elements of a <browser>
Element
A <browser> element has some or all of the following
child elements:
An <identification> element that
identifies the browser using a regular expression match (and optional
non-match) based on the headers sent by the browser. In addition to the user
agent, you can use other headers and capabilities to further narrow the browser
identification. When you are not defining a new browser, but are adding
capabilities to an existing browser definition, do not use the <identification> section.
A <capture> element that specifies a regular
expression match for capturing capabilities from the user agent or headers.
A <capabilities> element that
specifies the capabilities for a device and its browser through name/value
attributes of child <capability> elements. The capability values can be
hard-coded or captured from header information in the <identification> or <capture> sections. For
efficiency, you should hard-code all the known capabilities for a device and
browser combination.
A <controlAdapters> element that maps
control types to adapter types using <adapter> elements. It also specifies the markup
writer type for rendering to the browser.
Some mobile browsers need a gateway (such as a WAP
gateway) to connect to a Web server. A gateway has its own capabilities that
need to be added to the browser definitions. You can add gateway settings to
browser definitions by creating a <gateway> element, which has the same structure
as a <browser> element, as shown in
the next example. A <gateway> element can add its settings to <browser> definitions that have
the same parentID as the specified gateway.
Example .browser File
The following example shows a portion of the system-wide
ConfigBrowsersJphone.browser file. The example illustrates how to:
Identify a browser using a regular expression
match based on the user agent string (<identification> section).
Capture the majorVersion, minorVersion, and deviceModel capability values
using a regular expression match (<capture> section).
Specify capabilities as hard-coded strings or
obtain them through regular expression captures (<capabilities> section).
Specify the markup writer and control/adapter
mappings (<controlAdapters> section).
Use a capability value to narrow browser
identification. The second browser definition in the example narrows the JPhone
browser identification based on the deviceModel capability, which is captured in the
first browser definition from the user agent string.
Define a gateway (<gateway> element).
<browsers>
<!-- sample UA
'J-PHONE/3.0/J-SH04_a' -->
<browser
id='jphone' parentID='default'>
<identification>
<userAgent
match='J-PHONE/.*' />
</identification>
<capture>
<userAgent match='J-
PHONE/(?'majorVersion'd)(?'minorVersion'.d)/(?'deviceModel'.*)'
/>
</capture>
<capabilities>
<capability name='browser'
value='J-Phone' />
<capability
name='cookies' value='false' />
<capability
name='canInitiateVoiceCall' value='true' />
<capability
name='majorVersion' value='$' />
<capability
name='maximumRenderedPageSize' value='6000' />
<capability
name='minorVersion' value='$' />
<capability
name='version' value='$$' />
</capabilities>
<controlAdapters
markupTextWriterType='System.Web.UI.ChtmlTextWriter'>
<adapter
controlType='System.Web.UI.Page'
adapterType='System.Web.UI.Adapters.ChtmlPageAdapter' />
<adapter
controlType='System.Web.UI.DataBoundLiteralControl'
adapterType='System.Web.UI.Adapters.DataBound
LiteralControlAdapter' />
</controlAdapters>
</browser>
<browser
id='jphone_mitsubishi' parentID='jphone'>
<identification>
<capability
name='mobileDeviceModel' match='^J-Dd+' />
</identification>
<capture></capture>
<capabilities>
<capability
name='mobileDeviceManufacturer' value='Mitsubishi' />
</capabilities>
</browser>
<gateway
id='jphone_display' parentID='jphone'>
<identification>
<header
name='X-JPHONE-DISPLAY'
match='(?'screenWidth'd+)*(?'screenHeight'd+)'
/>
</identification>
<capture></capture>
<capabilities>
<capability
name='screenPixelsHeight' value='$' />
<capability
name='screenPixelsWidth' value='$' />
</capabilities>
</gateway>
</browsers>
Under the Hood: Parsing and Compilation
of .browser Files
The system-wide .browser files that you see in the
ConfigBrowsers directory of the .NET Framework installation are already parsed
into the System.Web.Config.Handlers.BrowserCapsFactory class and
precompiled into an assembly. This assembly is installed in the global assembly
cache and can be used by any application. If you add or delete .browser files
from the ConfigBrowsers directory after the installation of the .NET
Framework, you must run the tool aspnet_regbrowsers.exe, which ships with the
.NET Framework, to parse the .browser files (new and old).
The .browser files placed in an application's Browsers
directory do not require any work on the part of the application developer or
site administrator. They are automatically detected by ASP.NET, parsed into a
class derived from BrowserCapsFactory, and compiled dynamically into an
assembly that is accessible by ASP.NET for the application.
BrowserCapsFactory and its derived classes implement the logic
described in the next section, Browser Definition Tree
and Browser Capabilities Object. BrowserCapsFactory walks the
browser definition tree to match the requesting browser and create an HttpBrowserCapabilities
object. This is shown schematically in Figure 5.
Checklist for Creating .browser Files
The following list summarizes the main tasks involved in
creating a .browser file:
Add one or more <browser> elements.
Create a new browser definition using the id and parentID attributes, and
provide an <identification> section.
Provide a unique value for the id attribute when you
create a new browser definition. This value must not be previously used in a
system-wide or application-level .browser file.
Add capabilities or adapter mappings to an
existing browser definition by using the refID attribute. Do not create an <identification> section.
Do not modify existing .browser files. Create
one or more new .browser files if you want to define new browsers or add
settings to existing browsers. Based on whether you want the changes to be
system-wide or at the application level, add these files to the system-wide or
application-level Browsers directory. You should not modify existing files
because your changes will be lost if the original files are replaced or
reinstalled, and because you might accidentally introduce changes to existing
data. When you create a new .browser file you do not have to copy existing
settings into it. ASP.NET automatically merges data from new files with that in
existing .browser files.
Browser Definition Tree and Browser
Capabilities Object
ASP.NET combines the browser definitions from the
system-wide and application-specific .browser files into a browser definition
tree, such as the one shown in Figure 12.
Figure 12. Schematic representation of a browser
definition tree
The figure is representative only, and the nodes do not
correspond to exact definitions in the .browser files. The browser definition
tree in this release is likely to change in a future version. Each node in the
tree represents a browser definition labeled by the id of the <browser> element. At the root
of the tree is the default node which corresponds to the id='default' browser definition in
the ConfigBrowsersDefault.browser file. The default node does not represent a
specific browser but denotes an abstract representation of a browser. The
children of the default node are the browser definitions that have parentID='default'. Similarly, the children
of any node are browser definitions whose parentID values equal that of the node's id.
When you create a new browser definition, the value of
the parentID tells the ASP.NET
configuration system under which node to add the new definition. When you add
settings to an existing node, the refID tells ASP.NET which node to add the settings
to.
To identify a requesting browser, the ASP.NET
configuration system walks down the browser definition tree and looks for a
match based on the user agent and other headers. (The match condition is
specified in the <identification> section of a <browser> element). The configuration system
creates a default System.Web.HttpBrowserCapabilities object at the root
(default) node. As it walks down the tree to narrow the browser match, it
merges settings from each matched node into the HttpBrowserCapabilities
object of the parent node. This means that each node adds to or overrides the
previous settings. If a match is eventually found, the resulting HttpBrowserCapabilities
object represents a recursive merge of the inheritance hierarchy of the matched
node. For example, when the configuration system identifies a requesting
browser as nokia_6220, the resulting HttpBrowserCapabilities object is formed by
recursively merging (adding to or overriding the parent) the browser
definitions in the nodes default, nokia, and nokia_6220. If a match is not found, the resulting HttpBrowserCapabilities
object contains the settings in the default node.
The ASP.NET configuration system caches the HttpBrowserCapabilities
objects that it creates, using a subset of the request headers as the cache
lookup key. These objects are available for reuse within the application. For
any browser defined in the browser definition tree, the configuration system
walks the tree and creates an HttpBrowserCapabilities object only once,
on first request. For a subsequent request by a browser with the same headers,
the configuration system merely performs a cache lookup and returns a cached
object.
The HttpBrowserCapabilities Class
An HttpBrowserCapabilities object is an in-memory
representation of the data in the .browser configuration files for a specific
browser. The main properties of an HttpBrowserCapabilities class
correspond to the attributes and elements of a browser definition (<browser> element).
The following list describes the main properties of the HttpBrowserCapabilities
class.
The Browsers property is an ArrayList
that contains the id values from each matched node (<browser
id='someID'>) in the browser definition tree. This property is used
by ASP.NET for device filtering, as you'll see at the end of this section.
The Capabilities property is a
dictionary of browser and device capabilities, stored as strings. During the
browser-identification tree walk, the ASP.NET configuration infrastructure
updates this dictionary at each node by merging into it the name/value pairs of
the <capability> elements from the <capabilities> section of the node.
Adapters is a collection of
control/adapter mappings. During the browser-identification tree walk, the
configuration system updates this collection at each node by merging into it
the mappings from the <controlAdapters> section of the node.
MarkupTextWriter is the markup writer
type that the page must create for rendering markup to the requesting browser.
The configuration system obtains the value of this property from the markupTextwriter attribute of the <controlAdapters> element (<controlAdapters
markupTextWriterType='some MarkupTextWriter type name'>).
The Capabilities dictionary stores capability
values as strings. The HttpBrowserCapabilities class exposes a set of
strongly typed properties that wrap the string values in the Capabilities
dictionary. These properties offer the benefits of type checking and type conversion.
If you add capabilities to a browser definition that do not correspond to the
typed properties of HttpBrowserCapabilities, you can extend the HttpBrowserCapabilities
class to define strongly typed properties that wrap the capabilities you added.
If you define a derived browser capabilities class, named, for example, MyHttpBrowserCapabilities, you can use the <browserCaps> setting in the
Web.config file to tell the configuration infrastructure to use your derived
class instead of HttpBrowserCapabilities, as follows:
<browserCaps>
<result type='
MyHttpBrowserCapabilities' />
</browserCaps>
Note that code that accesses the browser capabilities
object must downcast it to the derived type, in this case MyHttpBrowserCapabilities, to access the new
properties you defined.
How Filters Work
A page developer can customize the value of a control's
property or attribute for a browser by using a filter, which is a string
qualifier such as 'UP' that precedes the property name. The value of
the filter must match the id of some node in the browser definition tree.
<sample:MyLabel id='MyLabel1' runat='server'
UP:Text='Adapters
101'
Text='Introduction to
Adapter Authoring' />
This is how property filtering works. The parser parses
the filtering syntax and generates code such as the following:
if (Request.Browser.IsBrowser('UP'))
A page developer can also selectively set property values
in code by invoking the Request.Browser.IsBrowser method to check
for the browser's identity.
At run time, ASP.NET checks each filter (string) passed
into IsBrowser against the id values in the Browsers collection of
the HttpBrowserCapabilities object. The Browsers collection
contains the id values of matched nodes as ASP.NET walks the browser definition tree
looking for an exact match for the requesting browser. If a filter matches an id in the Browsers
collection, the if clause evaluates to true and the page
framework performs the property assignment specified in the if
block.
The goal of filtering is not to enable the same page to
work with all browsers. Rather, filtering is meant to be used by page
developers to customize a page for browsers within a given category. For
example, in a page created for mobile-device browsers, a page developer could
perform filtering for different cell phone browsers.
Concluding Remarks
This article provided an under-the-hood look at adaptive
rendering in ASP.NET in the pre-release version of the .NET Framework,
code-named 'Whidbey.' It examined how adapters work and demonstrated
the main steps in implementing an adapter and creating a browser definition
file.
Acknowledgments
I am grateful to Matt Gibbs, Simon Calvert, and Mike Pope
of the ASP.NET team for reviewing this article and providing valuable feedback.
Matt and Simon also clarified many technical details in person and in e-mail. I
appreciate Carrie Klumpar's editorial help.
Openwave, the Openwave logo, Openwave SDK, Openwave SDK
Universal Edition, Openwave SDK WAP Edition are trademarks of Openwave Systems
Inc. All rights reserved.
About the Author
Vandana Datye is a freelance author who has written
samples and documentation for the .NET Framework SDK. She is co-author of Developing
ASP.NET Server Controls and Components, published by Microsoft Press. You
can reach Vandana at v-vdatye@microsoft.com.