科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道使用Web标准生成ASP.NET 2.0 Web站点二

使用Web标准生成ASP.NET 2.0 Web站点二

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

清单 6 中的页包含一个更复杂版本的 Boston 地铁时间表,它同时使用了 headers 和 axis 属性(参见图 9)。

作者:中国IT实验室 来源:中国IT实验室 2007年9月3日

关键字: Web站点 ASP.NET Web标准

  • 评论
  • 分享微博
  • 分享邮件
  清单 6 中的页包含一个更复杂版本的 Boston 地铁时间表,它同时使用了 headers 和 axis 属性(参见图 9)。

  图 9. 复杂的地铁时间表

  清单 6. Subway.aspx
<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Red Line Subway Schedule</title>
    <style type="text/css">
        caption {color:white;background-color:red;font-size:xx-large}
        table {width:500px;border-collapse:collapse}
        td,th {padding:5px}
        td {border:1px solid black}
        tbody th {text-align:right}
        .headerRow th {font-size:x-large;text-align:left}    
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <table 
      summary="This table contains the schedule of train 
                departures for the Red Line">
    <caption>Red Line Schedule</caption>
    <thead>
    <tr>
        <th></th>
        <th id="hdrFirstTrain" axis="train">First Train</th>
        <th id="hdrLastTrain" axis="train">Last Train</th>
    </tr>
    </thead>
    <tbody>
    <tr class="headerRow">
        <th id="hdrWeekday" axis="day" colspan="3">Weekday</th>
    </tr>
    <tr>
        <th id="hdrAlewife1" axis="location">Alewife</th>
        <td headers="hdrAlwife1 hdrWeekday hdrFirstTrain">5:24am</td>
        <td headers="hdrAlwife1 hdrWeekday hdrLastTrain">12:15am</td>
    </tr>
    <tr>
        <th id="hdrBraintree1" axis="location">Braintree</th>
        <td headers="hdrBraintree1 hdrWeekday hdrFirstTrain">5:15am</td>
        <td headers="hdrBraintree1 hdrWeekday hdrLastTrain">12:18am</td>
    </tr>  
    <tr class="headerRow">
        <th id="hdrSaturday" axis="day" colspan="3">Saturday</th>
    </tr>
    <tr>
        <th id="hdrAlewife2" axis="location">Alewife</th>
        <td headers="hdrAlewife2 hdrSaturday hdrFirstTrain">8:24am</td>
        <td headers="hdrAlewife2 hdrSaturday hdrLastTrain">11:15pm</td>
    </tr>
    <tr>
        <th id="hdrBraintree2" axis="location">Braintree</th>
        <td 
          headers="hdrBraintree2 hdrSaturday hdrFirstTrain">7:16am</td>
        <td 
          headers="hdrBraintree2 hdrSaturday hdrLastTrain">10:18pm</td>
    </tr>  
    </tbody>      
    </table>
    </div>
    </form>
</body>
</html>

  请注意,每个表单元格都包含 headers 属性。headers 属性表示与列和行标题相对应的 ID 的空格分隔列表。地铁时间表中的每个单元格都具有相关联的位置、日期和列车标题。

  同时,请注意,每个 <th> 标记都具有一个 axis 属性,用于表示与标题相关联的类别。例如,Weekday 和 Saturday 标题都与 day 轴相关联。First Train 和 Last Train 标题与 train 轴相关联。

  最后,请注意清单 6 中的表同时包含 summary 属性和 标记。summary 属性的工作方式非常类似于 alt 属性。您可以使用 summary 属性提供浏览器未呈现的表的说明。另一方面,浏览器呈现 标记的内容。您应当使用 标记来标识表的用途。

  如果使用 ASP.NET 2.0 GridView 或 DetailsView 控件来显示 HTML 表中的数据库数据,则默认情况下,生成的 HTML 表是可访问的。例如,清单 7 包含一个 ASP.NET 页,它通过使用 GridView 控件来显示 Titles 数据库表的内容。

  清单 7. DisplayTitles.aspx
<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Display Titles</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:GridView
        id="grdTitles"
        DataSourceId="srcTitles"
        Runat="server" />
    <asp:SqlDataSource
        id="srcTitles"
        ConnectionString=
         "Server=localhost;Trusted_Connection=true;Database=Pubs"
        SelectCommand="Select * FROM Titles"
        Runat="server" />
    </div>
    </form>
