Navigation
The importance of good navigation on a site cannot be underestimated. It doesn't matter how great your site looks or how well it was developedif it's hard to navigate, users won't like using it. It's easy to see how seriously navigation is taken just by looking at the number of menu controls that have been written since ASP.NET 1.0 was releasedthere are now controls that use tree views, vertical expansion, horizontal layouts, flashy graphics, and so on.Providing a good menu isn't the end of site navigation because it's important to ensure visitors know where they are within the site hierarchy. Too often we see sites with pages three or four levels deep within the menu structure, but when we navigate to those pages there's no indication of where we are. We are left wondering how to navigate back up the structure; at worst, we have to go back to the home page to navigate down again.
Site Maps
There are plenty of ways to implement navigation on a site, but none that are an intrinsic part of ASP.NET 1.x. With ASP.NET 2.0, there are controls and configuration files for providing a set way to define site structure and techniques for displaying the navigation information and extracting the current navigation path.Like the rest of ASP.NET, the architecture for navigation has been broken down into logical parts, allowing customization. First, there is a configurable provider supplying the site map information, and then a set of controls that can take advantage of the data supplied by the provider. The provider not only exposes the site structure to other controls but also keeps track of the current navigation, allowing pages to identify where in the hierarchy they are. The entire structure and the current details can be exposed to users by binding controls to the provider. This pluggable architecture means that data defining the structure of a site can come from any data sourcethe site map provider is the link between the data and the navigation within a site.
Site Map Providers
A site map provider is a data provider that exposes the site structure by way of a set interface. Site maps are pluggable within the application configuration file, within the system.web section. The syntax for this section is shown in Listing 5.9.
Listing 5.9 Site Map Configuration Syntax
<siteMap
defaultProvider="string"
enabled="[true|false]">
<providers>
<add
name="string"
description="string"
provider-specific-configuration />
<remove
name="string" />
<clear>
</providers>
</siteMap>
The attributes for the siteMap element are shown in Table 5.1.The attributes for the providers element are shown in Table 5.2.
Attribute | Description |
---|---|
defaultProvider | The name of the default provider. This should match one of the names supplied in the providers section. |
enabled | A Boolean value indicating whether or not site maps are enabled. |
Attribute | Description |
---|---|
name | The name of the site map provider. |
description | A description of the provider. |
type | A string containing the full .NET type of the provider. |
Attribute | Description |
---|---|
siteMapFile | The name of the XML file containing the site structure. The filename is configured as app.SiteMap. |
Site Map Configuration Files
The XmlSiteMapProvider defines a set schema for the app.SiteMap file, as shown in Listing 5.10.
Listing 5.10 XmlSiteMapProvider Schema
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<xs:element name="siteMap">
<xs:complexType>
<xs:sequence>
<xs:element ref="siteMapNode"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="siteMapNode">
<xs:complexType>
<xs:sequence>
<xs:element ref="siteMapNode" minOccurs="0"
MaxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="url" type="xs:string"/>
<xs:attribute name="title" type="xs:string"/>
<xs:attribute name="description" type="xs:string"/>
<xs:attribute name="keywords" type="xs:string"/>
<xs:attribute name="roles" type="xs:string"/>
<xs:attribute name="SiteMapFile" type="xs:string"/>
<xs:attribute name="Provider" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:schema>
This defines a structure consisting of a root siteMap element, with the site structure being contained by siteMapNode elements. There has to be one top-level siteMapNode element, and within that can be any number of siteMapNode elements of any depth. The attributes for the siteMapNode element are shown in Table 5.4.The use of SiteMapFile allows the site map information to be split among different sources. This is especially useful when different divisions supply sections of a corporate siteeach part of the site map can be authored independently and even stored in different providers.Listing 5.11 shows a sample site map file. To create one within Visual Studio .NET "Whidbey," you simply create a new XML file and call it app.SiteMapthere isn't a template for this.
Attribute | Description |
---|---|
url | The URL to be used to navigate to the node. This must be unique within the entire site map file. |
title | The title of the node. |
description | A description of the node. |
keywords | Keywords used to describe the node. Multiple keywords can be separated by semicolons (;) or commas (,). |
roles | A list of roles allowed to view the node. Multiple roles can be separated by semicolons (;) or commas (,). |
SiteMapFile | An external file containing additional siteMap nodes. |
Provider | The name of the site map provider that will supply additional nodes specified in SiteMapFile. |
Listing 5.11 Sample app.SiteMap File
<siteMap>
<siteMapNode title="Home"
description="Home"
url="SiteMaps.aspx?id=1">
<siteMapNode title="Sales"
description="The Sales Site"
url="SiteMaps.aspx?id=2">
<siteMapNode title="Customers"
url="SiteMaps.aspx?id=3"/>
<siteMapNode title="Products"
url="SiteMaps.aspx?id=4/>
<siteMapNode title="Region"
url="SiteMaps.aspx?id=5"/>
<siteMapNode title="Futures"
url="SiteMaps.aspx?id=6"/>
</siteMapNode>
<siteMapNode title="Research"
description="The Research Site"
url="SiteMaps.aspx?id=7">
<siteMapNode title="Widgets"
url="SiteMaps.aspx?id=8"/>
<siteMapNode title="Doodads"
url="SiteMaps.aspx?id=9"/>
<siteMapNode title="Thingies"
url="SiteMaps.aspx?id=10" />
</siteMapNode>
</siteMapNode>
</siteMap>
This provides the following structure for the site:
Home
Sales
Customers
Products
Region
Futures
Research
Widgets
Doodads
Thingies
Using a Site Map File
Once the structure of your site is defined in the site map file, you then need a way to make use of it. For this you use a SiteMapDataSource control, which provides data access to the site map data, and then a control to display that data. From within Visual Studio .NET "Whidbey," you can just drag a SiteMapDataSource control onto the design surfacethere's no need to set any properties because it defaults to using app.SiteMap as its data source. You can then drag a TreeView control onto the page and set its DataSourceId property to the id of the SiteMapDataSource control. Figure 5.11, for example, shows how our Big Corp site could be constructed using a single menu.
Figure 5.11. A TreeView bound to a SiteMapDataSource

