Sie sind auf Seite 1von 64

ASP.

NET Technical Articles


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

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 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>

7. 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" />.

8. 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.

9. 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).

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).

Figure 2. Quirks mode

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.
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 >
<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.
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).
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 >
<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).

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 >
<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.

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).

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 >
<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 >
<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).

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.

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 >
<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).

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 >
<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).
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:

• Bobby
• WAVE

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.
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.Ur
l"))%>'
AlternateText="Book cover image"
Runat="server" />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Book Information">
<ItemTemplate>
<h2><%#Server.HtmlEncode(Eval("ItemAttributes.Title"))%></h
2>
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.

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"))%></h
2>
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.
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).
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 >
<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).

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).
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.

Das könnte Ihnen auch gefallen