</body>
</html>

  在清单 7 中,GridView 控件被绑定到一个表示 Titles 数据库表中记录的 SqlDataSource 控件。在浏览器中打开清单 7 中的 ASP.NET 页时,Titles 数据库表的内容显示在 HTML 表中(参见图 10)。

  图 10. DisplayTitles.aspx

  请注意,GridView 控件自动为每个列标题生成 <th> 标记。而且,如果在浏览器中选择 View Source,则可以看到为每个列标题自动生成 scope="col" 属性。

  GridView 控件支持其他多个与可访问性相关的属性:

  Caption 和 CaptionAlign — 使用这些属性可以向 GridView 控件生成的 HTML 表中添加标题。

  RowHeaderColumn — 使用该属性可以指示行标题(相对于列标题而言)。请将该属性设置为从数据源返回的列的名称(例如,title_id)。

  UseAccessibleHeader — 使用该属性可以指示是否应当用 或 <td> 标记呈现列标题。默认情况下,该属性具有值 true.

  请注意,GridView 控件不具有 Summary 属性。但是,像大多数 ASP.NET 控件一样,GridView 控件支持 expando 属性。当您声明 GridView 控件时,您可以声明喜欢的任何属性,而该属性将被呈现到浏览器中。因此,如果您希望向 GridView 中添加摘要,则请按如下方式声明 summary 属性。
<asp:GridView
        id="grdTitles"
        DataSourceId="srcTitles"
        summary="Displays the contents of the Titles database table"
        Runat="server" />

  GridView 控件的默认行为非常适合于以可访问的方式显示简单数据表。但是,如果您需要显示更为复杂的表(例如,一组嵌套表),则您必须完成额外的工作。

  例如,您可能希望显示产品类别的列表,并且希望在每个类别下显示匹配产品的列表。换句话说,您希望创建单页“主要信息/详细信息”表单(参见图 11)。在这种情况下,需要为每个表单元格包含 headers 属性。

  图 11. 嵌套 Repeater 控件

  清单 8 中的页说明如何将一个 Repeater 控件嵌套到另一个 Repeater 控件中,以及如何生成符合可访问性准则要求的复杂表。

  清单 8. NestedRepeaters.aspx
<%@ Page Language="VB"%>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
    Private dtblProducts As New DataTable
    Sub Page_Load()
        Dim dad As New SqlDataAdapter("SELECT * FROM PRODUCTS", _
         "Server=localhost;Trusted_Connection=true;Database=Northwind")
        dad.Fill(dtblProducts)
    End Sub
    Function GetProducts(ByVal CategoryID As Integer) As DataView
        Dim view As DataView = dtblProducts.DefaultView
        view.RowFilter = "CategoryID=" & CategoryID
        Return view
    End Function
    Function GetCategoryHeader(ByVal index As Integer) As String
        Return "hdrCategory" & index.ToString()
    End Function
    Function GetProductHeader(ByVal productID As Integer) As String
        Return "hdrProduct" & productID.ToString()
    End Function
    Function GetHeaders(ByVal item As RepeaterItem, _
      ByVal columnHeader As String) As String
        Dim parent As RepeaterItem = _
         item.NamingContainer.NamingContainer
        Dim categoryHeader As String = _
         GetCategoryHeader(parent.ItemIndex)
        Dim productHeader As String = _
         GetProductHeader(item.DataItem("ProductID"))
        Return String.Format("{0} {1} {2}", categoryHeader, _
          productHeader, columnHeader)
    End Function
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
    <style type="text/css">
        .categoryRow {background-color:yellow}
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:Repeater
        id="grdCategories"
        DataSourceId="srcCategories"
        Runat="server">
        <HeaderTemplate>
        <table>
            <thead>
                <th id="hdrID">ID</th>
                <th id="hdrName">Name</th>
                <th id="hdrPrice">Price</th>
            </thead>
            <tbody>
        </HeaderTemplate>
        <ItemTemplate>
            <tr class="categoryRow">
                <th colspan="3" 
                  id='<%# GetCategoryHeader(Container.ItemIndex) %>'>
                    <%# Eval("CategoryName") %>
                </th>
            </tr>
            <asp:Repeater
                id="grdProducts"
                DataSource='<%# GetProducts(Eval("CategoryID")) %>'
                Runat="server">
                <ItemTemplate>
                <tr>
                    <th 
                     id='<%# GetProductHeader(Eval("ProductID")) %>'>
                        <%# Eval("ProductID") %>
                    </th>
                    <td 
                     headers='<%# GetHeaders(Container, "hdrName") %>'>
                        <%#Eval("ProductName")%>
                    </td>
                    <td headers=
                      '<%# GetHeaders(Container, "hdrPrice") %>'>
                        <%#Eval("UnitPrice", "{0:c}")%>
                    </td>
                </tr>
                </ItemTemplate>
            </asp:Repeater>
        </ItemTemplate>
        <FooterTemplate>
            </tbody>
            </table>
        </FooterTemplate>
    </asp:Repeater>    
    <asp:SqlDataSource
        id="srcCategories"
        ConnectionString=
         "Server=localhost;Trusted_Connection=true;Database=Northwind"
        SelectCommand="SELECT * FROM Categories"
        Runat="server" />
    </div>
    </form>