Other controls can be bound to site map data, but in the Technology Preview release, the TreeView provides the best option because of its hierarchical display. It's possible that a dedicated menu control will appear in future versions.
Site Maps in Depth
At its simplest, the use of site maps needs nothing more than has been discussed above, but there's actually more to them. Adding a SiteMapData Source control to a page provides all that's needed for site map handling, but there are properties that allow for more control over how the data is supplied from the SiteMapDataSource to controls. For example, the syntax of the SiteMapDataSource control is shown in Listing 5.12.
Listing 5.12 SiteMapDataSource Syntax
<asp:SiteMapDataSource id="String" runat="server"
FlatDepth="Integer"
SiteMapProvider="String"
SiteMapViewType="[Flat|Path|Tree]"
StartingDepth="Integer"
StartingNodeType="[Current|Parent|Root]"
StartingNodeUrl="String"
/>
The attributes are shown in Table 5.5.
Attribute | Description |
---|---|
FlatDepth | A number that defines how many nodes deep in the hierarchical structure are flattened. The default is -1, which indicates all nodes. |
SiteMapProvider | The name of the provider supplying the site map data. |
SiteMapViewType | One of the SiteMapViewType enumerations, whose values are:Flat: Indicates that the data is presented without any structure.Path: Indicates the data presented is a list of nodes between the root node and the current node.Tree: Indicates the data is presented in the same hierarchical structure as the original data source. Binding nonhierarchical controls when using this mode shows only the top level of the hierarchy. This is the default value. |
StartingDepth | Specifies the node depth at which to start representing data. The default is 0, which is the first node. |
StartingNodeType | One of the SiteMapNodeType enumerations, whose values are:Current: Indicates the node that represents the currently displayed page.Parent: Indicates the parent node of the currently displayed page.Root: Indicates the root node. This is the default value. |
StartingNodeUrl | The URL of the node at which to start representing data. |
Listing 5.13 Sample Site Map Displays
<asp:SiteMapDataSource id="SiteDataFlat" runat="server"
SiteMapViewType="Flat" />
<asp:SiteMapDataSource id="SiteDataPath" runat="server"
SiteMapViewType="Path" />
<asp:SiteMapDataSource id="SiteDataTree" runat="server"
SiteMapViewType="Tree" />
<table border="1" width="50%">
<tr>
<td>Flat</td>
<td>Path</td>
<td>Tree</td>
</tr>
<tr>
<td>
<asp:TreeView runat="server"
DataSourceId="SiteDataFlat" />
</td>
<td>
<asp:TreeView runat="server"
DataSourceId="SiteDataPath" />
</td>
<td>
<asp:TreeView runat="server"
DataSourceId="SiteDataTree" />
</td>
</tr>
<tr>
<td>
<asp:ListBox runat="server"
DataSourceId="SiteDataFlat" />
</td>
<td>
<asp:ListBox runat="server"
DataSourceId="SiteDataPath" />
</td>
<td>
<asp:ListBox runat="server"
DataSourceId="SiteDataTree" />
</td>
</tr>
</table>
The initial display is shown in Figure 5.12. By default the TreeView binds the title attribute of the site map to the Text property and the url attribute to the NavigateUrl property, giving you an instant menu control. For the ListBox the title attribute is bound to both the DataTextField and DataValueField properties.
Figure 5.12. Initial site map display

