Walkthrough: Accessibility Guidelines for Using the ListView Control
This walkthrough shows how to use the ASP.NET ListView control in ways that help make a Web page accessible for people who use screen reader software. These techniques can help you meet the following Web Content Accessibility Guidelines (WCAG) 2.0 guidelines:
- Separating structure from presentation (WCAG guideline 1.3).
For more information about accessibility and WCAG 2.0, see Accessibility in Visual Studio and ASP.NET.
Prerequisites
In order to run this walkthrough, you must have the following:
Visual Studio 2010 or a later version.
An ASP.NET 4 Web site to which you can add a new Web page.
If you are building the Configuration System Browser application, this will be the Web site that you created in Walkthrough: Accessibility Guidelines for Using Image Controls, Menu Controls, and AutoPostBack.
The Configuration System Browser Application
This walkthrough is the third in a series that demonstrates techniques that can help an ASP.NET Web site conform to WCAG 2.0 accessibility guidelines. This series of walkthroughs creates a Web application that you can use to view ASP.NET configuration settings. If you want to do all of the walkthroughs, start with Walkthrough: Accessibility Guidelines for Using Image Controls, Menu Controls, and AutoPostBack. If you do not want to complete other walkthroughs in the series, follow the alternate instructions that are provided for a few of the steps. The same accessibility features are illustrated whether you choose to complete the walkthrough as part of the series or independently.
The Web page that you create in this walkthrough displays elements from one section in the machine.config configuration file. You can specify the section in a query parameter. For more information about configuration files and the section groups, sections, and elements that they contain, see ASP.NET Configuration Files. However, it is not necessary to be familiar with the ASP.NET configuration file system to use these walkthroughs as illustrations of how to create Web pages that comply with accessibility guidelines.
Security Note |
---|
The configuration information that is displayed by the application that you create in these walkthroughs is useful for developers, but you should not display it in a production Web site for security reasons. Configuration settings might include sensitive information that should be shown only to authorized users. |
A Visual Studio Web site project with source code is available to accompany this topic: Download.
Creating a Data Source
The data to display in the ListView control that you use in this walkthrough comes from the ASP.NET configuration system. In the following procedure, you will create a class that retrieves a specified Configuration object, selects a section from that object, and returns a list of elements that are contained in the section.
Note
This section of the walkthrough does not illustrate accessibility features specifically. It just provides data for you to work with in the GridView control.
To create a class that returns a list of configuration sections
If the Web site does not have an App_Code folder, in Solution Explorer right-click the project name, click Add ASP.NET Folder, and then click App_Code.
Right-click App_Code and then click Add New Item.
Under Installed Templates, click Visual Basic or Visual C#, and then click Class.
In the Name text box, enter SectionDataSource.vb or SectionDataSource.cs, and then click OK.
Delete all the code in the new class file.
In its place, insert the following code:
Imports Microsoft.VisualBasic Imports System.Web.Configuration Imports System.Configuration Imports System.Reflection ''' <summary> ''' Retrieves a list of elements in a section. ''' </summary> Public Class SectionDataSource Public Sub New() End Sub Public Function GetProperties(ByVal sectionName As String, ByVal virtualPath As String, ByVal site As String, ByVal locationSubPath As String, ByVal server As String) _ As List(Of ElementInfo) Dim sectionList As New List(Of ElementInfo)() Dim config As Configuration = WebConfigurationManager.OpenWebConfiguration( virtualPath, site, locationSubPath, server) Dim cs As ConfigurationSection = config.GetSection(sectionName) Dim sectionType As Type = cs.[GetType]() Dim sectionProperties As PropertyInfo() = sectionType.GetProperties() For Each rpi As PropertyInfo In sectionProperties If rpi.Name <> "SectionInformation" _ AndAlso rpi.Name <> "LockAttributes" _ AndAlso rpi.Name <> "LockAllAttributesExcept" _ AndAlso rpi.Name <> "LockElements" _ AndAlso rpi.Name <> "LockAllElementsExcept" _ AndAlso rpi.Name <> "LockItem" _ AndAlso rpi.Name <> "ElementInformation" _ AndAlso rpi.Name <> "CurrentConfiguration" Then Dim ei As New ElementInfo() ei.Name = rpi.Name ei.TypeName = rpi.PropertyType.ToString() If rpi.PropertyType.BaseType _ Is GetType(ConfigurationElement) Then ei.NameUrl = ("Element.aspx?Section=" _ & sectionName & "&Element=") + rpi.Name ei.Value = "Element" ElseIf rpi.PropertyType.BaseType _ Is GetType(ConfigurationElementCollection) Then ei.NameUrl = ("Element.aspx?Section=" _ & sectionName & "&Element=") + rpi.Name ei.Value = "Element Collection" Else Dim indexParms As ParameterInfo() = rpi.GetIndexParameters() If rpi.PropertyType Is GetType(IList) _ OrElse rpi.PropertyType Is GetType(ICollection) _ OrElse indexParms.Length > 0 Then ei.Value = "Collection" Else Dim propertyValue As Object = rpi.GetValue(cs, Nothing) ei.Value = IIf(propertyValue Is Nothing, "", propertyValue.ToString()) End If End If sectionList.Add(ei) End If Next Return sectionList End Function End Class Public Class ElementInfo Private _Name As String Public Property Name() As String Get Return _Name End Get Set(ByVal value As String) _Name = value End Set End Property Private _ItemName As String Public Property ItemName() As String Get Return _ItemName End Get Set(ByVal value As String) _ItemName = value End Set End Property Private _SectionName As String Public Property SectionName() As String Get Return _SectionName End Get Set(ByVal value As String) _SectionName = value End Set End Property Private _NameUrl As String Public Property NameUrl() As String Get Return _NameUrl End Get Set(ByVal value As String) _NameUrl = value End Set End Property Private _TypeName As String Public Property TypeName() As String Get Return _TypeName End Get Set(ByVal value As String) _TypeName = value End Set End Property Public ReadOnly Property TypeNameUrl() As String Get Return "https://msdn.microsoft.com/en-us/library/" & TypeName & ".aspx" End Get End Property Private _Value As String Public Property Value() As String Get Return _Value End Get Set(ByVal value As String) _Value = value End Set End Property Private _Index As Integer Public Property Index() As Integer Get Return _Index End Get Set(ByVal value As Integer) _Index = value End Set End Property End Class
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Configuration; using System.Configuration; using System.ComponentModel; using System.Collections; using System.Reflection; /// <summary> /// Retrieves a list of elements in a section. /// </summary> public class SectionDataSource { public SectionDataSource() { } public List<ElementInfo> GetProperties( string sectionName, string virtualPath, string site, string locationSubPath, string server) { List<ElementInfo> sectionList = new List<ElementInfo>(); Configuration config = WebConfigurationManager.OpenWebConfiguration( virtualPath, site, locationSubPath, server); ConfigurationSection cs = config.GetSection(sectionName); Type sectionType = cs.GetType(); PropertyInfo[] sectionProperties = sectionType.GetProperties(); foreach (PropertyInfo rpi in sectionProperties) { if (rpi.Name != "SectionInformation" && rpi.Name != "LockAttributes" && rpi.Name != "LockAllAttributesExcept" && rpi.Name != "LockElements" && rpi.Name != "LockAllElementsExcept" && rpi.Name != "LockItem" && rpi.Name != "ElementInformation" && rpi.Name != "CurrentConfiguration") { ElementInfo ei = new ElementInfo(); ei.Name = rpi.Name; ei.TypeName = rpi.PropertyType.ToString(); if (rpi.PropertyType.BaseType == typeof(ConfigurationElement)) { ei.NameUrl = "Element.aspx?Section=" + sectionName + "&Element=" + rpi.Name; ei.Value = "Element"; } else if (rpi.PropertyType.BaseType == typeof(ConfigurationElementCollection)) { ei.NameUrl = "Element.aspx?Section=" + sectionName + "&Element=" + rpi.Name; ei.Value = "Element Collection"; } else { ParameterInfo[] indexParms = rpi.GetIndexParameters(); if (rpi.PropertyType == typeof(IList) || rpi.PropertyType == typeof(ICollection) || indexParms.Length > 0) { ei.Value = "Collection"; } else { object propertyValue = rpi.GetValue(cs, null); ei.Value = propertyValue == null ? "" : propertyValue.ToString(); } } sectionList.Add(ei); } } return sectionList; } } public class ElementInfo { public string Name { get; set; } public string ItemName { get; set; } public string SectionName { get; set; } public string NameUrl { get; set; } public string TypeName { get; set; } public string TypeNameUrl { get { return "https://msdn.microsoft.com/en-us/library/" + TypeName + ".aspx"; } } public string Value { get; set; } public int Index { get; set; } }
The SectionDataSource class contains a GetProperties method that accepts parameters that specify which Configuration object to retrieve and which section to select. The method returns a collection of ElementInfo objects. The ElementInfo class is defined immediately following the SectionDataSource class.
Note
The Configuration Browser application used in these walkthroughs includes redundant data source code that could have been refactored into common classes and methods. However, the code was duplicated to make sure that each walkthrough can be done separately. This approach also minimizes the number of steps in parts of the walkthrough that are not directly relevant to accessibility.
Creating a Web Page that Displays Tabular Data
In this section you create a Web page that uses an ObjectDataSource control to provide data to a ListView control. The ObjectDataSource control calls the GetProperties method of the SectionDataSource object that you created in the previous procedure.
Note
Whether you are using the ObjectDataSource or some other method to retrieve data (for example, a database query using the SqlDataSource control or the LinqDataSource control), the methods for configuring the ListView control are the same.
The ListView control creates an HTML table element that has one row for each element. Each row has the name of the element in one column and the name of the class that is used to store settings for it in the second column.
To make the table more accessible for people who use screen reader software, you will configure the ListView control to include the following features in the HTML table that it generates:
A caption element describes the purpose of the table in a short heading.
A summary element provides a longer description of the purpose of the table.
thead and tbody elements distinguish the heading and body sections of the table.
th elements that have scope="col" attributes identify column header cells.
td elements that have scope="row" attributes identify row header cells.
In the following procedure you will create a Web page and add markup that displays the list of elements in a ListView control.
To create a Web page that displays a list of configuration sections
In Solution Explorer, right-click the project name and then click Add New Item.
The Add New Item dialog box appears.
Under Installed Templates, click Visual Basic or Visual C#, and then click Web Form.
In the Name text box, enter Section.aspx.
Make sure that the Place code in separate file check box is checked.
If you are adding this page to the Configuration System Browser application, make sure that the Select master page check box is checked. (If you are not adding this page to the Web site that you create in Walkthrough: Accessibility Guidelines for Using Image Controls, Menu Controls, and AutoPostBack, there might not be a master page.)
In the Add New Item dialog box, click OK.
If the Select a Master Page dialog box appears, click OK. There is only one master page, and it will already be selected.
In the @ Page directive, set the Title property to Configuration System Browser - Elements in a Section, as shown in the following example:
<%@ Page Language="VB" AutoEventWireup="true" Title="Configuration System Browser - Elements in a Section" CodeFile="Section.aspx.vb" MasterPageFile="~/Site.master" Inherits="Section" %>
<%@ Page Language="C#" AutoEventWireup="true" Title="Configuration System Browser - Elements in a Section" CodeFile="Section.aspx.cs" MasterPageFile="~/Site.master" Inherits="Section" %>
This title identifies the site and the page in the site. Setting the page title is required by accessibility guidelines.
Inside the Content element that is for the MainContent ContentPlaceHolder control, insert the following markup:
<h2> <asp:Label ID="HeadingLabel" runat="server" Text="Elements in Section [name]"> </asp:Label> </h2>
<h2> <asp:Label ID="HeadingLabel" runat="server" Text="Elements in Section [name]"> </asp:Label> </h2>
(If you creating a Web page that is not part of the Configuration System Browser application, insert the markup between the <div> and </div> tags.)
This markup adds a heading in a Label control so that the heading can be changed programmatically. The PreRender event handler for the outer ListView control will replace the string "[name]" in the heading with the actual name of the section that the page is displaying.
Below the markup that you inserted in the previous step, insert the following markup:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetProperties" TypeName="SectionDataSource"> <SelectParameters> <asp:QueryStringParameter Name="sectionName" QueryStringField="Section" Type="String" DefaultValue="system.web/httpHandlers" /> <asp:SessionParameter Name="virtualPath" SessionField="Path" Type="String" DefaultValue="" /> <asp:SessionParameter Name="site" SessionField="Site" Type="String" DefaultValue="" /> <asp:SessionParameter Name="locationSubPath" SessionField="SubPath" Type="String" DefaultValue="" /> <asp:SessionParameter Name="server" SessionField="Server" Type="String" DefaultValue="" /> </SelectParameters> </asp:ObjectDataSource>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetProperties" TypeName="SectionDataSource"> <SelectParameters> <asp:QueryStringParameter Name="sectionName" QueryStringField="Section" Type="String" DefaultValue="system.web/httpHandlers" /> <asp:SessionParameter Name="virtualPath" SessionField="Path" Type="String" DefaultValue="" /> <asp:SessionParameter Name="site" SessionField="Site" Type="String" DefaultValue="" /> <asp:SessionParameter Name="locationSubPath" SessionField="SubPath" Type="String" DefaultValue="" /> <asp:SessionParameter Name="server" SessionField="Server" Type="String" DefaultValue="" /> </SelectParameters> </asp:ObjectDataSource>
This markup creates an ObjectDataSource control that calls the GetProperties method of the SectionDataSource object. Parameters that are passed to the GetProperties method specify the name of the section and the specific Configuration object from which to retrieve the section.
The section name is retrieved from a query string field that is named "Section". If there is no query string, the default section is "system.web/httpHandlers". Values for the other four parameters are retrieved from session state. Values are placed in session state by another page in the Configuration System Browser application. Therefore, only the default values will be used if you are creating this walkthrough as an independent Web page.
Below the ObjectDataSource control, insert the following markup:
<div class="dataTable"> <asp:ListView ID="ListView1" runat="server" DataSourceID="ObjectDataSource1" onprerender="ListView1_PreRender" >
<div class="dataTable"> <asp:ListView ID="ListView1" runat="server" DataSourceID="ObjectDataSource1" onprerender="ListView1_PreRender" >
This markup creates a ListView control in a div element (you will add the closing tags for these elements later). The markup registers a handler for the control's PreRender event so that the EmptyDataTemplate object and the caption element of the HTML table can be customized using the name of the selected configuration section.
Below the markup that you inserted in the preceding step, insert the following markup:
<LayoutTemplate> <table class="listViewTable" width="100%" cellpadding="5" rules="all" border="1" summary="This table shows elements that are contained in the specified section."> <caption runat="server" ID="ElementTableCaption"> Elements in the system.web/httpHandlers Section </caption> <thead> <tr style=""> <th scope="col">Name</th> <th scope="col">Type</th> <th scope="col">Value</th> </tr> </thead> <tbody> <tr id="itemPlaceholder" runat="server"></tr> </tbody> </table> </LayoutTemplate>
<LayoutTemplate> <table class="listViewTable" width="100%" cellpadding="5" rules="all" border="1" summary="This table shows elements that are contained in the specified section."> <caption runat="server" ID="ElementTableCaption"> Elements in the system.web/httpHandlers Section </caption> <thead> <tr style=""> <th scope="col">Name</th> <th scope="col">Type</th> <th scope="col">Value</th> </tr> </thead> <tbody> <tr id="itemPlaceholder" runat="server"></tr> </tbody> </table> </LayoutTemplate>
This markup creates a LayoutTemplate object for the ListView control. The LayoutTemplate object specifies that the control will generate an HTML table that has the following features that relate to accessibility:
The caption element is a short description of the table's contents.
The summary attribute is a longer description of the table's contents.
thead and tbody elements distinguish the heading and body sections of the table.
th elements have scope="col" attributes that identify column-header cells.
Below the markup that you inserted in the preceding step, insert the following markup:
<ItemTemplate> <tr> <td scope="row" class="rowHeading"> <asp:HyperLink ID="HyperLink1" runat="server" Text='<%# Eval("Name") %>' NavigateUrl='<%# Eval("NameUrl") %>'> </asp:HyperLink> </td> <td> <asp:HyperLink ID="HyperLink2" runat="server" Text='<%# Eval("TypeName") %>' NavigateUrl='<%# Eval("TypeNameUrl") %>'> </asp:HyperLink> </td> <td> <asp:Label ID="Label1" runat="server" Text='<%# Eval("Value") %>' > </asp:Label> </td> </tr> </ItemTemplate>
<ItemTemplate> <tr> <td scope="row" class="rowHeading"> <asp:HyperLink ID="HyperLink1" runat="server" Text='<%# Eval("Name") %>' NavigateUrl='<%# Eval("NameUrl") %>'> </asp:HyperLink> </td> <td> <asp:HyperLink ID="HyperLink2" runat="server" Text='<%# Eval("TypeName") %>' NavigateUrl='<%# Eval("TypeNameUrl") %>'> </asp:HyperLink> </td> <td> <asp:Label ID="Label1" runat="server" Text='<%# Eval("Value") %>' > </asp:Label> </td> </tr> </ItemTemplate>
This markup creates an ItemTemplate object for the ListView control. The ItemTemplate object specifies that each data row will generate a tr element. To help with accessibility, cells in the first column are generated using scope="row" attributes because the first column contains the element name and functions as a row header.
Below the markup that you inserted in the preceding step, insert the following markup:
<EmptyDataTemplate> <asp:Label ID="NoElementsLabel" runat="server" Text="The [name] section does not contain any elements."> </asp:Label> </EmptyDataTemplate> </asp:ListView> </div>
<EmptyDataTemplate> <asp:Label ID="NoElementsLabel" runat="server" Text="The [name] section does not contain any elements."> </asp:Label> </EmptyDataTemplate> </asp:ListView> </div>
This markup creates an EmptyDataTemplate object for the ListView control and provides the closing tags for the ListView control and the div element that it is in. The EmptyDataTemplate object provides a custom message in case the selected configuration section does not contain any elements.
Updating the Table Caption and the EmptyDataTemplate Object
In the following procedure you will add code that puts the name of the selected
configuration section in the caption element of the HTML table. You also define what happens if no data is returned.
To update the caption and the EmptyDataTemplate object
Open the SectionGroup.aspx.vb or SectionGroup.aspx.cs file.
At the end of the using statements (Imports in Visual Basic) add the following code:
Imports System.Web.UI.HtmlControls
using System.Web.UI.HtmlControls;
This using or Imports statement is needed for a class that you will reference in the following steps.
Below the Page_Load method, add the following code:
Protected Sub ListView1_PreRender(ByVal sender As Object, ByVal e As EventArgs) Dim s As String = ObjectDataSource1.SelectParameters( "sectionName").DefaultValue.ToString() If Request.QueryString("Section") IsNot Nothing Then s = Request.QueryString("Section") End If HeadingLabel.Text = HeadingLabel.Text.Replace("[name]", s) Dim tableCaption As HtmlGenericControl = TryCast(ListView1.FindControl("ElementTableCaption"), System.Web.UI.HtmlControls.HtmlGenericControl) If tableCaption IsNot Nothing Then tableCaption.InnerText = tableCaption.InnerText.Replace("[name]", s) End If Dim noElementsLabel As Label = TryCast(ListView1.Controls(0).FindControl("NoElementsLabel"), Label) If noElementsLabel IsNot Nothing Then noElementsLabel.Text = noElementsLabel.Text.Replace("[name]", s) End If End Sub
protected void ListView1_PreRender(object sender, EventArgs e) { string s = ObjectDataSource1.SelectParameters["sectionName"].DefaultValue.ToString(); if (Request.QueryString["Section"] != null) { s = Request.QueryString["Section"]; } HeadingLabel.Text = HeadingLabel.Text.Replace("[name]", s); HtmlGenericControl tableCaption = ListView1.FindControl("ElementTableCaption") as System.Web.UI.HtmlControls.HtmlGenericControl; if (tableCaption != null) { tableCaption.InnerText = tableCaption.InnerText.Replace("[name]", s); } Label noElementsLabel = ListView1.Controls[0].FindControl("NoElementsLabel") as Label; if (noElementsLabel != null) { noElementsLabel.Text = noElementsLabel.Text.Replace("[name]", s); } }
This code puts the currently selected configuration section name in the following places:
The page heading.
The caption element of the HTML table if it is generated (the table is only generated if the selected configuration section has elements).
Markup that is generated from the EmptyDataTemplate object. This template generates HTML only if the selected configuration section does not have any elements.
Testing the Web Page
You can now test to verify that the table displays correctly and that accessible HTML is generated for it.
To test the page
In Solution Explorer, right-click Section.aspx and then click View in Browser.
You see a table that lists the elements in the system.web/httpHandlers section. Element names are displayed as hyperlinks that point to a page that you will create if you run a different walkthrough in this series (Walkthrough: Accessibility Guidelines for Using the ListView Control).
Section type names are displayed as hyperlinks that point to the MSDN documentation for the indicated type, as shown in the following illustration:
If you created the Web page as an independent page instead of as part of the Configuration System Browser application, the heading and the table contents are the same, but there will be no title bar or menu bar, and the table caption appears above the table.
In the browser, view the page source.
The following example shows the table elements that were added to enhance the table's accessibility for people who use screen readers.
<table class="listViewTable" width="100%" cellpadding="5" rules="all" border="1" summary="This table shows elements that are contained in the specified section."> <caption id="ctl00_MainContent_ListView1_ElementTableCaption"> Elements in the system.web/httpHandlers Section </caption> <thead> <tr style=""> <th scope="col">Name</th> <th scope="col">Type</th> <th scope="col">Value</th> </tr> </thead> <tbody> <tr> <th scope="row" class="rowHeading"> <a id=...>Handlers</a> </th> <td> <a id=...> System.Web...HttpHandlerActionCollection </a> </td> <td> <span id=...>Element Collection</span> </td> </tr> </tbody> </table>
Next Steps
In this walkthrough you used a ListView control to generate HTML elements and attributes that help make tabular data accessible for people who use screen reader software. Other walkthroughs in this series demonstrate other techniques that help your Web site conform to accessibility guidelines. The next walkthrough in the series is Walkthrough: Accessibility Guidelines for Using Nested ListView Controls. The other walkthroughs in the series are the following:
Walkthrough: Accessibility Guidelines for Using Image Controls, Menu Controls, and AutoPostBack
Walkthrough: Accessibility Guidelines for Using the GridView Control
Walkthrough: Accessibility Guidelines for Using Nested ListView Controls