</body>
</html>

  在清单 8 中,外层的 Repeater 控件用来列出产品类别,而内层的 Repeater 控件用来列出匹配产品。下列两个 Helper 函数用来生成 Category Name 和 Product ID 标题的 id 值:GetProductHeader 和 GetCategoryHeader 函数。另外一个单独的名为 GetHeaders 的 Helper 函数用来生成用于 headers 属性的值。

  清单 8 中的 ASP.NET 页生成如下所示的 HTML 表。
<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

  请注意,每个 <td> 标记都包含适当的 headers 属性。

  创建可访问的 XHTML

  很多可访问性准则共有的一个主题是这样一个概念 — 即,Web 页应当符合标准,这样才能成为可访问的页。按照准则,您应当努力使用最新的 W3C 标准(例如,最新版本的 XHTML 和层叠样式表)来生成 Web 站点。

  特别需要指出的是,在设计 Web 页时,您应当将文档的结构与它的表示形式分开。请使用标记来表示 Web 页的结构,并且使用层叠样式表来控制 Web 页的外观。

  例如,绝不要仅仅使用 <blockquote>元素来缩进文本块。<blockquote> 元素的用途是创建原文的引文。如果您希望缩进文本,则应当改而使用层叠样式表 margin 属性。

  您还应当只在表示数据表时使用 <table> 标记。尽管使用 <table> 标记来对 Web 页面进行布局在当前是一种常见的做法,但是,请尽可能改而使用 <div> 标记。例如,清单 9 中的页具有三列式布局,但是不包含一个 <table> 标记(参见图 12)。

  图 12. 不含表的页布局

  清单 9. Tableless.aspx
<%@ Page Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Tableless Layout</title>
    <style type="text/css">
    #content
    {
        margin-left:auto;
        margin-right:auto;
        width:800px;
    }
    #leftColumn 
    {
        float:left;
        width:150px;
        border:1px solid black;
        padding:10px;
    }
    #middleColumn
    {
        float:left;
        width:430px;
        padding:10px;
    }
    #rightColumn 
    {
        float:right;
        width:150px;
        border:1px solid black;
        padding:10px;
    }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <div id="content">
    <div id="leftColumn">
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    Left column contents...
    </div>
    <div id="middleColumn">
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    Middle column contents...
    </div>
    <div id="rightColumn">
    Right column contents...
    Right column contents...
    Right column contents...
    Right column contents...
    Right column contents...
    Right column contents...
    Right column contents...
    Right column contents...
    </div>
    </div>
    </form>