You can see from this that when in Tree mode, the TreeView displays as you expect it to. However, the Flat view shows how all nodes (at whatever level) are shown. Nodes with children are expandable in the normal TreeView style. For the Path mode, nothing is shown because we haven't yet performed any navigation.For the ListBox control, the Tree mode shows only the first node because it is a naturally flat control and can deal only with a single level of the hierarchy. However, in Flat mode you see all nodes because they have been flattened and therefore appear at the top level.The results of navigating to the Sales Region page are shown in Figure 5.13.
Figure 5.13. Navigating to a page

Here you can see that the Tree and Flat views are essentially the same as their initial settings, and the Path view has now been filled. In the Path view column, note that the TreeView contains the same data as the Tree mode, but the ListBox shows only those nodes in the path between the root node and the selected node.
Flattening Nodes
Setting the FlatDepth property limits the depth of the nodes that are flattened. For example, on the left in Figure 5.14 you see a FlatDepth of 1, so only one node is flattened. On the right a FlatDepth of 2 causes three nodes to be flattenedthe top node, plus its two child nodes.
Figure 5.14. Results of setting different FlatDepth properties

Setting the Starting Depth
The StartingDepth property indicates at which node level the data is displayed from, and it affects all three modes (Flat, Path, and Tree). For example, setting the StartingDepth to 1 (where no FlatDepth is set) is shown in Figure 5.15.
Figure 5.15. Results of setting the StartingDepth property to 1

Here you can see that only nodes from level 1 down are shown and only those from our navigation pointremember, the SiteMapData Source keeps track of where we are in the navigational structure.
Setting the Starting Node Type
The StartingNodeType property identifies what type of node to start displaying data from. For example, setting this property to Parent would give the results in Figure 5.16. We've navigated to the Region node, a node that is underneath Sales. In the ListBox, for the Flat view we see only the Parent of the current node, plus its children; for the Path view, we see only the current node and its parent; and for the Tree view, we see only the parent.
Figure 5.16. Results of setting the StartingNodeType property to Parent

Setting the StartingNodeType to Current means that only the current node is displayed, as shown in Figure 5.17. Setting the CurrentNodeType to Root means that the current node becomes the root node as far as displaying the node hierarchy is concerned.
Figure 5.17. Results of setting the StartingNodeType property to Current

Setting the Start Node URL
The StartingNodeUrl property allows us to set the starting point, given the URL of a page. Since URLs in the site map file must be unique, this allows us to navigate to a given node knowing only the URL, rather than its location in the hierarchy.
Showing a Navigation Path
When a site map provides the navigational architecture for a site, it's easy to add features that take advantage of this. With a hierarchy three deep or more, it has always been hard for users to remember where they are within that structure, so the idea of breadcrumbs came about, laying a trail of the path back to the root of the site.With ASP.NET 2.0 this is simple: We have the SiteMapPath control, which automatically hooks into the site map architecture, so all you have to do is drop it on a page, as shown in Figure 5.18.
Figure 5.18. The SiteMapPath control

