Building ASP.NET 2.0 Web Sites Using Web Standards

 

Stephen Walther
SuperExpert.com

Applies to:
   Microsoft ASP.NET 2.0
   Microsoft Visual Studio 2005
   Microsoft Visual Web Developer

Note: A document that includes accessibility information for versions 2.0 through 4 of ASP.NET is available as the topic Accessibility in Visual Studio and ASP.NET in the ASP.NET 4 documentation. That document includes updated information about accessibility guidelines, which have changed significantly since this document was published.

Summary: Microsoft ASP.NET 2.0 has many features to help you design and build Web sites that are compliant with XHTML and accessibility standards. This article looks at how and why you should be building these standards-compliant sites. (78 printed pages)

Contents

Introduction
Building XHTML Web Sites
Versions of the XHTML Standard
Creating XHTML Pages
XHTML and ASP.NET Controls
Validating XHTML Pages
XHTML and DOCTYPE Switching
XHTML and MIME Types
Configuring XHTML Conformance
Accessibility Standards
Accessibility Improvements in ASP.NET 2.0
Creating Accessible Images
Creating Accessible Forms
Creating Accessible Navigation
Creating Accessible Data
Creating Accessible XHTML
Creating Accessible Scripts
Validating Pages for Accessibility
Accessing the Amazon Web Services
The Default Page
XHTML Features of the Default Page
Accessibility Features of the Default Page
The Search Page
XHTML Features of the Search Page
Accessibility Features of the Search Page
The Master Page
XHTML Features of the Master Page
Accessibility Features of the Master Page

Introduction

Web standards enable you to build Web sites that are accessible to the broadest possible audience with the least amount of work. The promise of Web standards is that you can design a page once and have the page appear and function in exactly the same way in any modern browser. For example, when built against standards, a page that was designed to display a certain way in Microsoft Internet Explorer can appear the same way in other browsers, such as Mozilla Firefox, Netscape Navigator, Opera, Camino, and Safari, without requiring you to perform any additional work.

An additional benefit of Web standards is that they make your Web sites more easily accessible to persons with disabilities. This is a broad audience that includes everyone from a middle-aged person with failing eyesight, to a person who just broke his or her arm while skiing, to a person who is completely blind. Standards prevent you from unintentionally blocking persons with temporary or permanent disabilities from your Web pages.

The Microsoft ASP.NET 2.0 framework was designed to be the best framework for building Web sites that meet public Web standards. In particular, every control in the ASP.NET 2.0 framework was extensively reviewed and tested against both XHTML and accessibility standards. Furthermore, Microsoft Visual Studio 2005 includes new tools for validating your Web pages against both XHTML and accessibility standards.

The purpose of this paper is to provide you with an overview of XHTML and accessibility standards, and explain how you can take advantage of ASP.NET 2.0 and Visual Studio 2005 to meet these standards. At the end of this paper, you are provided with a step-by-step walkthrough for creating an ASP.NET 2.0 Web site that satisfies both XHTML and accessibility standards.

Building XHTML Web Sites