</body>
</html>

  清单 9 中的页包含四个 <div> 标记。第一个 <div> 标记(名为 content)用来指定页的内容区域的宽度。其余三个 <div> 标记(分别名为 left、middle 和 right)将内容区域划分为三列。该页可以在 Internet Explorer 6、Firefox 和 Opera 8 中正确显示(要查看一些不使用 HTML 表创建布局的真正漂亮的页面,请参见 http://csszengarden.com.)

  WCAG 准则认为,不可能总是避免使用 <table> 标记来创建页布局,因为较旧的浏览器不完全支持层叠样式表标准(请参阅 WCAG 准则 5)。在无法避免使用表创建布局的情况下,您应该确认这些表的内容在进行线性化(即,按照表-单元格顺序来阅读)时是有意义的。

  因为 ASP.NET 框架必须与旧式和新式浏览器同时兼容,所以一些 ASP.NET 控件实际上确实使用 <table> 标记来创建布局。例如,ASP.NET 2.0 Login 控件使用 <table> 标记来控制用户名和密码输入字段的布局。

  创建可访问的脚本

  WCAG 和 508 节准则中包含的一个非常严格的限制与客户端脚本有关。根据 WCAG 1.0 准则中的优先级 1 检查点,要求:

  6.3 确保页在脚本、小程序或其他编程对象关闭或不受支持时是可用的。如果这是不可能的,请在可访问的替换页中提供等效信息。[优先级 1]

  508 节准则包含类似的要求:

  (l) 当页利用脚本语言来显示内容或者创建界面元素时,应该用可通过辅助性技术阅读的功能性文本标识由该脚本提供的信息。

  问题在于,多个 ASP.NET 控件要求具有客户端 JavaScript 才能正常工作。这方面的主要示例是 ASP.NET LinkButton 控件。LinkButton 控件使用 JavaScript 将包含该控件的表单提交给 Web 服务器。

  该问题没有很好的解决方案。如果需要生成能够满足所有可访问性准则的 Web 站点,则需要非常小心地使用客户端脚本。您可能需要避免使用某些依赖于 JavaScript 的 ASP.NET 控件,例如 LinkButton 控件。

  遗憾的是,在生成现代 Web 站点时,很难遵守该准则。这种假定似乎使得 Web 站点更像杂志而不是应用程序。现代 Web 站点倾向于包含动态的客户端内容。例如,很多房地产 Web 站点包含一个 JavaScript 按揭计算器。人们尚不清楚 JavaScript 按揭计算器的文本等效物应该是什么。

  验证页的可访问性

  与存在 XHTML 的完全自动化验证程序不同,并不存在完全自动化的可访问性验证程序。之所以不存在可访问性的自动化验证程序,原因在于判断页的可访问性需要人工解释。

  例如,为了使 Web 页可访问,该页中的每个图像都必须包含有意义的替换文本。目前,没有任何计算机能够确定一段文本是否具有与图像相同的含义。可访问性验证程序最多只能提供应该检查的事物的列表。

  Visual Studio .NET 2005(但不是 Visual Web Developer)包含可访问性检查器。可从工具栏中打开可访问性检查器。还可通过选择菜单选项 Tools、Check Accessibility 来打开它(参见图 13)。

  图 13. Visual Studio .NET 2005 可访问性检查器

  可访问性检查器提供了用于按照 WCAG 优先级 1 检查点、WCAG 优先级 2 检查点或 508 节准则验证 Web 站点的选项。可以通过打开“Error List”(依次选择菜单选项 View、Other Windows、Error List)来查看 Web 站点的验证结果。

  Visual Studio .NET 2005 可访问性检查器还提供显示可访问性问题的“手动检查列表”的选项。如果选择该选项,则每当验证 Web 站点的可访问性时,都会在 Error List 窗口中显示相同的可访问性问题静态列表。该检查列表包含无法通过可访问性检查器自动验证的问题。

  如果使用 Visual Web Developer 生成 Web 站点,则还可以检查 Web 页的可访问性。为此,需要使用某个联机可访问性检查器。下面的链接指向两个最流行的联机可访问性检查器:

  Bobby

  WAVE

  示例应用程序:可访问的 XHTML ASP.NET Web 站点

  在最后一节中,我们将从头到尾完整地生成一个 ASP.NET 2.0 Web 站点。本白皮书随附有该示例 Web 站点的源代码。您可以下载该示例 Web 站点的源代码,并且在 Visual Web Developer 或 Visual Studio .NET 2005 中打开该 Web 站点。

  我们的目标是创建一个完全符合标准的 Web 站点。该 Web 站点将通过 XHTML 1.0 Strict(甚至 XHTML 1.1)验证。而且,该 Web 站点还可供残疾人士访问。它将同时满足 508 节和 WCAG(优先级 1 和优先级 2)可访问性要求。

  我们将生成一个名为 Super Super Bookstore Web 站点的网上书店。我们将通过 Amazon 电子商务 Web 服务检索书店的所有书籍清单。Amazon 电子商务 Web 服务为我们提供了足够的免费示例数据,以供我们进行演练(有关 Amazon Web 服务的详细信息,请参阅 http://www.amazon.com/gp/aws/landing.html)。

  为简单起见,我们的 Web 站点仅由两个 ASP.NET 页组成:

  Default.aspx — 该页显示指定类别中的书籍的列表。

  Search.aspx — 该页使您能够搜索满足特定搜索条件的所有书籍。

  在幕后,该 Web 站点使用了 ASP.NET 2.0 框架的多项新功能。例如,该 Web 站点使用了一个母版页来创建公共页布局,并且使用了一个主题来创建公共页样式。最后,示例站点使用新的 GridView 和 ObjectDataSource 控件进行数据访问。

  访问 Amazon Web 服务

  Super Super Bookstore 使用一个名为 Amazon 的公共类来针对 Amazon 书目检索书籍信息并执行搜索。该类包含在清单 10 中。

  清单 10. Amazon.vb
Imports Microsoft.VisualBasic
Public Class Amazon
    Const SubscriptionId As String = "1CD1NYF3YQ830DG7AM02"
''' 
    ''' Attempts to get books in category from cache. 
    ''' If not in cache, call Amazon Web service
    ''' 
    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
    ''' 
    ''' Retrieves books in certain category from Web service
    ''' 
    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
    ''' 
    ''' Searches for books by calling Amazon Web service
    ''' 
    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
    ''' 
    ''' The Amazon Author property represents a list of authors.
    ''' Therefore, we create a comma separated list    
    ''' 
    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
    ''' 
    ''' Formats Amazon ListPrice into US currency
    ''' 
    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
    ''' 
    ''' Formats tooltip for the link to the book details
    ''' 
    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
    ''' 
    ''' If there is no book cover, we fall back to displaying our image
    ''' 
    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

  该类中的两个最重要的函数名为 GetBooksFromAmazon 和 SearchBooksFromAmazon.第一个函数从 Default.aspx 页中调用,以便按照类别显示书籍清单。第二个函数从 Search.aspx 页中调用,以便使用户能够搜索书籍。

  这两个函数都使用名为 AmazonServices 的 Web 服务代理类。该代理类是通过依次选择菜单选项 Web site、Add Web Reference 并且输入 URL http://soap.amazon.com/onca/soap?Service=AWSECommerceService 创建的。这是用于访问美国 Amazon 数据的正确的 URL.

  默认页

  Default.aspx 页显示书籍类别的列表,并且显示所选类别的匹配书籍的列表(参见图 14)。Default.aspx 页包含在清单 11 中。

  图 14. 默认页

  清单 11. Default.aspx
<%@ Page Language="VB" MasterPageFile="~/SiteMaster.master" 
  Title="Super Super Books" %>
<script runat="server">
    Sub Page_Load()
        Dim categoryIndex As Integer = 0
        If Not IsNothing(Request("index")) Then
            categoryIndex = Int32.Parse(Request("index"))
        End If
        MenuCategories.Items(categoryIndex).Selected = True
    End Sub
</script>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentBody" 
  Runat="Server">
    <h1>Book Listings</h1>
    <hr />
    <div id="leftColumn">
    <asp:Menu
        id="MenuCategories"
        ToolTip="Book categories menu"
        StaticMenuItemStyle-CssClass="menuNormal"
        StaticSelectedStyle-CssClass="menuSelected"
        Runat="server">
        <Items>
        <asp:MenuItem 
            Text="Arts and Photography"
            Value="1"
            NavigateUrl="~/Default.aspx?index=0" />
        <asp:MenuItem 
            Text="Biographies and Memoirs"
            Value="2"
            NavigateUrl="~/Default.aspx?index=1" />
        <asp:MenuItem 
            Text="Children's Books"
            Value="4" 
            NavigateUrl="~/Default.aspx?index=2" />
        <asp:MenuItem 
            Text="Computers and Internet"
            Value="5" 
            NavigateUrl="~/Default.aspx?index=3" />
        <asp:MenuItem 
            Text="Cooking, Food and Wine"
            Value="6" 
            NavigateUrl="~/Default.aspx?index=4" />
        <asp:MenuItem 
            Text="Science Fiction and Fantasy"
            Value="25" 
            NavigateUrl="~/Default.aspx?index=5" />
        </Items>
    </asp:Menu>    
    </div>
    <div id="middleColumn">
    <asp:GridView
        id="grdBooks"
        DataSourceID="srcBooks"
        AutoGenerateColumns="false"
        CssClass="books"
        HeaderStyle-CssClass="booksHeader"
        EmptyDataText="No matching results"
        Runat="server">
        <Columns>
        <asp:TemplateField HeaderText="Book Cover Image">
        <ItemTemplate>
            <asp:Image
                id="imgBook"
                ImageUrl='<%#Amazon.FormatBookCover(Eval("SmallImage.Url"))%>'
                AlternateText="Book cover image"
                Runat="server" />
        </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Book Information">
        <ItemTemplate>
            <h4><%#Server.HtmlEncode(Eval("ItemAttributes.Title"))%></h4>
            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>

  该页使用下列两个 ASP.NET 控件来显示书籍清单:Menu 控件和 GridView 控件。Menu 控件用来显示书籍类别的列表,而 GridView 控件用来显示书籍列表。

  GridView 控件被绑定到 ObjectDataSource 控件。ObjectDataSource 控件继而调用 Amazon 类中的 GetBooks() 方法来检索书籍列表。

  默认页的 XHTML 功能

  在生成 XHTML 页时,目标之一是将文档的结构与其表示形式截然分开。为了达到这一目标,不能在 Default.aspx 页中的任何 ASP.NET 控件上设置格式属性。页格式设置被封装在通过 ASP.NET 主题与该页相关联的外部样式表中。

  ASP.NET 2.0 主题使您可以更容易地遵循 Web 标准,因为它们将所有表示内容与页分开。示例站点包含一个名为 SiteTheme 的主题,该主题包含单个样式表。该主题自动使用 Web.Config 文件中的下列配置设置与每个页相关联。
 <pages 
      styleSheetTheme="SiteTheme"
      masterPageFile="SiteMaster.master" />

  您应该注意到,HTML 表没有用来创建页布局。尽管 XHTML 标准和可访问性标准都没有禁止您使用表来创建页布局,但这两个标准都建议您避免这样做。在示例站点中,页布局完全是由外部样式表确定的。页本身由两个 <div> 元素划分为两列。外部样式表包含确定这两个 <div> 元素位置的规则。

  最后,示例站点在提供页时使用内容协商。当使用能够理解 application/xhtml+xml MIME 类型的浏览器从 Web 站点请求页时,将以 MIME 类型提供该页;否则,将以 text/html 类型提供该页。

  内容协商是用 Global.asax 文件中的以下事件处理程序完成的。
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

  默认页的可访问性功能

  当无法提供 JavaScript 的文本等效内容时,WCAG 和 508 节可访问性准则都禁用客户端 JavaScript.为了满足这些准则,Default.aspx 页不依赖于客户端 JavaScript.即使您在浏览器中关闭 JavaScript,该页仍然能够正常工作。

  为了满足该要求,在实现菜单时必须完成额外的工作。默认情况下,ASP.NET Menu控件为每个菜单项呈现 JavaScript 以处理客户端单击事件。但是,当为菜单项提供了 NavigateUrl 属性时,该菜单项将不再使用 JavaScript.

  在示例站点中,为每个菜单项提供了一个指回到 Default.aspx 页的 NavigateUrl 属性。当您单击菜单项时,将重新加载 Default.aspx 页。Page_Load 事件处理程序用来检测单击了哪个菜单项,而且该子例程用当前菜单选择更新菜单。

  使用 Menu 控件的好处是,Menu 控件自动生成“跳过导航”链接。如果使用 Tab 键浏览 Default.aspx 页中的每个元素,会注意到(观察浏览器的状态栏)有一个跳过菜单内容的隐藏链接。Menu 控件使您能够自动满足 WCAG 和 508 节准则,该准则要求您提供相应的方法以跳过重复性的导航链接。

  搜索页

  搜索页包含一个表单,使 Web 站点的用户能够通过提供书籍作者、书名、书籍关键字或者通过提供复杂查询来搜索书籍(参见图 15)。查询的结果显示在 GridView 控件中。Search.aspx 页包含在清单 12 中。

  图 15. 搜索页

  清单 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>
            <h4><%#Server.HtmlEncode(Eval("ItemAttributes.Title"))%></h4>
            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 功能

  就像默认页一样,搜索页不包含任何表示性元素或属性。搜索页的样式和布局完全封装在通过 ASP.NET 主题与该页相关联的外部样式表中。

  同样,像默认页一样,搜索页使用内容协商。如果有人用能够识别 application/xhtml+xml MIME 类型的浏览器请求搜索页,则将以该 MIME 类型提供该页;否则,将以 text/html 类型提供该页。

  搜索页的可访问性功能

  搜索页包含一个表单。或者,更准确地说,该页包含被划分为两个子表单的单个表单。它包含一个“Quick Search”表单和一个“Power Search”表单。

  请注意,该表单用 HTML <fieldset> 标记划分为两个子表单。<fieldset> 标记使您能够将逻辑相关的表单元素组合在一起。可访问性准则要求您在处理复杂表单时使用 <fieldset> 标记(请参阅 WCAG 12.3)。

  而且,请注意,每个表单字段都与其标签显式关联。每个 ASP.NET 控件都包含一个指向其相应表单字段的 AssociatedControlID 属性。标签和字段之间的这些显式关联可以帮助屏幕阅读器的用户确定特定表单字段的用途。

  最后,请注意每个 Label 控件都分配了一个访问键。访问键使您无需使用鼠标就能够方便地浏览表单字段。例如,如果按 ALT+A,则可以输入作者的姓名。如果随后按 ALT+S,则会提交“Quick Search”表单,并且在 GridView 中显示结果。换句话说,无需触摸鼠标即可方便地执行搜索。

  如果按 ALT 键,则会自动显示访问键(参见图 16)。这通过 JavaScript 实现。请注意,在每个表单字段后面都会出现一个 <span> 标记。例如,Title 搜索字段是通过以下代码实现的。
<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>

  当按 ALT 键时,将执行客户端 JavaScript,并且显示 <span> 标记的内容。

  图 16. 搜索表单访问键

  您可能为该功能感到担心,因为按照可访问性准则,当 JavaScript 和样式表关闭时,该页必须能够继续工作(WCAG 准则 6)。幸运的是,当 JavaScript 和样式表都被禁用时,该页确实能够正常工作。在这种情况下,<span> 标记的内容不再隐藏,并且总是显示访问键(参见图 17)。

  图 17. 搜索表单适度降格

  母版页

  示例 Web 站点在幕后使用一个名为 SiteMaster.master 的 ASP.NET 2.0 母版页。母版页使您能够在 Web 站点的多个页中包含相同的内容,并创建相同的布局。母版页通过 Web.Config 文件中的以下配置设置与示例 Web 站点中的每个页相关联。
<pages 
      styleSheetTheme="SiteTheme"
      masterPageFile="SiteMaster.master" />

  母版页的内容包含在清单 13 中。

  清单 13. SiteMaster.master
<%@ Master Language="VB" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<script runat="server">
    ''' <summary>
    ''' Select style sheet to display
    ''' </summary>
    Sub Page_Load()
        If Not IsNothing(Request("large")) Then
            Profile.AccessibleStyleSheet = True
        End If
        If Not IsNothing(Request("normal")) Then
            Profile.AccessibleStyleSheet = False
        End If
        If Profile.AccessibleStyleSheet Then
            lnkAccessibleStyle.Visible = True
            lnkStyle.Text = "Normal Text Version"
            lnkStyle.NavigateUrl = Request.Path & "?normal=1"
        Else
            lnkAccessibleStyle.Visible = False
            lnkStyle.Text = "Large Text Version"
            lnkStyle.NavigateUrl = Request.Path & "?large=1"
        End If
    End Sub
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Super Super Books</title>
    <script type="text/javascript" 
      src="Scripts/AccessKeys.js"></script>
    <link id="lnkAccessibleStyle" 
        type="text/css" 
        rel="Stylesheet"
        href="~/Styles/Accessible.css" 
        runat="server" />
</head>
<body>
    <form id="form1" runat="server">
    <div id="content">
        <img id="SiteLogo" src="Images/SiteLogo.png" 
          alt="SSB Web site logo image" />
        <div id="banner">
            <asp:HyperLink
                id="lnkStyle"
                ToolTip="Modify the size of all text in this Web site"
                Runat="server" />
            <br />
            <asp:Menu
                id="MenuSite"
                ToolTip="Web site navigation menu"
                CssClass="menuSite"
                Orientation="Horizontal"
                StaticTopSeparatorImageUrl="Images/bullet.gif"
                Runat="server">
                <Items>
                <asp:MenuItem
                    Text="Home"
                    ToolTip="Navigate to home page"
                    NavigateUrl="~/Default.aspx" />
                <asp:MenuItem
                    Text="Search"
                    Tooltip="Navigate to search page"
                    NavigateUrl="~/search.aspx" />    
                </Items>
            </asp:Menu>
        </div>
        <hr />        
        <asp:contentplaceholder id="ContentBody" runat="server" />
        <hr />
        <a href="http://validator.w3.org/check?uri=referer"
            title="Explanation of XHTML 1.0 Conformance">
        <img
          src="http://www.w3.org/Icons/valid-xhtml10"
          alt="Valid XHTML 1.0 icon" class="icon"/></a>
        <a href="http://jigsaw.w3.org/css-validator/"
            title="Explanation of CSS Conformance">
        <img 
            src="http://jigsaw.w3.org/css-validator/images/vcss" 
            alt="Valid CSS icon" class="icon" /></a>
        <a href="http://www.w3.org/WAI/WCAG1AA-Conformance"
            title="Explanation of Level Double-A Conformance">
        <img height="32" width="88" 
            src="http://www.w3.org/WAI/wcag1AA"
            alt="Level Double-A conformance icon, 
            W3C-WAI Web Content Accessibility Guidelines 1.0" 
            class="icon" /></a>
    </div>
    </form>
</body>
</html>

  母版页的 XHTML 功能

  利用母版页,可以方便地为 Web 站点中的所有页提供正确的 DOCTYPE.SiteMaster.master 页包含 XHTML 1.0 Strict DOCTYPE.在母版页中指定 DOCTYPE 的好处是:如果您将来需要更改 Web 站点中所有页的 DOCTYPE,那么只需在一个位置更改它。例如,在不久之后的某一天,您可能希望迁移到 XHTML 1.1 Web 站点并修改 DOCTYPE 以反映所做的更改。

  母版页还包含一系列一致性标识语,它们出现在示例 Web 站点中每一页的页脚。一致性标识语通告人们该 Web 站点符合 XHTML、CSS 和 WCAG 1.0 Web 标准(参见图 18)。

  图 18. 一致性标识语

  母版页的可访问性功能

  示例 Web 站点中每一页的顶部都包含一个链接,可以使用该链接切换用于显示相应页的样式表。每一页都可以用下列两个版本之一进行显示:“普通文本”版本和“大型文本”版本。在选择“大型文本”版本以后,Web 站点中所有文本的大小都会有所增加以使可读性更强(参见图 19)。

  图 19. 大型文本大小

  用户只需要执行该选择一次。如果某个用户选择 Web 站点的“大型文本”版本,那么该首选项会被自动记录下来,并且每当该用户返回该 Web 站点时都会使用它。该功能是通过利用 ASP.NET 2.0 框架的另一项新功能实现的:配置文件。配置文件使您能够存储用户在多次访问 Web 站点时的设置。

  配置文件在 Web.Config 文件中定义。
<anonymousIdentification enabled="true"/>
    <profile>
      <properties>
        <add 
          name="AccessibleStyleSheet"
          type="Boolean" 
          defaultValue="false" />
      </properties>
    </profile>
  该配置文件定义一个名为 AccessibleStyleSheet 的布尔型属性。在 Web.Config 文件中定义该属性以后,就可以在任何 ASP.NET 页中,通过由 Page 类公开的 Profile 属性来读取或设置该属性。例如,要将 AccessibleStyleSheet 属性设置为值 True,并且显示该 Web 站点的“大型文本”版本,可以编写以下代码。

  Profile.AccessibleStyleSheet = true

  母版页包含用于选择 Web 站点的“普通文本”或“大型文本”版本的所有逻辑。HyperLink 控件用于使 Web 站点访问者能够执行该选择。在单击 HyperLink 以后,Page_Load 事件处理程序检测用户的选择并设置 AccessibleStyleSheet Profile 属性。

  如果使用 LinkButton 控件(而不是 HyperLink 控件),那么这里的代码会更加简单。但是,可访问性准则再一次禁止我们这样做,因为 LinkButton 控件依赖客户端 JavaScript.

  在选择“大型文本”版本以后,将向该页中添加对附加样式表的引用。该样式表包含单个规则。
body
{
    font-size: x-large;
}

  该规则将正文字体大小设置为值 x-large.因为主样式表(包含在 SiteTheme 文件夹中)中指定的所有字体都使用相对大小,所以修改正文元素的字体大小会自动增加 Web 站点中所有元素的字体大小。

  小结

  Web 标准是一个好东西。通过遵循 Web 标准,您能够以最少的工作,让最广大的受众访问您的 Web 站点。您的 Web 站点将与更多的浏览器兼容,并且它们更有可能在将来继续正常工作。

  ASP.NET 2.0 框架旨在使您能够轻松地生成满足 Web 标准的 Web 站点。该框架使您能够轻松地生成 XHTML Web 站点。在 ASP.NET 2.0 框架中,默认情况下,所有 ASP.NET 控件都呈现 XHTML 元素和属性。而且,Visual Studio .NET 2005 和 Visual Web Developer 允许您在生成页的过程中自动针对 XHTML 标准验证这些页的有效性。

  通过 ASP.NET 2.0 框架,还可以更容易地生成可被残疾人士访问的 Web 站点。ASP.NET 2.0 框架中的控件包含大量在设计时考虑了可访问性的新属性。例如,每个呈现图像的 ASP.NET 控件都使您能够呈现图像的替换文本。此外,所有新导航控件都包含“跳过导航”链接,以使残疾人士可以更容易地浏览 Web 站点。

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章