This figure shows the default implementation, just from adding the following line of code to our page:
<asp:SiteMapPath runat="server" />
To use the SiteMapPath control you don't need a SiteMapDataSource control because it works directly with the site map provider.The current node is shown as simple text, and parent nodes are shown as hyperlinks, allowing quick navigation up the tree. The text for the tooltip is set to the description attribute from the site map file.There are plenty of ways to customize this control to fit it to your site. The syntax is shown in Listing 5.14.
Listing 5.14 SiteMapPath Syntax
<SiteMapPath id="String" runat="server"
CurrentNodeStyle="Style"
CurrentNodeTemplate="Template"
HoverNodeStyle="Style"
NodeStyle="Style"
NodeTemplate="Template"
ParentLevelsDisplayed="Integer"
PathDirection="[CurrentToRoot|RootToCurrent]"
PathSeparator="String"
PathSeparatorStyle="Style"
PathSeparatorTemplate="Template"
RenderCurrentNodeAsLink="Boolean"
RootNodeStyle="Style"
RootNodeTemplate="Template"
ShowToolTips="Boolean"
SiteMapProvider="String"
/>
These are just the unique properties for this control, described in Table 5.6. All other properties are inherited and are described in the documentation.
Property | Description |
---|---|
CurrentNodeStyle | Sets or returns the Style object that defines how the current node is displayed. |
CurrentNodeTemplate | Sets a Template, allowing customization of how the current node is displayed. |
NodeStyle | Sets or returns the Style to be used for nodes. |
NodeTemplate | Sets a Template, allowing customization of how a node is displayed. |
ParentLevelsDisplayed | Sets or returns the number of parent levels displayed. By default all parent levels are displayed. |
PathDirection | Gets or sets the direction in which the nodes are displayed. This can be one of the PathDirection enumerations, whose values are:CurrentToRoot: The current node is shown on the left, and child nodes are shown to the right.RootToCurrent: The current node is shown on the left, and parent nodes are shown on the right. This is the default value. Setting the direction has no effect on the separator between nodes. |
PathSeparator | Sets or returns a string to be used as a separator between nodes. This is replaced by the contents of the PathSeparatorTemplate if present. The default is >. |
PathSeparatorStyle | Sets or returns the Style to be used for the PathSeparator string. |
PathSeparatorTemplate | Sets a Template, allowing customization of the node separator. |
RenderCurrentNodeAsLink | Sets or returns a Boolean that indicates whether or not the current node is rendered as a hyperlink. The default value is False. |
RootNodeStyle | Sets or returns the Style to be used for the root node. Any Style values set here override those set in the NodeStyle property. |
RootNodeTemplate | Sets a Template, allowing customization of the root node. |
ShowToolTips | Sets or returns a Boolean indicating whether or not tooltips are shown on hyperlinks. |
SiteMapProvider | Sets or returns a string indicating the site name of the provider supplying the site map data. |
Listing 5.15 Setting the SiteMapPath Properties
<asp:SiteMapPath ID="SiteMapPath1" runat="server"
NodeStyle-Font-Name="Franklin Gothic Medium"
NodeStyle-Font-Underline="true"
NodeStyle-Font-Bold="true"
RootNodeStyle-Font-Name="Symbol"
RootNodeStyle-Font-Bold="false"
CurrentNodeStyle-Font-Name="Verdana"
CurrentNodeStyle-Font-Size="10pt"
CurrentNodeStyle-Font-Bold="true"
CurrentNodeStyle-ForeColor="red"
CurrentNodeStyle-Font-Underline="false">
<PathSeparatorTemplate>
<asp:Image runat="server" ImageUrl="arrow.gif"/>
</PathSeparatorTemplate>
</asp:SiteMapPath>
This defines styles for the nodes and a separator that uses a custom image. The results are shown in Figure 5.19.
Figure 5.19. A customized SiteMapPath control