HTML is officially outdated. The World Wide Web Consortium (W3C) published the first version of XHTML as a recommendation on January 26, 2000. The XHTML standard is intended as a replacement for HTML. According to the W3C, "XHTML is the successor of HTML" (http://www.w3.org/MarkUp/).

The framers of the XHTML standard have two broad goals:

  • Create a cleaner separation between document structure and presentation.
  • Reformulate HTML as an application of XML.

In pursuit of the first goal, the W3C has been steadily removing purely presentational elements and attributes from HTML (a process that they started with HTML 4.0). For example, XHTML 1.0 Strict does not include elements such as the <font> tag, or attributes such as the bgcolor attribute, because these elements and attributes are used solely to describe the appearance of a document, and they have nothing to do with a document's structure.

The W3C has been attempting to wean Web site designers and developers away from the idea that any particular tag should have any particular appearance. For example, you might think that the purpose of an <h1> tag (the heading tag) is to render large, bold text in a page. That would be wrong. The <h1> tag is used to mark a heading in a document, and nothing else. It is up to the browser to determine how the heading tag should be rendered. A screen reader used by a person with reduced eyesight might read aloud the contents of a heading tag with a booming, authoritative voice. A PDA, which doesn't support multiple font sizes, might render the contents of a heading tag with blinking text.

You should not attempt to use page elements, such as the <h1> tag, to control the appearance of a Web page. Instead, you should indicate the appearance of a Web page through the use of Cascading Style Sheets. Preferably, the Cascading Style Sheets should be external Cascading Style Sheets. Use tags and attributes to mark up the structure of a document, and use Style Sheets to control the document's presentation.

The second goal of XHTML is to enforce the stricter rules of XML on HTML developers. In the words of the W3C, "XHTML 1.0 is a reformulation of HTML 4.01 as an XML 1.0 application" (http://www.w3.org/MarkUp/). In other words, when you build a Web page using XHTML, you are actually creating an XML document.

An XML document has a much stricter syntax than an HTML document. For example, XML is case-sensitive, all XML attributes must be quoted, and XML tags cannot overlap. Forcing Web site developers and designers to follow the rules of a more demanding language has many benefits.

One benefit is that pages written with XHTML markup are more cross-browser, cross-device, and cross-operating system compatible. If you open a traditional HTML page in a browser, the browser will make every effort to render the page. The browser will attempt to render the page even if your HTML is a total mess. For example, Internet Explorer (and Firefox and Opera) will display the following HTML page just fine.

<i><B>this is bold and italic</I> and this is bold
</body></HTML>

Internet Explorer happily displays this page, even though the page is missing opening <html> and <body> tags, the <b> tag has no matching closing tag, and the case of the opening and closing <i> tags is inconsistent. All major browsers will accommodate almost any "tag soup" of HTML tags and desperately attempt to render something.

This accommodating behavior of browsers is dangerous, because different browsers (or future versions of the same browser, or the same browser running on a different operating system) might render garbled HTML in different ways. In point of fact, the latest versions of Internet Explorer, Mozilla Firefox, and Opera are surprisingly consistent in the way that they render invalid HTML. However, once you start to play outside of the rules, there are no guarantees.

If you write your Web pages with the stricter rules of XHTML, however, there is more of a chance that your Web pages will work consistently with current browsers, and that they will continue to work with new versions of current browsers introduced in the future. Few companies have the resources to test their Web sites against every browser running on every operating system and every device. If you write your pages against Web standards, you don't have to.

Versions of the XHTML Standard

There are three versions of XHTML 1.0, which correspond to the three versions of HTML 4.01:

  • XHTML 1.0 Transitional
  • XHTML 1.0 Strict
  • XHTML 1.0 Frameset

XHTML 1.0 Transitional contains all of the tags and attributes from HTML 4.01 Transitional. The XHTML 1.0 Transitional standard was introduced to enable existing HTML designers and developers to migrate to XHTML without experiencing too much shock and pain.

XHTML 1.0 Strict differs from XHTML 1.0 Transitional by enforcing a cleaner separation between document structure and presentation. Unlike XHTML 1.0 Transitional, XHTML 1.0 Strict forces you to use Cascading Style Sheets to control the appearance of your pages.

XHTML 1.0 Frameset documents are intended to be documents that use the <frameset> tag to partition a browser into multiple frames (XHTML 1.0 Transitional and Strict pages cannot contain the <frameset> tag).

The W3C has also published XHTML 1.1 as a recommendation (on May 31, 2001). XHTML 1.1 is very similar to XHTML 1.0 Strict. The primary difference is that XHTML 1.1 can be extended with additional modules in order to support new elements. You can, for example, build XHTML 1.1 pages that also include elements from the MathML (the Mathematical Markup Language), or SVG (the Scalable Vector Language), or a custom module of your own creation.

Finally, the W3C is working on a recommendation for XHTML 2.0. Because XHTML 2.0 is still in the draft stage, and no Web browser currently supports this standard, we won't discuss it in this paper.

The ASP.NET 2.0 framework and Visual Studio 2005 are targeted at XHTML 1.0 Transitional. This is the least restrictive of the XHTML standards, and it is the standard that is the most compatible with existing HTML pages. However, you can also build ASP.NET 2.0 pages that target the XHTML 1.0 Strict standard or even the XHTML 1.1 standard (see the later section, Configuring XHTML Conformance).

Creating XHTML Pages

Unlike an HTML page, an XHTML page must be a well-formed and valid XML document. The differences between HTML and XHTML are summarized in Section 4 of the XHTML 1.0 recommendation. Here's a list of the most important requirements for building a valid XHTML page:

  1. The page must include a valid XHTML DOCTYPE.

    A valid XHTML page must include an XHTML DOCTYPE before any of its content. When you create a new ASP.NET page in Visual Studio 2005 or Microsoft Visual Web Developer, the correct DOCTYPE for XHTML 1.0 Transitional is automatically included in the page. Here are the four standard XHTML DOCTYPES:

    XHTML 1.0 Transitional

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    

    XHTML 1.0 Strict

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    

    XHTML 1.0 Frameset

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
    

    XHTML 1.1

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" 
    "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    

    Adding a DOCTYPE to a page has an impact on how the page is rendered in a browser. See the section below entitled XHTML and DOCTYPE Switching.

  2. The root element must refer to the XHTML namespace

    The opening <html> tag of an XHTML page must specify a default namespace of http://www.w3.org/1999/xhtml. Here's a sample of a valid opening <html> tag for an XHTML 1.0 Transitional page.

    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    
  3. All element and attribute names must be lowercase.

    XML is case-sensitive. Therefore, there is a difference between the <p> tag and the <P> tag. Only the former is a valid XHTML paragraph tag.

  4. Attribute values must always be quoted.

    Always wrap attribute values in either double or single quotation marks. For example, the following is invalid XHTML.

    <a href=SomePage.aspx>Next</a> 
    

    In this case, the href attribute is missing quotation marks. The following is valid XHTML.

    <a href="SomePage.aspx">Next</a> 
    

    You can configure Visual Studio 2005 and Visual Web Developer to automatically quote attribute values, by selecting the menu option Tools, Options, Format.

  5. All non-empty elements that have an opening tag must have a matching closing tag.

    If you have an opening <p> tag, then you must include a closing </p> tag to mark the end of the paragraph. In the case of tags that never contain any content, such as the <br> tag, you can either supply a both an opening and closing <br></br> tag, or you can use the empty element shorthand <br />.

    In order to make your XHTML pages backward-compatible with existing HTML browsers, you need to be careful about how you open and close your tags. For example, existing HTML browsers tend to misinterpret an opening and closing <br></br> tag as two <br> elements. For that reason, you should use the empty element shorthand <br />.

    Furthermore, existing HTML browsers have problems with the empty element shorthand <br /> unless you are careful to add a space before the closing slash. So, you should add a <br> element to a page using <br [space] /> and not <br/>.

  6. There must be no overlapping tags.

    You can nest tags, but you are not allowed to overlap tags. For example, the following XHTML is valid.

    <b><i>This is bold and italic</i></b>
    

    However, the following XHTML is invalid.

    <i><b>This is bold and italic</i></b>
    
  1. There must be no attribute minimization.

    All attributes must have a value, even when it looks a little strange. For example, the tag <input type="checkbox" checked /> is invalid XHTML, because the checked attribute does not have a value. The tag should be written <input type="checkbox" checked="checked" />.

  2. The id attribute must be used instead of the name attribute.

    In HTML, you use the name attribute to identify <a>, <applet>, <form>, <frame>, <iframe>, <img>, and <map> elements. While you can use the name attribute when building XHTML 1.0 Transitional pages, the name attribute has been removed from the XHTML 1.0 Strict and XHTML 1.1 standards. You should use the id attribute to identify these elements instead.

  3. The contents of <script> and <style> elements must be wrapped in CDATA sections.

    If you use special characters such as < or &, or entity references such as &lt; or &amp; in a script or style sheet, then you'll need to mark the contents of your script or style sheet as a CDATA (character data) section, as follows.

    <script type="text/javascript">
    <![CDATA[
    
    function isLess(a, b) {
    if (a < b)
        return true;
    }
    
    ]]>
    </script>
    

    Notice that the JavaScript function contained in the script includes a < character. If you do not wrap the script in a CDATA section, then the < character would be interpreted as marking the start of an XHTML tag.

    Using a CDATA section will not work with all browsers. For example, Internet Explorer considers a CDATA section in a <script> tag a syntax error. You can avoid this problem by adding JavaScript comments, as follows.

    <script type="text/javascript">
    /* <![CDATA[ */
    
    function isLess(a, b) {
    if (a < b)
        return true;
    }
    
    /* ]]> */
    </script>
    

    JavaScript uses /* and */ to mark the beginning and end of a comment. Therefore, the CDATA section is hidden from the JavaScript, but not from the browser that parses the page. In general, it is a better idea to place your style rules and scripts in external files and reference the files from your XHTML pages. Using external style sheets and scripts enables you to avoid all of these issues.

XHTML and ASP.NET Controls

Every ASP.NET control included in the ASP.NET 2.0 framework renders valid XHTML by default. In other words, you don't need to do anything special to generate valid XHTML markup when adding ASP.NET controls to a page. For example, if you add a GridView control to a page, the GridView control will generate valid XHTML markup.

Three points need to be clarified here. First, the source code of a page that contains ASP.NET controls will not validate as XHTML. When validating an ASP.NET page, you need to validate the rendered content of the page (everything that you see when you select View Source in Internet Explorer) and not the source of the page.

Second, there is nothing that prevents you from writing invalid XHTML when creating an ASP.NET page. You can, of course, add any tag to an ASP.NET page that you want. For example, if you add a <font> tag to your page, then your page will not validate as XHTML 1.0 Strict.

Finally, there are no guarantees when you use custom ASP.NET controls. If you buy a third-party ASP.NET control—for example, a super enhanced DataGrid control—the control may or may not render valid XHTML. It's the control vendor's responsibility to do the right thing.

Validating XHTML Pages

Visual Studio 2005 and Visual Web Developer automatically validate your Web pages as you build the pages. Validation problems are indicated in Source view by either green or red squiggles under the offending content. Red squiggles correspond to validation errors such as a missing closing tag. Green squiggles correspond to validation warnings such as the use of deprecated tags.

You can hover your mouse over any squiggle to view a ToolTip that contains the validation error or warning message (see Figure 1). Alternatively, you can view a list of validation errors and warnings in the Error List window (select View, Other Windows, Error List).

Click here for larger image.

Figure 1. Validating an XHTML document (Click the graphic for a larger image.)

By default, Visual Studio 2005 and Visual Web Developer are configured to validate pages against the Internet Explorer 6.0 schema. If you want to validate your pages against an XHTML schema, then you need to select one of the XHTML schemas from the drop-down list in the toolbar, or you can select Tools, Options, Validation to select a target schema.

As an alternative, you can validate your ASP.NET pages by using the W3C validation service. The W3C validation service enables you to validate a page by supplying a URL or by uploading the source of an XHTML page.

XHTML and DOCTYPE Switching

Specifying a DOCTYPE for a Web page impacts the way in which the page is rendered by a browser. Internet Explorer, Mozilla Firefox, and Opera all support a feature called DOCTYPE Switching (also called DOCTYPE Sniffing).

DOCTYPE Switching was introduced to enable browsers to render both standards-compliant and legacy Web sites correctly. Most Web sites were developed to render HTML pages and not XHTML pages. Browsers use the presence of a DOCTYPE to determine when a page should be rendered by using standards.

Internet Explorer 6+ supports two rendering modes, called Quirks mode and Standards mode. When Internet Explorer renders a page that contains a valid XHTML (or HTML 4.0) DOCTYPE, it renders the page in Standards mode; otherwise, it renders the page in Quirks mode (for details, see CSS Enhancements in Internet Explorer 6.

The Opera browser (Opera 7+) supports the same two rendering modes (Quirks and Standards) as Internet Explorer (for details, see http://www.opera.com/docs/specs/doctype/).

Mozilla Firefox 1+ supports three rendering modes: Quirks mode, Almost Standards mode, and Standards mode. Firefox's Almost Standards mode corresponds to Internet Explorer's and Opera's Standards mode. When a page contains a valid XHTML 1.0 Transitional DOCTYPE (and it is served with a text/html MIME type), Firefox renders the page in Almost Standards mode. When a page contains either an XHTML 1.0 Strict or XHTML 1.1 DOCTYPE (or the page is served with an XML MIME type), the page is rendered in Standards mode (for details, see http://www.mozilla.org/docs/web-developer/quirks/doctypes.html).

You can determine a browser's current rendering mode by temporarily adding the following client-side script to a page (this script works in the latest versions of Internet Explorer, Firefox, and Opera).

    <script type="text/javascript">
        alert( document.compatMode );
    </script>

You need to care about the browser rendering mode, because it affects the way in which Cascading Style Sheets are applied to the page. If you convert your existing HTML pages into XHTML pages, they might look very different when you open them in your browser.

For example, Internet Explorer calculates the size of page elements in different ways, depending on the rendering mode (it uses a different CSS Box Model). In Quirks mode, the width of an element is calculated by summing the width of the element's content, padding, borders, and margins. In Standards mode, the width of an element is calculated by taking into account only the width of the element's content.

For example, consider the following two <div> tags.

    
    <div style="width:400px;border:solid 1px black">
    First Box
    </div>
    
    <div style="width:400px;border:solid 1px black;padding:10px">
    Second Box
    </div>

The two <div> elements are the same, except for the second <div> element's additional padding. In Quirks mode (see Figure 2), the two <div> elements appear to be the same size, because the additional padding of the second <div> element is taken into account when calculating its width (the total width of both elements is 400px). In Standards mode (see Figure 3), the second <div> element appears wider than the first <div> element, because padding is not taken into account when calculating the width of an element (the total width of both elements is wider than 400px).

Aa479043.aspnetstd_fig02(en-us,MSDN.10).gif

Figure 2. Quirks mode

Aa479043.aspnetstd_fig03(en-us,MSDN.10).gif

Figure 3. Standards mode

This is only one example of browser differences in Quirks mode. In Quirks mode, each browser implements the W3C Cascading Style Sheet standards in significantly different ways. The beautiful thing about switching to Standards mode is that it forces almost all modern browsers to interpret the W3C standards in a very similar way (not exactly the same, but much better).

If you want your Web pages to appear in the same way across browsers, then it is a good idea to trigger Standards mode (in Internet Explorer and Opera) and Almost Standards mode (in Firefox), by including an XHTML 1.0 Transitional DOCTYPE. Fortunately, Visual Studio 2005 and Visual Web Developer automatically add this DOCTYPE, by default, to every new page ASP.NET page.

XHTML and MIME Types

When a Web browser requests a page from a Web server, the Web server serves the page with a certain MIME type (also called a Content type). For example, an HTML page is served with the text/html MIME type, a GIF image is served with an image/gif MIME type, and a Microsoft Word document is served with an application/msword MIME type.

A browser uses the MIME type to determine how a page (or other resource) should be handled. For instance, if a browser gets a file from a Web server that has a recognizable image MIME type, the browser attempts to interpret and render the file as an image. If a browser gets a file that has an application/msword MIME type, the browser might automatically open Microsoft Word to display the document (the exact behavior here depends on the browser and how it is configured).

The W3C has introduced a MIME type for XHTML documents. This new MIME type is application/xhtml+xml. The W3C recommends that you use the application/xhtml+xml MIME type when serving XHTML documents, because XHTML pages should be interpreted in a stricter way than legacy HTML pages.

You can serve an ASP.NET page with a particular MIME type by including the ContentType attribute in a page directive. For example, including the following directive at the top of an ASP.NET page causes the page to be served as application/xhtml+xml.

<%@ ContentType="application/xhtml+xml" %>

There is one glaring problem with the W3C's recommendation: not all browsers recognize application/xhtml+xml. In particular, Internet Explorer (the most popular Web browser in the history of the world) does not recognize the application/xhtml+xml MIME type. Therefore, serving your XHTML pages using the recommended application/xhtml+xml MIME type is not a viable option.

There are three ways that you can work around this problem. You can serve your XHTML pages by using the text/html MIME type, you can serve your XHTML pages by using the application/xml (or text/xml) MIME type, or you can use content negotiation. Let's explore each of these options.

The first option, serving your pages as text/html, is the easiest option. An ASP.NET page is served with this MIME type by default. Better yet, the W3C recommends this option when serving pages to existing HTML browsers (see http://www.w3.org/TR/xhtml-media-types/). If you are creating XHTML 1.0 Transitional pages, and the primary audience for your Web application is using a browser that does not understand the application/xhtml+xml MIME type, then serving your pages as text/html seems perfectly sensible. After all, the XHTML 1.0 Transitional standard was introduced to make it easier for developers to migrate existing HTML pages to XHTML.

This claim is controversial. For example, Ian Hickson argues that XHTML pages should never be served as text/html, because this option promotes sloppy, broken XHTML pages (see http://hixie.ch/advocacy/xhtml). He recommends that authors stick to HTML 4.0 until more browsers completely support XHTML standards.

The second option is to serve your XHTML pages as XML, using either the application/xml or text/xml MIME type. When Internet Explorer is served an XML document, the document is parsed as an XML document and rendered to the browser. (The document is represented by the XML DOM exposed by the document.XMLDocument object.)

The advantage of serving an XHTML document as XML is that any problems with the XHTML document will be caught by Internet Explorer's XML parser. For example, if your document contains overlapping tags, or if the value of an attribute is not wrapped in quotation marks, then the document is not rendered, and an error message is displayed (see Figure 4). XHTML purists consider this behavior a good thing, because it prevents you from writing malformed XHTML.

Aa479043.aspnetstd_fig04(en-us,MSDN.10).gif

Figure 4. Displaying XML in Internet Explorer

The problem with this approach is that Internet Explorer, by default, renders the source of an XML document. So, if you serve an XHTML document as XML, your Web site visitors will see the source of your XHTML documents and not the desired rendered output. The W3C suggests a "trick" for getting around this problem (see http://www.w3.org/MarkUp/2004/xhtml-faq#ie): If you transform an XHTML document into HTML by using an XSLT transformation, then your document will be parsed as XML and displayed as HTML.

For example, the ASP.NET page in Listing 1 will be served as an XML document but transformed into an HTML document. The resulting page displays correctly in Internet Explorer, Opera, and Firefox.

Listing 1. XMLPage.aspx

<%@ Page Language="VB" ContentType="text/xml" %>
<?xml-stylesheet type="text/xsl" href="copy.xsl"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>My Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
    <asp:TextBox ID="txtFirstName" runat="server" />
    
    </div>
    </form>
</body>
</html>

The page directive causes this page to be rendered as text/xml. The second line in the listing refers to an XSLT style sheet, named copy.xsl, that performs an identity transformation on the current document. In other words, it does absolutely nothing, except copy all of the elements from the original XML document into a new HTML document. The source for copy.xsl is contained in Listing 2.

Listing 2. Copy.xsl

<stylesheet version="1.0"
     xmlns="http://www.w3.org/1999/XSL/Transform">
  <template match="/">
    <copy-of select="."/>
  </template>
</stylesheet>

This solution works, but it doesn't seem very elegant. You do get the extra validation step when the XML document is parsed. However, if you are building your ASP.NET pages in Visual Studio 2005 or Visual Web Developer, the same validation is performed by the development environment in Source view. At the end of the day, Internet Explorer receives the same document as it would get if you had sent it text/html.

The third option, content negotiation, best combines the spirit of the W3C recommendations with the greatest degree of browser compatibility (see http://www.w3.org/2003/01/xhtml-mimetype/content-negotiation). When you use content negotiation, you serve an ASP.NET page with different MIME types to different browsers. If a browser claims that it supports XHTML, then you serve it XHTML; otherwise, you serve the browser the page with the text/html MIME type.

The Global.asax in Listing 3 contains the necessary code for serving different MIME types to different browsers. If you add this file to your Web project, then the MIME type of every ASP.NET page will be modified with each request. When a page is served to Firefox or Opera, the page will be served as application/xhtml+xml. Internet Explorer 6, on the other hand, will receive text/html pages.

Listing 3. Global.asax

<script runat="server">

    Sub Application_PreSendRequestHeaders(ByVal s As Object, _
      ByVal e As EventArgs)
        If Array.IndexOf(Request.AcceptTypes, _
          "application/xhtml+xml") > -1 Then
            Response.ContentType = "application/xhtml+xml"
        End If
    End Sub

</script>

Configuring XHTML Conformance

The default behavior of the ASP.NET 2.0 framework is to render pages that validate against XHTML 1.0 Transitional. Most developers building Web sites will want to target this standard, because it is the standard that is the most compatible with existing HTML pages. However, there are situations in which this standard might be either too lax or too strict.

For example, if you are feeling ambitious, you might decide to build an XHTML 1.0 Strict, or even an XHTML 1.1, Web site. After all, the goal of the XHTML 1.0 Transitional standard is to act as a springboard to these more restrictive standards. Because, by default, the ASP.NET 2.0 framework targets XHTML 1.0 Transitional, some of the ASP.NET controls will render attributes that are not compatible with XHTML 1.0 Strict or XHTML 1.1.

Alternatively, you might discover that the XHTML 1.0 Transitional standard is too restrictive. Microsoft had to make several changes to existing ASP.NET 1.1 controls in order to comply with the XHTML 1.0 Transitional standard. Some of these changes might break an existing ASP.NET 1.1 Web site.

In order to keep everyone happy, Microsoft created a new configuration option, named xhtmlConformance, that you can set in your Web site's configuration file. The new configuration option enables you to specify the level of XHTML conformance of your Web pages. It looks like this.

    <configuration>
<system.web>
    <xhtmlConformance 
        mode="transitional" />
</system.web>
</configuration>

By default, xhtmlConformance is set to the value transitional. However, you can also set this option to the value strict or legacy.

If you set the xhtmlConformance option to strict, then certain attributes will no longer be rendered by the standard ASP.NET controls. For example, the ASP.NET <form> control will no longer render a name attribute. Unless your ASP.NET pages contain (non-standards-compliant) client-side scripts, you won't notice any changes when switching from transitional to strict mode.

If you set the xhtmlConformance option to legacy, then the ASP.NET framework will revert to ASP.NET 1.1 rendering behavior for some elements and attributes (but not all). In this case, the ASP.NET framework will render content that is not compatible with any XHTML standard, and your pages will no longer validate against the XHTML standards. For example, in legacy mode, the <br> tag is not rendered with its required XHTML closing slash (<br />). Setting xhtmlConformance to legacy mode only makes sense when you run into a problem migrating an existing ASP.NET 1.1 application to ASP.NET 2.0.

Building Accessible ASP.NET Web Sites

The benefit of following public Web standards is that they make your Web pages accessible to the greatest number of people with the least amount of work. In particular, accessibility standards enable you to build Web sites that can be more easily accessed by persons with disabilities.

It is worth emphasizing, once again, that a broad audience of Web site users has one form of disability or another. Think of the members of your own family and consider how many of them would have trouble interacting with a Web page. I have aging relatives who are blind or who are losing their motor coordination. My guess is that many readers of this paper also have aging parents or grandparents who would find it challenging to use most Web sites.

There are many good reasons for building accessible Web sites: financial, moral, legal, and so on. Let's concentrate, however, on the legal motivations. In the United States, any Web site developed by a federal agency is required by Section 508 of the Rehabilitation Act to be accessible to persons with disabilities. This law applies to federal agencies and companies that contract with federal agencies (see http://www.section508.gov).

Other countries have similar requirements. For example, in Canada, the Treasury Board Common Look and Feel Standards require that Web sites developed by federal agencies be accessible. In Australia, the Disability Discrimination Act requires that all Web sites hosted on Australian servers (regardless of whether or not it is a government Web site) be accessible. (For more details on accessibility laws, see http://www.w3.org/WAI/Policy.)

I don't know any Web site developer who would intentionally build a Web site that is not accessible to persons with disabilities. The problem is that most developers are not familiar with the various accessibility standards.

In the following sections of this paper, you'll be provided with an overview of the two most important accessibility standards: the WCAG and Section 508 standards You'll also learn how to build accessible Web pages by using ASP.NET controls. Finally, you'll learn how to "validate" your Web pages for accessibility.

Accessibility Standards

Almost all accessibility standards and laws derive from the W3C Web Content Accessibility 1.0 Guidelines (WCAG). These guidelines were first published by the World Wide Web Consortium as a recommendation on May 5, 1999 (see http://www.w3.org/TR/WCAG10).

The WCAG consists of 14 guidelines. Each guideline, in turn, consists of one or more checkpoints that further clarify the guideline. Each checkpoint is ranked with a priority between 1 and 3. To make it easier to implement the guidelines, the W3C has published a set of documents that contain techniques for following the guidelines (see http://www.w3.org/TR/WCAG10-TECHS/).

You can claim different levels of conformance with the WCAG guidelines. If you claim that your Web site satisfies all priority 1 checkpoints, then you can display a logo that claims Conformance Level A. When a Web site meets all priority 1 and 2 checkpoints, the Web site can display a logo for Conformance Level Double-A. Finally, a Web site that satisfies all checkpoints can display the logo for Conformance Level Triple-A (see http://www.w3.org/WAI/WCAG1-Conformance.html).

The Section 508 guidelines derive from the WCAG guidelines. In the United States, federal agencies (and companies who contract with federal agencies) need to be most concerned with this set of guidelines, because these guidelines have the force of law. You can read the complete text of the Section 508 guidelines at the Section 508 Web site.

The ASP.NET 2.0 framework was designed to enable you to meet all WCAG priority 1 and priority 2 checkpoints, and all Section 508 guidelines. These guidelines were taken very seriously. Every developer working on the ASP.NET 2.0 framework was required to review and test every ASP.NET control for accessibility. Furthermore, every developer had a screen reader installed on his or her desktop so that pages could be tested against the guidelines.

Accessibility Improvements in ASP.NET 2.0

This paper focuses on six areas of accessibility improvements in the ASP.NET 2.0 framework. In the following sections, you learn how to use ASP.NET controls to display accessible images, forms, navigation, data, and XHTML. At the end of this section, we'll also consider accessibility issues related to using client-side scripts in ASP.NET pages.

Creating Accessible Images

You should not assume that everyone who interacts with your Web site can actually see your Web site. If someone is blind or has low vision, that person might need to use either a screen reader or a Braille display to visit your Web pages. A screen reader reads the text in your Web pages by using a speech synthesizer. A Braille display transforms the text in your pages into a Braille representation.

Images and other non-text page elements, such as Java, Shockwave, and Flash content, is useless content for someone who cannot see. If you want to make your Web site accessible to people who have low-vision or who are blind, then you need to provide text equivalents for all non-textual content in your Web pages.

Each and every image in a Web page should include an alt attribute. The alt attribute is used to represent alternate text read by a screen reader or other assistive device. Here's how you use the alt attribute.

    <img src="Products23.gif" alt="Image of Products" />

The alt attribute should contain a description of the image. It should never, under any circumstances, simply contain the filename of the image. The purpose of the alt attribute is to convey the same information to someone who is blind as the image conveys to someone who is sighted. Writing the value of an alt attribute requires human interpretation of the meaning of the element. For this reason, the process of creating alt attributes cannot be automated.

Every ASP.NET control that displays an image includes a method for supplying alternate text for the image. For example, the ASP.NET Image control includes an AlternateText property. If you use an Image control, then you need to set the AlternateText attribute to a meaningful value.

<asp:Image ImageUrl="Products23.gif" 
  AlternateText="Image of Products" Runat="Server" /> 

If an image is used only as a design element, then you should set its alt attribute to an empty string. If an image has no useful information to convey, then there is no reason to clutter up a screen reader's narration of the page.

<img src="PageDivider.gif" alt="" />   

Special measures had to be taken in the ASP.NET 2.0 framework to enable you to render empty AlternateText. If you assign empty text to an attribute of an ASP.NET control, then the ASP.NET control will not render the attribute at all. For example, imagine that you add the following ASP.NET Image control to a page.

<asp:Image ImageUrl="PageDivider.gif" AlternateText="" 
  Runat="Server" />

In this case, the following tag is rendered.

<img src="PageDivider.gif" style="border-width:0px;" />    

Notice that the alt attribute has disappeared. This is the default behavior of all ASP.NET control attributes. When you do not assign an attribute a value, it is not rendered. Unfortunately, in this case, we really want to render an empty value for the alt attribute.

To work around this problem, a new property was introduced into the ASP.NET 2.0 framework to enable you to display empty alternate text with an Image control: the GenerateEmptyAlternateText property.

<asp:Image ImageUrl="PageDivider.gif" 
    GenerateEmptyAlternateText="true" Runat="Server" />

If you use the GenerateEmptyAlternateText property, then an alt="" attribute is correctly rendered.

When an image represents something truly complicated, such as an organizational chart, then you cannot use the alt attribute to provide an alternate text description. When you need to provide a long description of the meaning of an image, then you need to use the longdesc attribute.

The longdesc attribute accepts either a relative or absolute URL for its value. The URL should link to a page that contains a textual description of the contents of the image. Here's a sample of how you can use this attribute with the <img> tag.

<img src="OrgChart.gif" alt="Company Organization Chart" 
  longdesc="/OrgChartDescription.aspx" />

The ASP.NET Image control includes a property, named DescriptionUrl, that corresponds to the HTML longdesc attribute. Here's a sample of how you can use this property.

<asp:Image 
    ImageUrl="OrgChart.gif" 
    AlternateText="Company Organization Chart" 
    DescriptionUrl="/OrgChartDescription.aspx" 
    Runat="server" />

Creating Accessible Forms

Web page forms can create problems for persons with low vision and for persons with reduced motor coordination. If you access a Web page form through a screen reader, then it might be difficult to associate form fields with their corresponding labels. For example, imagine that a Web page contains the following form.

    <table>
    <tr>
        <td>First Name:</td>
        <td><input name="txtFirstName" /></td>
    </tr>
    <tr>
        <td>Last Name:</td>
        <td><input name="txtLastName" /></td>
    </tr>
    </table>

This form displays input fields for a person's first name and last name. In this case, because the form is displayed in a table, it might be difficult for a user of a screen reader to associate the proper label with the proper form field. In HTML 4.0, a new tag was introduced to enable you to associate a form field label with a form field: the <label> tag. Here's how the previous form should be written using a <label> tag.

    <table>
    <tr>
        <td><label for="txtFirstName">First Name:</label></td>
        <td><input name="txtFirstName" id="txtFirstName" /></td>
    </tr>
    <tr>
        <td><label for="txtLastName">Last Name:</label></td>
        <td><input name="txtLastName" id="txtLastName" /></td>
    </tr>
    </table> 

The <label> tag explicitly associates the form field labels with their corresponding form fields. Notice that the <input> fields include an id attribute, because the value of the for attribute must be an input field's id and not its name attribute.

Normally, the ASP.NET Label control generates a <span> tag. However, if you provide an AssociatedControlId property when declaring an ASP.NET Label control, then the control renders a <label> tag. Here's how you can generate an accessible form with ASP.NET Label and TextBox controls.

    <table>
    <tr>
        <td><asp:Label AssociatedControlID="txtFirstName" 
          runat="server">First Name:</asp:Label></td>
        <td><asp:TextBox ID="txtFirstName" runat="server" /></td>
    </tr>
    <tr>
        <td><asp:Label AssociatedControlID="txtLastName" 
          runat="server">Last Name:</asp:Label></td>
        <td><asp:TextBox ID="txtLastName" runat="server" /></td>
    </tr>
    </table>

When providing a label for an ASP.NET control, you should use the ASP.NET Label control instead of the HTML <label> tag. When you assign an ID to an ASP.NET control such as the TextBox control, the ID that is rendered to the browser might be a different ID than the ID that you assigned to the control. Therefore, if you use a <label> tag, the ID in the <label> tag might not match the ID of the rendered TextBox control. If, on the other hand, you use the ASP.NET Label control, you don't have to worry about this issue.

The ASP.NET CheckBox, RadioButton, CheckBoxList, and RadioButtonList controls automatically render <label> tags. Be careful, when using these controls, to use the Text attribute to label the text of the control. You should not do the following.

    <asp:CheckBox Runat="Server" /> Include Gift Wrap

Instead, do the following.

    <asp:CheckBox Text="Include Gift Wrap" Runat="Server" />  

Large forms can also create problems for individuals interacting with a Web page through a screen reader. When listening to a large form, it is easy to lose track of the section of the form that you are listening to. When displaying a large form, it is a good idea to divide the form into bite-sized chunks. You can divide a single form into multiple sections by using the <fieldset> tag. Here's a sample of how you can use this tag.

    <form id="form1" runat="server">
    <div>
 
    <fieldset>
    <legend>Contact Information</legend>
    
    ... form fields
    
    </fieldset>
 
    <fieldset>
    <legend>Payment Information</legend>
    
    ... form fields
    
    </fieldset>
    
    </div>
    </form>

This form is divided into two subforms, using the <fieldset> tag. The <legend> tag is used to label the purpose of the subforms. When displayed in Internet Explorer, Firefox, and Opera, the subforms are visually divided into separate areas by a border (see Figure 5). However, it is important to keep in mind that the primary purpose of the <fieldset> tag is accessibility. If you don't like the visual appearance of the <fieldset> tag, then you can modify the appearance of the tag through a style sheet rule, or you can completely hide the tag by using the CSS display or visibility attribute.

Aa479043.aspnetstd_fig05(en-us,MSDN.10).gif

Figure 5. The <fieldset> tag

People with low vision are not the only users of a Web page who might find a Web form challenging. Individuals who have reduced motor coordination can also experience difficulty when interacting with a form.

When building a Web form, it is always a good idea to include accesskey and tabindex attributes for each of the form fields. The accesskey attribute enables someone who cannot use a mouse to navigate directly to any form field. The tabindex attribute enables you to control the tabbing order of the form fields. Both attributes make life easier for someone who must interact with your page through a keyboard (or an assistive device that acts like a keyboard).

Here's a sample form that uses both the accesskey and tabindex attributes.

    <asp:Label 
        AssociatedControlID="txtFirstName" 
        AccessKey="f"
        runat="server"><u>F</u>irst Name</asp:Label>
    <asp:TextBox
        id="txtFirstName"
        TabIndex="1" 
        Runat="server" />
    
    <br />
    <asp:Label 
        AssociatedControlID="txtLastName" 
        AccessKey="l"
        runat="server"><u>L</u>ast Name</asp:Label>
    <asp:TextBox
        id="txtLastName"
        TabIndex="2" 
        Runat="server" />

The tabindex attribute is used to control the tab order of the form fields. Because the first form field has a tabindex value of 1, any other elements in the page that appear before the form are skipped when the user first hits the TAB key.

When using Internet Explorer or Firefox, pressing ALT+F automatically moves focus to the First Name text box. If you press ALT+L, then focus is automatically moved to the Last Name text box. When using Opera, you must first press SHIFT+ESC before selecting an access key.

Notice that the first letter of both the First Name and Last Name labels are underlined. Underlining the letter provides the user of the Web site with a visual indication of the access keys. This is the standard way to mark access keys in Microsoft Windows applications. However, there are other proposed methods for indicating access keys in a form (see http://www.cs.tut.fi/~jkorpela/forms/accesskey.html).

One problem with using underlines to indicate access keys is the fact that you cannot underline characters in a button, and hyperlinks are already underlined. For example, the following Button control does not work as you would expect and hope.

    
    <asp:Button
        Text="<u>S</u>ubmit"
        Runat="server" /> 

When this ASP.NET Button control is rendered, the actual text <u>S</u>ubmit is displayed, instead of an underlined S character. The ASP.NET Button control renders an HTML <input type="submit"> tag, and, unfortunately, the <input type="submit"> tag does not support underlining.

You might think that you could get around this problem by using a style rule. Unfortunately, there currently is no cross-browser compatible method of underlining a single character in an <input type="submit"> tag using Cascading Style Sheets.

You can get around this problem if you are willing to use client-side JavaScript in the page. The page in Listing 4 contains JavaScript that displays or hides all of the access keys, depending on whether the ALT key is held down. When you hold down the ALT key, boxes pop up, displaying the access key keyboard combinations (see Figure 6). This script works in both Internet Explorer and Firefox (Opera does not use the ALT key to select access keys).

Aa479043.aspnetstd_fig06(en-us,MSDN.10).gif

Figure 6. AccessKeys.aspx

Listing 4. AccessKeys.aspx

<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Contact Form</title>


<style type="text/css">
    .accessKey 
    {
        display:none;
        position:absolute;
        z-index:5000;
        padding:3px;
        border:solid 1px black;
        background-color: #ffffe0
    }
</style>

<script type="text/javascript">
/* <![CDATA[ */

    window.onload = function() 
        {
            document.onkeydown = displayAccessKeys;
        }

    function displayAccessKeys(e)
    {
        if (!e) e = window.event;
        if (e.keyCode == 18)
        {
            toggleAccessKeys();
            document.onkeydown = null;
            document.onkeyup = hideAccessKeys;
        }
    }

    function hideAccessKeys(e)
    {
        if (!e) e = window.event;
        if (e.keyCode == 18)
        {
            toggleAccessKeys();
            document.onkeyup = null;
            document.onkeydown = displayAccessKeys;
        }
    }


    function toggleAccessKeys()
    {
        var spans = document.getElementsByTagName('span');
        for (var k=0;k<spans.length;k++)
            if (spans[k].className == 'accessKey' )
            {
                if ( 'inline' != spans[k].style.display)
                    spans[k].style.display = 'inline';
                else
                    spans[k].style.display = 'none';    
            }
    }
    
/* ]]> */
</script>

</head>
<body>
    <form id="form1" runat="server">
    <div>

    <table>
    <tr>
        <td>
            <asp:Label 
                ID="lblFirstName"
                AssociatedControlID="txtFirstName" 
                AccessKey="f"
                runat="server">First Name</asp:Label>
        </td>
        <td>
            <asp:TextBox ID="txtFirstName" runat="server" />
            <span class="accessKey">access key is f</span>
        </td>
    </tr>
    <tr>
        <td>
            <asp:Label 
                ID="lblLastName" 
                AssociatedControlID="txtLastName"
                AccessKey="l" 
                runat="server">Last Name:</asp:Label>
        </td>
        <td>
            <asp:TextBox ID="txtLastName" runat="server" />
            <span class="accessKey">access key is l</span>
        </td>
    </tr>
    <tr>
        <td colspan="2">
        <asp:Button Text="Submit" runat="server" />
        <span class="accessKey">access key is s</span>
        </td>
    </tr>
    </table>

    </div>
    </form>
</body>
</html>

The page in Listing 4 contains a style sheet and client-side JavaScript. The style sheet hides the contents of any <span> tag identified by the accessKey class. The JavaScript detects when the ALT key has been pressed, and reveals the contents of the <span> tags.

Note that this page will function even when style sheets and JavaScript are disabled on a Web browser. In that case, the access key help will always be displayed (see Figure 7).

Aa479043.aspnetstd_fig07(en-us,MSDN.10).gif

Figure 7. AccessKeys.aspx degrading gracefully

Creating Accessible Navigation

I hate calling customer support numbers and following the automated systems. I feel myself slowly age as the computer voice announces each and every option in its droning voice. If you press one wrong key, you end up lost forever in the depths of the automated computer system.

Unfortunately, if you are forced to use a screen reader, this is precisely your experience when you visit almost any Web page. Most Web sites include on every page a navigation bar that contains a list of links to the various sections of the Web site. If you are using a screen reader, then you must listen to each of these navigation links, one at a time, whenever you open a page.

With one simple modification to a navigation bar, you can dramatically improve the accessibility of your Web pages. You simply need to add a method for someone to skip all of the navigation links. You can do this with a "Skip Navigation link."

For example, the CNN Web site includes a navigation bar that lists the different sections of the CNN Web site (World, U.S., Weather, and so on). However, the designers of the CNN Web site have done something smart. If you view the source of the page, you'll notice that the following link appears above the navigation bar.

<a href="#ContentArea"><img src="http://i.cnn.net/cnn/images/1.gif" alt="Click here to skip to main content." width="10" height="1" border="0" align="right"></a>

When you view the home page of the CNN Web site, you never see this link. The image contained in the link is a transparent single-pixel image. However, if you access this page with a screen reader, then the alternate text associated with the image is read. A person who is blind can choose to skip all of the navigation links and move directly to the main content area of the Web page (The equivalent of pressing 0 in an automated voice system and navigating directly to the operator).

Skip Navigation links have been integrated into several of the standard ASP.NET 2.0 controls. In particular, the Menu, TreeView, SiteMapPath, Wizard, and CreateUserWizard controls all support Skip Navigation links.

For example, the page in Listing 5 includes an ASP.NET Menu control. This control is used to display a list of links to other pages in the Web site.

Listing 5. SiteMenu.aspx

<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Skip Navigation</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
    <asp:Menu 
        id="Menu1"
        Runat="server">
        <Items>
            <asp:MenuItem Text="Home" NavigateUrl="Home.aspx" />
            <asp:MenuItem Text="Products" NavigateUrl="Products.aspx" />
            <asp:MenuItem Text="Services" NavigateUrl="Services.aspx" />
            <asp:MenuItem Text="About" NavigateUrl="About.aspx" />
        </Items>
    </asp:Menu>
    
    <hr />
    
    Here is the main content of the page...
    
    </div>
    </form>
</body>
</html>

If you view the source of the page in Listing 5, you'll see that the following link appears at the top of the menu.

<a href="#Menu1_SkipLink"><img alt="Skip Navigation Links" 
src="/WebResource.axd?d=ChXz41GuDxNm-7TcWyCl_w2&amp;t=632495684475122400" 
  width="0" height="0" style="border-width:0px;" />

This link contains a zero-width and zero-height image that does not appear when you view the page. However, someone accessing this page through a screen reader can select the Skip Navigation link to skip to the end of the menu.

By default, the Skip Navigation link contains the text Skip Navigation Links. You can modify this value by changing the Menu control's SkipLinkText property.

Creating Accessible Data

The ASP.NET 2.0 framework includes a rich set of controls for displaying database data. These controls include the GridView, DetailsView, DataList, FormView, and Repeater controls. By default, the GridView, DetailsView, and DataList controls display database records in an HTML table.

Presenting information in HTML tables, if not done right, can create accessibility problems. When the content of an HTML table is read aloud, you can easily lose track of your current position in the table. For example, imagine that you use an HTML table to display a list of product information. When the content of the table is read by a screen reader, you can easily get confused about whether a certain table cell represents information about the product name, the number of products on order, or a code for the warehouse that stores the products.

When you look at an HTML table, you can determine the meaning of a particular cell by glancing at either the column or row heading. In order to make tables accessible to persons who are using screen readers, you need to explicitly mark the table headings, and explicitly associate the headings with each cell.

When you create a table to display data, you should always use the proper tags to mark the column and row headings. A table heading should always be marked with the <th> tag, as follows.

    <table>
    <thead>
    <tr>
        <th>Product Name</th>
        <th>Price</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <td>Milk</td>
        <td>$2.33</td>
    </tr>
    <tr>
        <td>Cereal</td>
        <td>$5.61</td>
    </tr>  
    </tbody>      
    </table>

In this example, the <th> tag is used to mark the two column headings: Product Name and Price.

Some designers avoid using the <th> tag because they do not like the default visual appearance of it. In most browsers, the contents of a <th> tag are centered and bolded. However, it is important to remember that tags should never be used to control presentation. If you want the column headings to look like normal table cells, then you should add a style rule such as the following.

    <style type="text/css">
        th {text-align:left;font-weight:normal}
    </style>

In order to make a table accessible, you should also explicitly indicate the heading or headings associated with each cell. There are several attributes that you can use for this purpose: scope, headers, and axis.

The scope attribute can be used to indicate whether a table heading is a column heading or a row heading. For example, the following table contains both column headings and row headings, marked with <th> tags that use the scope attribute.

    <table>
    <thead>
    <tr>
        <th></th>
        <th scope="col">First Train</th>
        <th scope="col">Last Train</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <th scope="row">Alewife</th>
        <td>5:24am</td>
        <td>12:15am</td>
    </tr>
    <tr>
        <th scope="row">Braintree</th>
        <td>5:15am</td>
        <td>12:18am</td>
    </tr>  
    </tbody>      
    </table>

This table contains the schedule for the Boston subway Red Line (see Figure 8). Notice that each of the column headings include a scope="col" attribute, and each of the row headings include a scope="row" attribute.

Aa479043.aspnetstd_fig08(en-us,MSDN.10).gif

Figure 8. Simple subway schedule

The scope attribute works great for simple tables. However, in the case of more complicated tables, you need to use the headers attribute. For example, a nested table might have three or more headings associated with a single cell. The headers attribute enables you to mark each cell with its associated headings.

The axis attribute enables you to categorize a table heading. For example, in the subway schedule table, the attribute axis="location" could be added to each heading that represents a location (the Alewife and Braintree headings). The axis attribute accepts a comma delimited list of categories.

The page in Listing 6 contains a more complicated version of the Boston subway schedule that uses both the headers and axis attributes (see Figure 9).

Aa479043.aspnetstd_fig09(en-us,MSDN.10).gif

Figure 9. Complicated subway schedule

Listing 6. Subway.aspx

<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Red Line Subway Schedule</title>

    <style type="text/css">
        caption {color:white;background-color:red;font-size:xx-large}
        table {width:500px;border-collapse:collapse}
        td,th {padding:5px}
        td {border:1px solid black}
        tbody th {text-align:right}
        .headerRow th {font-size:x-large;text-align:left}    
    </style>

</head>
<body>
    <form id="form1" runat="server">
    <div>

    <table 
      summary="This table contains the schedule of train 
                departures for the Red Line">
    <caption>Red Line Schedule</caption>
    <thead>
    <tr>
        <th></th>
        <th id="hdrFirstTrain" axis="train">First Train</th>
        <th id="hdrLastTrain" axis="train">Last Train</th>
    </tr>
    </thead>
    <tbody>
    <tr class="headerRow">
        <th id="hdrWeekday" axis="day" colspan="3">Weekday</th>
    </tr>
    <tr>
        <th id="hdrAlewife1" axis="location">Alewife</th>
        <td headers="hdrAlwife1 hdrWeekday hdrFirstTrain">5:24am</td>
        <td headers="hdrAlwife1 hdrWeekday hdrLastTrain">12:15am</td>
    </tr>
    <tr>
        <th id="hdrBraintree1" axis="location">Braintree</th>
        <td headers="hdrBraintree1 hdrWeekday hdrFirstTrain">5:15am</td>
        <td headers="hdrBraintree1 hdrWeekday hdrLastTrain">12:18am</td>
    </tr>  
    <tr class="headerRow">
        <th id="hdrSaturday" axis="day" colspan="3">Saturday</th>
    </tr>
    <tr>
        <th id="hdrAlewife2" axis="location">Alewife</th>
        <td headers="hdrAlewife2 hdrSaturday hdrFirstTrain">8:24am</td>
        <td headers="hdrAlewife2 hdrSaturday hdrLastTrain">11:15pm</td>
    </tr>
    <tr>
        <th id="hdrBraintree2" axis="location">Braintree</th>
        <td 
          headers="hdrBraintree2 hdrSaturday hdrFirstTrain">7:16am</td>
        <td 
          headers="hdrBraintree2 hdrSaturday hdrLastTrain">10:18pm</td>
    </tr>  
    </tbody>      
    </table>

    </div>
    </form>
</body>
</html>

Notice that each table cell contains a headers attribute. The headers attribute represents a space delimited list of IDs that correspond to column and row headings. Each cell in the subway schedule table has an associated location, day, and train heading.

Also, notice that each <th> tag has an axis attribute that is used to represent the category associated with the heading. For example, the Weekday and Saturday headings are both associated with the day axis. The First Train and Last Train headings are associated with the train axis.

Finally, notice that the table in Listing 6 contains both a summary attribute and a <caption> tag. The summary attribute works very much like the alt attribute. You can use the summary attribute to provide a description of the table that is not rendered by the browser. The contents of the <caption> tag, on the other hand, are rendered by the browser. You should use the <caption> tag to label the purpose of a table.

If you use the ASP.NET 2.0 GridView or DetailsView controls to display database data in an HTML table, then the generated HTML table is accessible by default. For example, Listing 7 contains an ASP.NET page that displays the contents of the Titles database table by using a GridView control.

Listing 7. DisplayTitles.aspx

<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Display Titles</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
    <asp:GridView
        id="grdTitles"
        DataSourceId="srcTitles"
        Runat="server" />

    <asp:SqlDataSource
        id="srcTitles"
        ConnectionString=
         "Server=localhost;Trusted_Connection=true;Database=Pubs"
        SelectCommand="Select * FROM Titles"
        Runat="server" />
   
    </div>
    </form>
</body>
</html>

In Listing 7, the GridView control is bound to a SqlDataSource control that represents the records from the Titles database table. When the ASP.NET page in Listing 7 is opened in a browser, the contents of the Titles database table is displayed in an HTML table (see Figure 10).

Click here for larger image.

Figure 10. DisplayTitles.aspx (Click the graphic for a larger image.)

Notice that the GridView control automatically generates <th> tags for each of the column headers. Furthermore, if you select View Source in your browser, you can see that scope="col" attributes are automatically generated for each column heading.

The GridView control supports several additional properties relevant to accessibility:

  • Caption and CaptionAlign—Use these properties to add a caption to the HTML table generated by the GridView control.
  • RowHeaderColumn—Use this property to indicate a row header (as opposed to a column header). Set this property to the name of a column returned from the data source (such as title_id).
  • UseAccessibleHeader—Use this property to indicate whether column headings should be rendered with <th scope="col"> tags or <td> tags. By default, this property has the value true.

Notice that the GridView control does not have a Summary property. However, like most ASP.NET controls, the GridView control supports expando attributes. You can declare any attribute you please when you declare the GridView control, and the attribute will be rendered to the browser. So, if you want to add a summary to a GridView, declare the summary attribute as follows.

    <asp:GridView
        id="grdTitles"
        DataSourceId="srcTitles"
        summary="Displays the contents of the Titles database table"
        Runat="server" />

The default behavior of the GridView control is great for displaying a simple table of data in an accessible manner. However, if you need to display a more complicated table, such as a set of nested tables, then you must perform additional work.

Imagine, for example, that you want to display a list of product categories and, under each category, you want to display a list of matching products. In other words, you want to create a single page Master/Detail form (see Figure 11). In that case, you'll need to include the headers attribute for each table cell.

Aa479043.aspnetstd_fig11(en-us,MSDN.10).gif

Figure 11. Nested Repeater controls

The page in Listing 8 illustrates how you can nest one Repeater control in a second Repeater control and generate a complicated table that meets the requirements of the accessibility guidelines.

Listing 8. NestedRepeaters.aspx

<%@ Page Language="VB"%>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

    Private dtblProducts As New DataTable
    
    Sub Page_Load()
        Dim dad As New SqlDataAdapter("SELECT * FROM PRODUCTS", _
         "Server=localhost;Trusted_Connection=true;Database=Northwind")
        dad.Fill(dtblProducts)
    End Sub
    
    Function GetProducts(ByVal CategoryID As Integer) As DataView
        Dim view As DataView = dtblProducts.DefaultView
        view.RowFilter = "CategoryID=" & CategoryID
        Return view
    End Function
    
    Function GetCategoryHeader(ByVal index As Integer) As String
        Return "hdrCategory" & index.ToString()
    End Function
        
    Function GetProductHeader(ByVal productID As Integer) As String
        Return "hdrProduct" & productID.ToString()
    End Function
    
    Function GetHeaders(ByVal item As RepeaterItem, _
      ByVal columnHeader As String) As String
        Dim parent As RepeaterItem = _
         item.NamingContainer.NamingContainer
        Dim categoryHeader As String = _
         GetCategoryHeader(parent.ItemIndex)
        Dim productHeader As String = _
         GetProductHeader(item.DataItem("ProductID"))
        Return String.Format("{0} {1} {2}", categoryHeader, _
          productHeader, columnHeader)
    End Function
    
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
    <style type="text/css">
        .categoryRow {background-color:yellow}
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
    <asp:Repeater
        id="grdCategories"
        DataSourceId="srcCategories"
        Runat="server">
        <HeaderTemplate>
        <table>
            <thead>
                <th id="hdrID">ID</th>
                <th id="hdrName">Name</th>
                <th id="hdrPrice">Price</th>
            </thead>
            <tbody>
        </HeaderTemplate>
        <ItemTemplate>
            <tr class="categoryRow">
                <th colspan="3" 
                  id='<%# GetCategoryHeader(Container.ItemIndex) %>'>
                    <%# Eval("CategoryName") %>
                </th>
            </tr>
            <asp:Repeater
                id="grdProducts"
                DataSource='<%# GetProducts(Eval("CategoryID")) %>'
                Runat="server">
                <ItemTemplate>
                <tr>
                    <th 
                     id='<%# GetProductHeader(Eval("ProductID")) %>'>
                        <%# Eval("ProductID") %>
                    </th>
                    <td 
                     headers='<%# GetHeaders(Container, "hdrName") %>'>
                        <%#Eval("ProductName")%>
                    </td>
                    <td headers=
                      '<%# GetHeaders(Container, "hdrPrice") %>'>
                        <%#Eval("UnitPrice", "{0:c}")%>
                    </td>
                </tr>
                </ItemTemplate>
            </asp:Repeater>
        </ItemTemplate>
        <FooterTemplate>
            </tbody>
            </table>
        </FooterTemplate>
    </asp:Repeater>    
    
    <asp:SqlDataSource
        id="srcCategories"
        ConnectionString=
         "Server=localhost;Trusted_Connection=true;Database=Northwind"
        SelectCommand="SELECT * FROM Categories"
        Runat="server" />
    
    </div>
    </form>
</body>
</html>

In Listing 8, the outer Repeater control is used to list the product categories, and the inner Repeater control is used to list the matching products. Two helper functions are used to generate the id values for the Category Name and Product ID headers: the GetProductHeader and GetCategoryHeader functions. A separate helper function, named GetHeaders, is used to generate the values used with the headers attribute.

The ASP.NET page in Listing 8 generates an HTML table that looks like this.

        <table>
            <thead>
                <th id="hdrID">ID</th>
                <th id="hdrName">Name</th>
                <th id="hdrPrice">Price</th>
            </thead>
            <tbody>
        
            <tr class="categoryRow">
                <th colspan="3" id='hdrCategory0'>
                    Beverages
                </th>
            </tr>
            
                <tr>
                    <th id='hdrProduct1'>
                        1
                    </th>
                    <td headers='hdrCategory0 hdrProduct1 hdrName'>
                        Chai 2
                    </td>
                    <td headers='hdrCategory0 hdrProduct1 hdrPrice'>
                        $18.55
                    </td>
                </tr>
                
                <tr>
                    <th id='hdrProduct2'>
                        2
                    </th>
                    <td headers='hdrCategory0 hdrProduct2 hdrName'>
                        Chang
                    </td>
                    <td headers='hdrCategory0 hdrProduct2 hdrPrice'>
                        $19.00
                    </td>
                </tr>
                .... remainder of the table

Notice that each <td> tag contains a proper headers attribute.

Creating Accessible XHTML

One common theme that is shared by many of the accessibility guidelines is the idea that Web pages should be standards compliant in order to be accessible. According to the guidelines, you should strive to use the latest W3C standards, such as the latest versions of XHTML and Cascading Style Sheets, when building Web sites.

In particular, when designing Web pages, you should separate the structure of a document from its presentation. Use tags to represent the structure of your Web pages, and use Cascading Style Sheets to control the appearance of your Web pages.

For example, never use the <blockquote> element purely to indent a block of text. The purpose of the <blockquote> element is to create a citation for a source. If you want to indent text, you should use the Cascading Style Sheet margin attribute instead.

You should also strive to use <table> tags only when representing tables of data. While using <table> tags to layout a Web page is currently a common practice, try to use <div> tags instead. For example, the page in Listing 9 has a three-column layout, but does not contain a single <table> tag (see Figure 12).

Click here for larger image.

Figure 12. Tableless page layout (Click the graphic for a larger image.)

Listing 9. Tableless.aspx

<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Tableless Layout</title>
    <style type="text/css">
    #content
    {
        margin-left:auto;
        margin-right:auto;
        width:800px;
    }

    #leftColumn 
    {
        float:left;
        width:150px;
        border:1px solid black;
        padding:10px;
    }

    #middleColumn
    {
        float:left;
        width:430px;
        padding:10px;
    }

    #rightColumn 
    {
        float:right;
        width:150px;
        border:1px solid black;
        padding:10px;
    }
    </style>
</head>
<body>
    <form id="form1" runat="server">
   
    <div id="content">
   
    <div id="leftColumn">
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    </div>

    <div id="middleColumn">
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    </div>

    <div id="rightColumn">
    Right column contents...
    Right column contents...
    Right column contents...
    Right column contents...
    Right column contents...
    Right column contents...
    Right column contents...
    Right column contents...
    </div>

    </div>
    
    </form>
</body>
</html>

The page in Listing 9 contains four <div> tags. The first <div> tag, named content, is used to specify the width of the page's content area. The remaining three <div> tags—named left, middle, and right—divide the content area into three columns. This page displays correctly in Internet Explorer 6, Firefox, and Opera 8. (To view some really beautiful pages that do not use HTML tables for layout, see http://csszengarden.com.)

The WCAG guidelines recognize that it is not always possible to avoid using <table> tags to create page layouts, because older browsers do not fully support the Cascading Style Sheet standards (see WCAG Guideline 5). In those cases in which you cannot avoid using tables for layout, you should verify that the content of the tables makes sense when linearized (that is, read in table-cell order).

Because the ASP.NET framework must be compatible with browsers both old and new, some of the ASP.NET controls do, in fact, use <table> tags for layout. For example, the ASP.NET 2.0 Login control uses the <table> tag to control the layout of the user name and password input fields.

Creating Accessible Scripts

One, quite severe, restriction included in both the WCAG and Section 508 guidelines concerns client-side scripts. According to a priority 1 checkpoint in the WCAG 1.0 guidelines:

6.3 Ensure that pages are usable when scripts, applets, or other programmatic objects are turned off or not supported. If this is not possible, provide equivalent information on an alternative accessible page. [Priority 1]

The Section 508 guidelines include a similar requirement:

(l) When pages utilize scripting languages to display content, or to create interface elements, the information provided by the script shall be identified with functional text that can be read by assistive technology.

The problem is that several ASP.NET controls require client-side JavaScript in order to function. The prime example of this is the ASP.NET LinkButton control. The LinkButton control uses JavaScript to submit the form containing the control to the Web server.

There is no good solution to this problem. If you are required to build a Web site that meets all accessibility guidelines, then you need to be very careful about using client-side scripts. You might need to avoid using certain ASP.NET controls that depend on JavaScript, such as the LinkButton control.

Unfortunately, this guideline is difficult to follow when building a modern Web site. The assumption seems to be that Web sites are more like magazines than applications. Modern Web sites tend to include dynamic, client-side content. For example, many real estate Web sites include a JavaScript mortgage calculator. It is not clear what the text equivalent of a JavaScript mortgage calculator would look like.

Validating Pages for Accessibility

There is no such thing as a completely automated validator for accessibility in the same way as there is a completely automated validator for XHTML. There can't be an automated validator for accessibility, because judging the accessibility of a page requires human interpretation.

For example, in order to make a Web page accessible, every image in the page must contain meaningful alternate text. Currently, no machine can determine whether a fragment of text has the same meaning as an image. At best, an accessibility validator can only provide you with a list of things that you should check.

Visual Studio 2005 (but not Visual Web Developer) includes an Accessibility Checker. You can open the Accessibility Checker from the toolbar. or you can select the menu option Tools, Check Accessibility (see Figure 13).

Click here for larger image.

Figure 13. Visual Studio 2005 Accessibility Checker (Click the graphic for a larger image.)

The Accessibility Checker provides you with options for validating your Web site against WCAG Priority 1 checkpoints, WCAG Priority 2 checkpoints, or Section 508 guidelines. You can view the results of validating your Web site by opening the Error List (select the menu option View, Other Windows, Error List).

The Visual Studio 2005 Accessibility Checker also provides you with the option of displaying a "manual checklist" of accessibility issues. If you select this option, the same static list of accessibility issues is displayed in the Error List window whenever you validate your Web site for accessibility. This checklist contains issues that cannot be automatically validated by the Accessibility Checker.

If you are building Web sites with Visual Web Developer, you can also check your Web pages for accessibility. To do this, you'll need to use one of the online Accessibility Checkers. Here are links to two of the most popular online accessibility checkers:

Sample Application: An Accessible XHTML ASP.NET Web Site

In this final section, we'll build an ASP.NET 2.0 Web site from start to finish. The source code for this sample Web site is included with this whitepaper. You can download the source code for the sample Web site, and open the Web site in either Visual Web Developer or Visual Studio 2005.

The goal is to create a Web site that is completely standards compliant. Our Web site will validate as XHTML 1.0 Strict (and even XHTML 1.1). Furthermore, the Web site will be accessible to persons with disabilities. It will satisfy both section 508 and WCAG (priority 1 and priority 2) accessibility requirements.

We will build an online bookstore called the Super Super Bookstore Web site. We'll retrieve all of our book listings for our bookstore through the Amazon E-Commerce Web services. The Amazon E-Commerce Web services provide us with plenty of free sample data to play with (for more information about the Amazon Web Services, see http://www.amazon.com/gp/aws/landing.html).

To keep things simple, our Web site will consist of only two ASP.NET pages:

  • Default.aspx—This page displays a list of books in a specified category.
  • Search.aspx—This page enables you to search for all books that meet a certain search criterion.

Behind the scenes, the Web site uses several new features of the ASP.NET 2.0 framework. For example, the Web site uses a Master Page to create a common page layout, and a Theme to create a common page style. Finally, the sample site uses the new GridView and ObjectDataSource controls for data access.

Accessing the Amazon Web Services

The Super Super Bookstore uses a common class, named Amazon, to retrieve book information and perform searches against the Amazon catalog of books. This class is contained in Listing 10.

Listing 10. Amazon.vb

Imports Microsoft.VisualBasic

Public Class Amazon

    Const SubscriptionId As String = "1CD1NYF3YQ830DG7AM02"
    
''' <summary>
    ''' Attempts to get books in category from cache. 
    ''' If not in cache, call Amazon Web service
    ''' </summary>
    Public Function GetBooks(ByVal CategoryId As String) _
      As AmazonServices.Item()
        Dim context As HttpContext = HttpContext.Current
        Dim Books As AmazonServices.Item()

        If IsNothing(context.Cache(CategoryId)) Then
            Books = GetBooksFromAmazon(CategoryId)
            context.Cache(CategoryId) = Books
        Else
            Books = CType(context.Cache(CategoryId), _
              AmazonServices.Item())
        End If

        Return Books
    End Function

    ''' <summary>
    ''' Retrieves books in certain category from Web service
    ''' </summary>
    Public Function GetBooksFromAmazon(ByVal CategoryId As String) _
      As AmazonServices.Item()
        Dim service As New AmazonServices.AWSECommerceService()

        ' Initialize Request
        Dim searchRequest As New AmazonServices.ItemSearchRequest
        With searchRequest
            .SearchIndex = "Books"
            .Sort = "salesrank"
            .ResponseGroup = New String() {"Medium"}
            .BrowseNode = CategoryId
        End With

        Dim search As New AmazonServices.ItemSearch
        With search
            .SubscriptionId = SubscriptionId
            .Request = New AmazonServices.ItemSearchRequest() _
              {searchRequest}
        End With

        ' Get Response
        Dim response As AmazonServices.ItemSearchResponse = Nothing
        Try
            service.Timeout = 5000
            response = service.ItemSearch(search)
        Catch
        End Try

        If IsNothing(response) Then
            Return Nothing
        End If
        Return response.Items(0).Item
    End Function

    ''' <summary>
    ''' Searches for books by calling Amazon Web service
    ''' </summary>
    Public Function SearchBooksFromAmazon(ByVal Author As String, _
      ByVal Title As String, ByVal Keywords As String, _
      ByVal PowerSearch As String) As AmazonServices.Item()
        ' Don't search if nothing to search for
        If IsNothing(PowerSearch) And IsNothing(Author) And _
          IsNothing(Title) And IsNothing(Keywords) Then
            Return Nothing
        End If

        ' Initialize Request
        Dim service As New AmazonServices.AWSECommerceService()
        Dim searchRequest As New AmazonServices.ItemSearchRequest
        With searchRequest
            .SearchIndex = "Books"
            .ResponseGroup = New String() {"Medium"}
            If Not IsNothing(PowerSearch) Then
                .Power = PowerSearch
            Else
                If Not IsNothing(Author) Then
                    .Author = Author
                End If
                If Not IsNothing(Title) Then
                    .Title = Title
                End If
                If Not IsNothing(Keywords) Then
                    .Keywords = Keywords
                End If
            End If
        End With

        Dim search As New AmazonServices.ItemSearch
        With search
            .SubscriptionId = SubscriptionId
            .Request = New AmazonServices.ItemSearchRequest() _
              {searchRequest}
        End With

        ' Get Response
        Dim response As AmazonServices.ItemSearchResponse
        Try
            service.Timeout = 5000
            response = service.ItemSearch(search)
        Catch
        End Try

        If IsNothing(response) Then
            Return Nothing
        End If
        Return response.Items(0).Item
    End Function


    ''' <summary>
    ''' The Amazon Author property represents a list of authors.
    ''' Therefore, we create a comma separated list    
    ''' </summary>
    Public Shared Function FormatAuthor(ByVal Authors As String()) _
      As String
        If Not IsNothing(Authors) Then
            Return String.Join(", ", Authors)
        Else
            Return "Not Listed"
        End If
    End Function

    ''' <summary>
    ''' Formats Amazon ListPrice into US currency
    ''' </summary>
    Public Shared Function FormatPrice(ByVal Price As String) As String
        If Not IsNothing(Price) Then
            Return "$" & Price.Insert(Price.Length - 2, ".")
        Else
            Return "Not Listed"
        End If
    End Function

    ''' <summary>
    ''' Formats tooltip for the link to the book details
    ''' </summary>
    Public Shared Function _
      FormatDetailsTooltip(ByVal Title As String) As String
        If Not IsNothing(Title) Then
            Return String.Format("Link to {0} details", Title)
        Else
            Return "Link to details"
        End If
    End Function

    ''' <summary>
    ''' If there is no book cover, we fall back to displaying our image
    ''' </summary>
    Public Shared Function FormatBookCover(ByVal Url As String) _
      As String
        If Not IsNothing(Url) Then
            Return Url
        Else
            Return "Images/NoBookCover.gif"
        End If
    End Function
End Class

The two most important functions in the class are called GetBooksFromAmazon and SearchBooksFromAmazon. The first function is called from the Default.aspx page to display the book listings by category. The second function is called from the Search.aspx page to enable users to search for books.

Both functions use a Web Service proxy class named AmazonServices. This proxy class was created by selecting the menu option Web site, Add Web Reference, and entering the URL http://soap.amazon.com/onca/soap?Service=AWSECommerceService. This is the proper URL for accessing United States Amazon data.

The Default Page

The Default.aspx page displays a list of book categories, and a list of matching books for the selected category (see Figure 14). The Default.aspx page is contained in Listing 11.

Click here for larger image.

Figure 14. The default page (Click the graphic for a larger image.)

Listing 11. Default.aspx

<%@ Page Language="VB" MasterPageFile="~/SiteMaster.master" 
  Title="Super Super Books" %>

<script runat="server">

    Sub Page_Load()
        Dim categoryIndex As Integer = 0
        If Not IsNothing(Request("index")) Then
            categoryIndex = Int32.Parse(Request("index"))
        End If
        MenuCategories.Items(categoryIndex).Selected = True
    End Sub

</script>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentBody" 
  Runat="Server">
    <h1>Book Listings</h1>
    <hr />
    <div id="leftColumn">
    <asp:Menu
        id="MenuCategories"
        ToolTip="Book categories menu"
        StaticMenuItemStyle-CssClass="menuNormal"
        StaticSelectedStyle-CssClass="menuSelected"
        Runat="server">
        <Items>
        <asp:MenuItem 
            Text="Arts and Photography"
            Value="1"
            NavigateUrl="~/Default.aspx?index=0" />
        <asp:MenuItem 
            Text="Biographies and Memoirs"
            Value="2"
            NavigateUrl="~/Default.aspx?index=1" />
        <asp:MenuItem 
            Text="Children's Books"
            Value="4" 
            NavigateUrl="~/Default.aspx?index=2" />
        <asp:MenuItem 
            Text="Computers and Internet"
            Value="5" 
            NavigateUrl="~/Default.aspx?index=3" />
        <asp:MenuItem 
            Text="Cooking, Food and Wine"
            Value="6" 
            NavigateUrl="~/Default.aspx?index=4" />
        <asp:MenuItem 
            Text="Science Fiction and Fantasy"
            Value="25" 
            NavigateUrl="~/Default.aspx?index=5" />
        </Items>
    </asp:Menu>    

    </div>
    
    <div id="middleColumn">
    <asp:GridView
        id="grdBooks"
        DataSourceID="srcBooks"
        AutoGenerateColumns="false"
        CssClass="books"
        HeaderStyle-CssClass="booksHeader"
        EmptyDataText="No matching results"
        Runat="server">
        <Columns>
        <asp:TemplateField HeaderText="Book Cover Image">
        <ItemTemplate>
            <asp:Image
                id="imgBook"
                ImageUrl='<%#Amazon.FormatBookCover(Eval("SmallImage.Url"))%>'
                AlternateText="Book cover image"
                Runat="server" />
        </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Book Information">
        <ItemTemplate>
            <h2><%#Server.HtmlEncode(Eval("ItemAttributes.Title"))%></h2>
            Authors: 
            <%#Amazon.FormatAuthor(Eval("ItemAttributes.Author"))%>
            <br />Price:
            <%#Amazon.FormatPrice(Eval("ItemAttributes.ListPrice.Amount"))%>
            <br />Sales Rank:
            <%#Eval("SalesRank")%>
            <br />
            <asp:HyperLink 
                id="lnkDetails"
                NavigateUrl='<%#Eval("DetailPageURL")%>'
                Text="View Details"
                Tooltip=
  '<%#Amazon.FormatDetailsTooltip(Eval("ItemAttributes.Title"))%>'
                Runat="server" />
        </ItemTemplate>
        </asp:TemplateField>
        </Columns>
    </asp:GridView>    

    <asp:ObjectDataSource
        id="srcBooks"
        TypeName="Amazon"
        SelectMethod="GetBooks"
        Runat="server">
        <SelectParameters>
            <asp:ControlParameter
                Name="CategoryId"
                ControlId="menuCategories"
                DefaultValue="1" />
        </SelectParameters>
    </asp:ObjectDataSource>    

    </div>

</asp:Content>

This page uses two ASP.NET controls to display the book listings: the Menu control and the GridView control. The Menu control is used to display the list of book categories, and the GridView control is used to display the list of books.

The GridView control is bound to an ObjectDataSource control. The ObjectDataSource control, in turn, calls the GetBooks() method from the Amazon class, to retrieve the list of books.

XHTML Features of the Default Page

One goal, when building XHTML pages, is to cleanly separate a document's structure from its presentation. To reach this goal, no formatting properties are set on any of the ASP.NET controls in the Default.aspx page. The page formatting is encapsulated in an external style sheet that is associated with the page through an ASP.NET Theme.

ASP.NET 2.0 Themes make it easier to follow web standards, because they enable you to separate all of your presentational content from your pages. The sample site includes a Theme, named SiteTheme, that contains a single style sheet. This Theme is automatically associated with every page, using the following configuration setting in the Web.Config file.

    <pages 
      styleSheetTheme="SiteTheme"
      masterPageFile="SiteMaster.master" /> 

You should notice that HTML tables are not used to create the page layout. Although neither the XHTML standard nor accessibility standards prohibit you from using tables for page layout, both standards encourage you to avoid it. In the sample site, the page layout is completely determined by the external style sheet. The page itself is divided into two columns by two <div> elements. The external style sheet contains rules for positioning the two <div> elements.

Finally, the sample site uses content negotiation when serving pages. When a page is requested from the Web site, using a browser that understands the application/xhtml+xml MIME type, the page is served with this MIME type; otherwise, the page is served as text/html.

The content negotiation is accomplished with the following event handler in the Global.asax file.

Sub Application_PreSendRequestHeaders(ByVal s As Object, _
  ByVal e As EventArgs)
    If Array.IndexOf(Request.AcceptTypes, _
      "application/xhtml+xml") > -1 Then
            Response.ContentType = "application/xhtml+xml"
    End If
End Sub

Accessibility Features of the Default Page

Both the WCAG and Section 508 accessibility guidelines prohibit client-side JavaScript when a text equivalent of the JavaScript cannot be provided. In order to satisfy these guidelines, the Default.aspx page does not depend on client-side JavaScript. The page works even when you turn off JavaScript in your browser.

In order to satisfy this requirement, extra work had to be done when implementing the menu. By default, the ASP.NET Menu control renders JavaScript for each menu item to handle the client click event. However, when a menu item is provided with a NavigateUrl property, the menu item no longer uses JavaScript.

In the sample site, each menu item is provided with a NavigateUrl property that points back to the Default.aspx page. When you click a menu item, the Default.aspx page is reloaded. The Page_Load event handler is used to detect which menu item was clicked, and this subroutine updates the menu with the current menu selection.

The advantage of using a Menu control is that a Menu control automatically generates a Skip Navigation link. If you tab through each of the elements in the Default.aspx page, you'll notice (if you look at your browser's status bar) that there is a hidden link that skips the contents of the menu. The Menu control enables you to automatically satisfy the WCAG and Section 508 guidelines that require you to provide a method of skipping repetitive navigation links.

The Search Page

The search page contains a form that enables users of the Web site to search for books by supplying the book author, book title, book keywords, or by supplying a complex query (see Figure 15). The results of the query are displayed in a GridView control. The Search.aspx page is contained in Listing 12.

Click here for larger image.

Figure 15. The search page (Click the graphic for a larger image.)

Listing 12. Search.aspx

<%@ Page Language="VB" MasterPageFile="~/SiteMaster.master" 
    Title="Search Books" %>

<script runat="server">

    Protected Sub btnQuickSearch_Click(ByVal sender As Object, _
      ByVal e As System.EventArgs)
        txtPowerSearch.Text = String.Empty
    End Sub
        
    Protected Sub btnPowerSearch_Click(ByVal sender As Object, _
      ByVal e As System.EventArgs)
        txtAuthor.Text = String.Empty
        txtTitle.Text = String.Empty
        txtKeywords.Text = String.Empty
    End Sub
</script>

<asp:Content ID="Content1" ContentPlaceHolderID="ContentBody" 
  Runat="Server">
    <h1>Search Books</h1>
    <hr />
    <div id="leftColumn">
    
    <fieldset class="quickSearch">
    <legend>Quick Search</legend>

    <asp:Label
        Text="Author:"
        AssociatedControlID="txtAuthor"
        AccessKey="a"
        Runat="server" />
    <asp:TextBox
        id="txtAuthor"
        ToolTip="Search by author"
        Runat="server" />
    <span class="accessKey">access key is a</span>
    
    <br />
    <asp:Label
        Text="Title:"
        AssociatedControlID="txtTitle"
        AccessKey="t"
        Runat="server" />
    <asp:TextBox
        id="txtTitle"
        ToolTip="Search by title"
        Runat="server" />
    <span class="accessKey">access key is t</span>
        
    <br />
    <asp:Label
        Text="Keywords:"
        AssociatedControlID="txtKeywords"
        AccessKey="k"
        Runat="server" />
    <asp:TextBox
        id="txtKeywords"
        ToolTip="Search by keywords"
        Runat="server" />
    <span class="accessKey">access key is k</span>
    
    <br />
    <asp:Button
        id="btnQuickSearch"
        Text="Quick Search Now"
        ToolTip="Peform quick search"
        AccessKey="s"
        Runat="server" OnClick="btnQuickSearch_Click" />     
    <span class="accessKey">access key is s</span>
    </fieldset>

    <br />

    <fieldset class="powerSearch">
    <legend>Power Search</legend>

    <asp:Label
        Text="Query:"
        AssociatedControlID="txtPowerSearch"
        AccessKey="q"
        Runat="server" />
    <asp:TextBox
        id="txtPowerSearch"
        ToolTip="Power search query text"
        TextMode="MultiLine"
        Columns="20"
        Rows="3"
        Runat="server" />
    <span class="accessKey">access key is q</span>
    
    <br />
    <asp:Button
        id="btnPowerSearch"
        Text="Power Search Now"
        ToolTip="Perform power search"
        AccessKey="p"
        Runat="server" OnClick="btnPowerSearch_Click" />     
    <span class="accessKey">access key is p</span>
    
    </fieldset>
    </div>

   
    <div id="middleColumn">
    <asp:GridView
        id="grdBooks"
        DataSourceID="srcBooks"
        AutoGenerateColumns="false"
        CssClass="books"
        HeaderStyle-CssClass="booksHeader"
        EmptyDataText="No matching results"
        Runat="server">
        <Columns>
        <asp:TemplateField HeaderText="Book Cover Image">
        <ItemTemplate>
            <asp:Image
                id="imgBook"
                ImageUrl=
  '<%#Amazon.FormatBookCover(Eval("SmallImage.Url"))%>'
                AlternateText="Book cover image"
                Runat="server" />
        </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Book Information">
        <ItemTemplate>
            <h2><%#Server.HtmlEncode(Eval("ItemAttributes.Title"))%></h2>
            Authors: 
            <%#Amazon.FormatAuthor(Eval("ItemAttributes.Author"))%>
            <br />Price:
            <%#Amazon.FormatPrice( 
                Eval("ItemAttributes.ListPrice.Amount"))%>
            <br />Sales Rank:
            <%#Eval("SalesRank")%>
            <br />
            <asp:HyperLink 
                id="lnkDetails"
                NavigateUrl='<%#Eval("DetailPageURL")%>'
                Text="View Details"
                Tooltip=
       '<%#Amazon.FormatDetailsTooltip(Eval("ItemAttributes.Title"))%>'
                Runat="server" />
        </ItemTemplate>
        </asp:TemplateField>
        </Columns>
    </asp:GridView>    

    <asp:ObjectDataSource
        id="srcBooks"
        TypeName="Amazon"
        SelectMethod="SearchBooksFromAmazon"
        Runat="server">
        <SelectParameters>
            <asp:ControlParameter 
                Name="Author"
                ControlId="txtAuthor"
                ConvertEmptyStringToNull="true" />
            <asp:ControlParameter 
                Name="Title"
                ControlId="txtTitle"
                ConvertEmptyStringToNull="true" />
            <asp:ControlParameter 
                Name="Keywords"
                ControlId="txtKeywords"
                ConvertEmptyStringToNull="true" />
            <asp:ControlParameter 
                Name="PowerSearch"
                ControlId="txtPowerSearch"
                ConvertEmptyStringToNull="true" />
        </SelectParameters>
    </asp:ObjectDataSource>    
    
    </div>    

</asp:Content>

XHTML Features of the Search Page

The search page, just like the default page, contains no presentational elements or attributes. The style and layout of the search page is completely encapsulated in an external style sheet associated with the page through an ASP.NET Theme.

Again, like the default page, the search page uses content negotiation. If someone requests the search page with a browser that recognizes the application/xhtml+xml MIME type, then the page is served with this MIME type; otherwise, the page is served as text/html.

Accessibility Features of the Search Page

The search page includes a form. Or, more accurately, the page contains a single form divided into two subforms. It includes a Quick Search form and a Power Search form.

Notice that the form is divided into subforms with the HTML <fieldset> tag. The <fieldset> tag enables you to group logically related form elements. The accessibility guidelines require you to use the <fieldset> tag when working with complex forms (see WCAG 12.3).

Notice, furthermore, that each form field is explicitly associated with its label. Each ASP.NET control includes an AssociatedControlID property that points to its corresponding form field. These explicit associations between labels and fields help users of screen readers determine the purpose of particular form fields.

Finally, notice that each Label control is assigned an access key. The access keys enable you to easily navigate the form fields without using a mouse. For example, if you press ALT+A, you can enter the name of an author. If you then press ALT+S, the Quick Search form is submitted, and the results are displayed in the GridView. In other words, you can easily perform searches without touching the mouse.

If you press the ALT key, the access keys are automatically displayed (see Figure 16). This is accomplished through JavaScript. Notice that there is a <span> tag that appears after each form field. For example, the Title search field is implemented with the following code.

    <asp:Label
        Text="Title:"
        AssociatedControlID="txtTitle"
        AccessKey="t"
        Runat="server" />
    <asp:TextBox
        id="txtTitle"
        ToolTip="Search by title"
        Runat="server" />
    <span class="accessKey">access key is t</span>  

When you press the ALT key, client-side JavaScript executes, and the contents of the <span> tag are displayed.

Aa479043.aspnetstd_fig16(en-us,MSDN.10).gif

Figure 16. Search form access keys

You might worry about this functionality, because, according to the accessibility guidelines, the page must continue to work when JavaScript and style sheets are turned off (WCAG guideline 6). Fortunately, the page does work when both JavaScript and style sheets are disabled. In that case, the contents of the <span> tags are no longer hidden, and the access keys are always displayed (see Figure 17).

Aa479043.aspnetstd_fig17(en-us,MSDN.10).gif

Figure 17. Search form degrading gracefully

The Master Page

The sample Web site uses an ASP.NET 2.0 Master Page, named SiteMaster.master, behind the scenes. Master Pages enables you to include the same content and create the same layout in multiple pages in a Web site. The Master Page is associated with every page in the sample Web site through the following configuration setting in the Web.Config file.

    <pages 
      styleSheetTheme="SiteTheme"
      masterPageFile="SiteMaster.master" /> 

The contents of the Master Page are contained in Listing 13.

Listing 13. SiteMaster.master

<%@ Master Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<script runat="server">
    
    ''' <summary>
    ''' Select style sheet to display
    ''' </summary>
    Sub Page_Load()
        If Not IsNothing(Request("large")) Then
            Profile.AccessibleStyleSheet = True
        End If
        If Not IsNothing(Request("normal")) Then
            Profile.AccessibleStyleSheet = False
        End If
        
        If Profile.AccessibleStyleSheet Then
            lnkAccessibleStyle.Visible = True
            lnkStyle.Text = "Normal Text Version"
            lnkStyle.NavigateUrl = Request.Path & "?normal=1"
        Else
            lnkAccessibleStyle.Visible = False
            lnkStyle.Text = "Large Text Version"
            lnkStyle.NavigateUrl = Request.Path & "?large=1"
        End If
    End Sub
   
    
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Super Super Books</title>
    <script type="text/javascript" 
      src="Scripts/AccessKeys.js"></script>
    <link id="lnkAccessibleStyle" 
        type="text/css" 
        rel="Stylesheet"
        href="~/Styles/Accessible.css" 
        runat="server" />
</head>
<body>
    <form id="form1" runat="server">
    <div id="content">
        
        <img id="SiteLogo" src="Images/SiteLogo.png" 
          alt="SSB Web site logo image" />
        
        <div id="banner">
            <asp:HyperLink
                id="lnkStyle"
                ToolTip="Modify the size of all text in this Web site"
                Runat="server" />
            <br />
            <asp:Menu
                id="MenuSite"
                ToolTip="Web site navigation menu"
                CssClass="menuSite"
                Orientation="Horizontal"
                StaticTopSeparatorImageUrl="Images/bullet.gif"
                Runat="server">
                <Items>
                <asp:MenuItem
                    Text="Home"
                    ToolTip="Navigate to home page"
                    NavigateUrl="~/Default.aspx" />
                <asp:MenuItem
                    Text="Search"
                    Tooltip="Navigate to search page"
                    NavigateUrl="~/search.aspx" />    
                </Items>
            </asp:Menu>
        </div>
        
        <hr />        
    
        <asp:contentplaceholder id="ContentBody" runat="server" />

        <hr />
        <a href="http://validator.w3.org/check?uri=referer"
            title="Explanation of XHTML 1.0 Conformance">
        <img
          src="http://www.w3.org/Icons/valid-xhtml10"
          alt="Valid XHTML 1.0 icon" class="icon"/></a>

        <a href="http://jigsaw.w3.org/css-validator/"
            title="Explanation of CSS Conformance">
        <img 
            src="http://jigsaw.w3.org/css-validator/images/vcss" 
            alt="Valid CSS icon" class="icon" /></a>

        <a href="http://www.w3.org/WAI/WCAG1AA-Conformance"
            title="Explanation of Level Double-A Conformance">
        <img height="32" width="88" 
            src="http://www.w3.org/WAI/wcag1AA"
            alt="Level Double-A conformance icon, 
            W3C-WAI Web Content Accessibility Guidelines 1.0" 
            class="icon" /></a>


    </div>
    </form>
</body>
</html>

XHTML Features of the Master Page

By taking advantage of Master Pages, you can easily supply the right DOCTYPE for all of the pages in your Web site. The SiteMaster.master page includes the XHTML 1.0 Strict DOCTYPE. The benefit of specifying the DOCTYPE in a Master Page is that you only need to change it in one location if you ever need to change the DOCTYPE for all the pages in a Web site in the future. For example, some day soon, you might want to migrate to an XHTML 1.1 Web site and modify the DOCTYPE to reflect the change.

The Master Page also includes a series of conformance logos, which appear in the footer of every page in the sample Web site. The conformance logos advertise that the Web site conforms to XHTML, CSS, and WCAG 1.0 Web standards (see Figure 18).

Aa479043.aspnetstd_fig18(en-us,MSDN.10).gif

Figure 18. Conformance logos

Accessibility Features of the Master Page

Every page in the sample Web site includes a link at the top of the page that can be used to switch the style sheet used to display the page. Each page can be displayed in one of two versions: a Normal Text version and a Large Text version. When the Large Text version is selected, the size of all of the text in the Web site is increased to a more readable size (see Figure 19).

Aa479043.aspnetstd_fig19(en-us,MSDN.10).gif

Figure 19. Large text size

A user needs to make this selection only once. If someone selects the Large Text version of the Web site, this preference is automatically recorded and used whenever the person returns to the Web site. This functionality is implemented by taking advantage of yet another new feature of the ASP.NET 2.0 framework: Profiles. A Profile enables you to store user settings across multiple visits to a Web site.

The Profile is defined in the Web.Config file.

    <anonymousIdentification enabled="true"/>
    <profile>
      <properties>
        <add 
          name="AccessibleStyleSheet"
          type="Boolean" 
          defaultValue="false" />
      </properties>
    </profile>

This Profile defines a Boolean property named AccessibleStyleSheet. Once the property is defined in the Web.Config file, you can read or set the property in any ASP.NET page, through the Profile property exposed by the Page class. For example, to set the AccessibleStyleSheet property to the value True, and display the Large Text version of the Web site, you would write the following.

Profile.AccessibleStyleSheet = true

The Master Page includes all of the logic for selecting the Normal Text or Large Text version of the Web site. A HyperLink control is used to enable Web site visitors to make this selection. After the HyperLink is clicked, the Page_Load event handler detects the user's selection and sets the AccessibleStyleSheet Profile property.

The code here would have been simpler if a LinkButton control could have been used instead of a HyperLink control. Once again, however, the accessibility guidelines prevent us from doing this, because a LinkButton control depends on client-side JavaScript.

When the Large Text version is selected, a reference to an additional style sheet is added to the page. The style sheet includes a single rule.

body
{
    font-size: x-large;
}

This rule sets the body font size to the value x-large. Because all of the fonts specified in the primary style sheet (contained in the SiteTheme folder) use relative sizes, modifying the font size of the body element automatically increases the font size of all elements in the Web site.

Conclusion

Web standards are a good thing. By following Web standards, you can make your Web sites accessible to the broadest possible audience with the least amount of work. Your Web sites will be compatible with more browsers, and they will be more likely to continue to work in the future.

The ASP.NET 2.0 framework was designed to enable you to easily build Web sites that satisfy Web standards. The framework enables you to easily build XHTML Web sites. In the ASP.NET 2.0 framework, all ASP.NET controls render XHTML elements and attributes by default. Furthermore, Visual Studio 2005 and Visual Web Developer allow you to automatically validate your pages against the XHTML standards while you build them.

The ASP.NET 2.0 framework also makes it easier to build Web sites that are accessible to persons with disabilities. The controls in the ASP.NET 2.0 framework include a host of new properties designed with accessibility in mind. For example, every ASP.NET control that renders an image enables you to render alternate text for the image. Furthermore, all the new navigation controls include Skip Navigation links to make it easier for persons with disabilities to navigate your Web site.

 

About the author

Stephen Walther wrote the best-selling book on ASP.NET, ASP.NET Unleashed. He was also the architect and lead developer of the ASP.NET Community Starter Kit, a sample ASP.NET application produced by Microsoft. He has provided ASP.NET training to companies across the United States, including NASA and Microsoft, through his company Superexpert.

© Microsoft Corporation. All rights reserved.