Notice that the root node is underlined even though it wasn't specified as part of the RootNodeStylethe underlining was inherited from the NodeStyle.
SiteMapPath Events
The SiteMapPath is built dynamically from the data held by the underlying site map provider. As the tree of nodes is traversed, each item in the path, from the root node to the current node, is added to the Controls collection of the SiteMapPath control. Like other collection controls (such as the DataList or DataGrid), two events are fired when items are either created (ItemCreated) or bound (ItemDataBound) to the SiteMapPath. The signature for these events is the same:
Sub eventName(Sender As Object,
E As SiteMapNodeItemEventArgs)
SiteMapNodeItemEventArgs has one property, Item, which returns an object of type SiteMapNodeItem, which in turn has three properties, as described in Table 5.7.
Property | Description |
---|---|
ItemIndex | The zero-based index number of the item being added. |
ItemType | The type of node being added, which can be one of the SiteMapNodeItemType enumerations:Current: Indicates the current node (page) within the navigation path.Parent: Indicates a parent of the current node. All nodes between the current node and the root node are parent nodes.PathSeparator: Indicates a separator between nodes.Root: Indicates the root node of the navigation path. |
SiteMapNode | The SiteMapNode that represents the node being added to the SiteMapPath. |
Listing 5.16 SiteMapPath ItemCreated Event
<%@ Page %>
<head runat="server" id="PageHead" />
<script runat="server">
Sub ItemCreated(Sender As Object,
E As SiteMapNodeItemEventArgs)
If E.Item.ItemType = _
SiteMapNodeItemType.Current Then
Dim sb As New StringBuilder()
Dim s As String
For Each s In E.Item.SiteMapNode.Keywords
sb.Append(s)
sb.Append(" ")
Next
Dim ctl As New HtmlGenericControl("meta")
ctl.Attributes.Add("name", "keywords")
ctl.Attributes.Add("content", sb.ToString())
PageHead.Controls.Add(ctl)
End If
End Sub
</script>
<form runat="server">
<asp:SiteMapPath runat="server"
onItemCreated="ItemCreated"/>
</form>
The SiteMapNode Object
When the site map is constructed from the data provider, each of the items is built into a SiteMapNode object. These in turn are added to a SiteMapNodeCollection, which therefore represents all pages within a Web site. The SiteMapNode object provides links to nodes up, down, and next to it in the hierarchy and thus can be used to build a treelike structure. As shown in Listing 5.16, the ItemCreated event of the SiteMapPath object allows access to the SiteMapNode, which has the properties detailed in Table 5.8.
Property | Description |
---|---|
Attributes | Returns a collection of additional attributes applicable to the node. For the XmlSiteMapProvider, the list of attributes maps to existing properties, namely Title, Description, Url, Attributes, Roles, and Keywords. |
ChildNodes | If applicable, returns a SiteMapNodeCollection containing child nodes of the current node. |
Description | Returns the description of the current node. |
HasChildNodes | Indicates whether or not the current node has any child nodes. |
Keywords | Returns an IList containing keywords for the current node. |
NextSibling | Returns the next node on the same level as the current node, or returns null (Nothing in Visual Basic) if there is no next node. |
ParentNode | Returns the parent node of the current node, or returns null (Nothing in Visual Basic) if there is no parent node (i.e., the current node is the root node). |
PreviousSibling | Returns the previous node on the same level as the current node, or returns null (Nothing in Visual Basic) if there is no previous node. |
Roles | Returns an IList containing the roles applicable to the current node. |
RootNode | Returns the root node. |
Title | Returns the title of the current node. |
Url | Returns the URL of the current node. |
Method | Description |
---|---|
GetAllNodes | Returns a SiteMapNodeCollection containing all child nodes of the current node. |
GetDataSourceView | Returns a SiteMapDataSourceView, which is a view of the underlying site map data. This is useful for control developers who wish to interface to the site map architecture. |
IsDescendantOf | Indicates whether or not the current node is a descendent of a supplied node. |
Accessing the Site Map at Runtime
So far we've seen the site map be used by controls, but it can also be accessed directly because it is exposed through a static page property called SiteMap. For example, to access the current node within the site map, you can use the following code:
Dim currNode As SiteMapNode
currNode = SiteMap.CurrentNode
This means that even if you aren't using a SiteMapPath control, you can easily build links pointing back to the hierarchy, as shown in Listing 5.17.
Listing 5.17 Using the SiteMap Property of the Page
<script runat="server">
Sub Page_Load(Sender As Object, E As EventArgs)
ParentLink.NavigateUrl = SiteMap.CurrentNode.ParentNode.Url
End Sub
</script>
<form runat="server">
<asp:HyperLink id="ParentLink" Text="Go Back" />
</form>
Table 5.10 details the properties of the SiteMap class.
Property | Description |
---|---|
CurrentNode | Returns a SiteMapNode object representing the current page. |
Provider | Returns the site map provider. |
Providers | Returns a collection (SiteMapProviderCollection) of all site map providers. |
RootNode | Returns a SiteMapNode object representing the root node. |