Examining ASP.NET 2.0's Site Navigation - Part 5
By Scott Mitchell
A Multipart Series on ASP.NET 2.0's Site Navigation
This article is one in a series of articles on ASP.NET 2.0's site navigation functionality.
Part 1 - shows how to create a simple site map
using the default XML-based site map provider and how to display a
TreeView and SiteMapPath (breadcrumb) based on the site map data.
Part 2 - explores programmatically accessing
site map data through the SiteMap class;
includes a thorough discussion of the SiteMapPath (breadcrumb) control.
Part 3 - examines how to use base the site map's
contents on the currently logged in user and the authorization rules
defined for the pages in the site map.
Part 4 - delves into creating a custom site
map provider, specifically one that bases the site map on the website's physical, file system structure.
Part 5 - see how to customize the markup
displayed by the navigation controls, and how to create your own custom navigation UI.
Introduction
The site navigation features in ASP.NET 2.0 make it easy to define
a site map and implement common navigation UI elements, such as a breadcrumb, treeview, and menu. Due to its use of
the provide model, you can dictate how to serialize
the site map. ASP.NET 2.0 ships with a default implementation that serializes site map information to an XML-formatted file
(Web.sitemap, by default), but as we saw in Part 4
this logic can be customized to garner site map information directly from the file system or through
a SQL Server database table. Site navigation can even be configured
to use security trimming, which will remove those nodes in the site map for which the currently logged on user does not
have authorization to view.
The site map provider model and security trimming features are used to customize the set of site map nodes used by the navigation
Web controls, and afford a great deal of customization. However, there are times where we may want to customize the rendered
output of the navigation control based on the site map data. For example, maybe in our Menu control we want to
display an icon next to each menu item depending on some classification defined for the menu item's corresponding site map
node. Alternatively, the markup rendered by ASP.NET's built-in navigation controls may not suit our needs. Rather than
displaying a TreeView or Menu, we may want to show the site navigation information in a bulleted list. Such functionality is
possible by directly working with the SiteMap
class.
In this article we'll look at how to accomplish a hodgepodge of customizations when rendering the navigation UI controls.
Read on to learn more!
(This article does not explore setting up the site map or the basics of the SiteMap class or the site navigation
system. Refer to the earlier parts of this article series for such information.)
Adding Custom Attributes to the Site Map's Nodes
In earlier installments of this article series we discussed how a website's logical navigational structure is composed as a
site map, with the site map being a hierarchical collection of site map nodes. Each site map node is represented
in code as an instance of the SiteMapNode
class, which has properties like ChildNodes, ParentNode, Title, Url, and
others. The following diagram provides a pictoral representation of a site map; each block in the diagram is a node in the site map.
The properties of the SiteMapNode class indicate the information that can be stored about a node in the site map.
Rather than limit the site map nodes to just the predefined properties in the SiteMapNode class, Microsoft
added a generic collection to the class. With this generic collection, you can stuff any values into a SiteMapNode
instance and associate it with a string key. This generic collection can be accessed using the following syntax:
// C#
siteMapNodeInstance["key"] = value;
object value = siteMapNodeInstance["key"];
' VB
siteMapNodeInstance("key") = value
Dim value as Object = siteMapNodeInstance("key")
These custom values can then be programmatically accessed when working with the site map nodes (such as when displaying them
in a Menu), and decisions can be made based on these values.
Of course, before we can work with the custom values we must first assign the custom values to the SiteMapNode instances
that makeup the site map. How you accomplish this depends on the site map provider used. If you use the default site map provider
(XmlSiteMapProvider,
the one that stores the XML site map in an XML-formatted file), the custom values can be added as additional attributes to the
<siteMapNode> elements. For example, imagine that we wanted to include an image in each node in a TreeView
control, with the image for each node defined in the site map. We could add an imageUrl attribute to the
various <siteMapNode> elements like so:
With the imageUrl properties defined, we now need to customize the navigation UI control used to display this
information to include the image URL. Let's look at an example using the TreeView control (see Betrand
Le Roy's blog entry Site Map Menu with Icons for
an example of this code using the Menu control instead). Start by adding a SiteMapDataSource to the page and then a
TreeView, binding the TreeView to the SiteMapDataSource. Next, create an event handler for the TreeView's TreeNodeDataBound
event. This event fires once for each node in the TreeView after the item has been bound to the TreeView node.
This event handler passes in as its second parameter a TreeNodeEventArgs instance, which has a Node
property which returns the TreeView node being databound. The TreeView node has a DataItem property which returns
the object that's been bound to the TreeView. When using the TreeView to show site map data, the DataItem is the
particular SiteMapNode instance in the site map that was bound to the particular TreeView node.
The following code checks to make sure that the current SiteMapNode instance has a imageUrl custom
value defined. If it does, it sets the TreeView node's ImageUrl property to ~/Images/value,
where value is the value of the SiteMapNode's imageUrl attribute.
'VB
Protected Sub TreeViewID_TreeNodeDataBound(ByVal sender As Object,
ByVal e As TreeNodeEventArgs) Handles TreeViewID.TreeNodeDataBound
'Reference the underlying SiteMapNode object...
Dim nodeFromSiteMap As SiteMapNode = CType(e.Node.DataItem, SiteMapNode)
'If we have an imageUrl value, assign it to the TreeView node's ImageUrl property
If nodeFromSiteMap("imageUrl") IsNot Nothing Then
e.Node.ImageUrl = System.IO.Path.Combine("~/Images/", nodeFromSiteMap("imageUrl"))
End If
End Sub
// C#
protected void TreeViewID_TreeNodeDataBound(object sender,
TreeNodeEventArgs e)
{
// Reference the underlying SiteMapNode object...
SiteMapNode nodeFromSiteMap = (SiteMapNode) e.Node.DataItem;
// If we have an imageUrl value, assign it to the TreeView node's ImageUrl property
if (nodeFromSiteMap("imageUrl") != null)
e.Node.ImageUrl = System.IO.Path.Combine("~/Images/", nodeFromSiteMap["imageUrl"]);
}
Please forgive my lack of any semblance of artistic skill...
Creating a Custom Navigation User Interface
ASP.NET 2.0 ships with three controls that are commonly used to display information from the site map:
The SiteMapPath, which displays a "breadcrumb" showing the user their position in the site map hierarchy
(This control was discussed in detail in Part 2 of this article series.)
The TreeView, which displays all of the nodes in the site map in a collapsible treeview
The Menu, which displays all of the nodes in the site map in a vertically- or horizontally-aligned menu
To display site map data, both the TreeView and Menu controls should be bound to a SiteMapDataSource. The SiteMapDataSource
is a control that returns the site map data to the TreeView or Menu control as a SiteMapNodeCollection object,
which contains the hierarchy of nodes. The TreeView and Menu control then programmatically recurse this collection and
build up their menu items and tree nodes appropriately.
We can work with the SiteMapNodeCollection returned by the SiteMapDataSource on our own, either declaratively
or programmatically. Scott Guthrie has an example of doing just this
in his blog entry Data Tutorial #2: Building our
Master Page and Site Navigation Structure. He uses a Repeater to declaratively, using something similar to the following markup:
Here we've bound the Repeater siteMapAsBulletedList to the SiteMapDataSource control SiteMapDataSource1.
Keep in mind that the SiteMapDataSource returns a hierarchical SiteMapNodeCollection object. The Repeater, not being
a hierarchical data Web control, only enumerates the first "level" of site map nodes returns. That is, it doesn't drill
down into each SiteMapNode's children nodes. In this example the SiteMapDataSource's ShowStartingNode
is set to False, which causes the SiteMapNodeCollection to begin with the second "level" (Books, DVDs, Electroincs,
and Computers). That means for each SiteMapNode in this second level, the ItemTemplate will be
instantiated. (The root site map node (Home) is displayed with a bit of markup in the HeaderTemplate.)
With the above declarative markup, the following output will be rendered:
This is great for displaying the root site map node and the first level, but what if we want to show additional levels? We
can just add another Repeater in the first Repeater's ItemTemplate, setting that second Repeater's DataSource
to the ChildNodes property of the current node being used, like:
For C#, the DataSource property would be set like DataSource='<%# ((SiteMapNode) Container.DataItem).ChildNodes
%>'
The appearance of the bulleted list could be enhanced with a bit of CSS. See Scott
Guthrie's blog entry for an example of a much more attractive rendering of the bulleted list example from above.
The declarative approach is limited to only showing as many site map levels as there are embedded Repeaters. That is, if the
Romance node had additional sub-nodes, the above declarative markup wouldn't include them. In order to see them using declarative
markup we'd need to add a third Repeater to the second Repeater's ItemTemplate. However, that would not display
any site nodes that happened to be four levels deep....
To display an arbitrary depth of nodes in a bulleted list we'll need to programmatically invoke the SiteMapDataSource and
recursively iterate through the SiteMapNodeCollection object returned. The following code (in VB only, sorry!)
shows how to accomplish this. On the page there's a Label Web control with IDbulletedList, and
a SiteMapDataSource with IDsiteMapData and its ShowStartingNode property set to False.
(This code, as well as the earlier examples, are all avaiable for download at the end of this article...)
Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) _
Handles Me.Load
'Create the bulleted list
bulletedList.Text = _
String.Format("<ul><li><a href=""{0}"">{1}</a></li>{2}</ul>", _
SiteMap.RootNode.Url, SiteMap.RootNode.Title, _
DisplaySiteMapLevelAsBulletedList())
End Sub
Private Function DisplaySiteMapLevelAsBulletedList() As String
'Get the SiteMapDataSourceView from the siteMapData SiteMapDataSource
Dim siteMapView As SiteMapDataSourceView = _
CType(siteMapData.GetView(String.Empty), SiteMapDataSourceView)
'Get the SiteMapNodeCollection from the SiteMapDataSourceView
Dim nodes As SiteMapNodeCollection = _
CType(siteMapView.Select(DataSourceSelectArguments.Empty), _
SiteMapNodeCollection)
'Recurse through the SiteMapNodeCollection...
Return GetSiteMapLevelAsBulletedList(nodes)
End Function
Private Function GetSiteMapLevelAsBulletedList(ByVal nodes As _
SiteMapNodeCollection) As String
Dim output As String = String.Empty
For Each node As SiteMapNode In nodes
output &= String.Format("<li><a href=""{0}"">{1}</a>", _
node.Url, node.Title)
'Add any children levels, if needed (recursively)
If node.HasChildNodes Then
output &= String.Format("<ul>{0}</ul>", _
GetSiteMapLevelAsBulletedList(node.ChildNodes))
End If
output &= "</li>"
Next
Return output
End Function
Conclusion
In this article we saw how to further customize the site navigation system in ASP.NET. The nodes that comprise the site map
can have an arbitrary number of custom values associated with them, which can then be examined when working with the
site map data. A common example of this is using this custom information to make formatting or appearance-related decisions
when rendering a TreeView or Menu.
We also saw how to create our own navigation UI elements for greater control over the rendered markup. In this article we saw
how to have the site map rendered as a nested, bulleted list, both using declarative and programmatic techniques.
A Multipart Series on ASP.NET 2.0's Site Navigation
This article is one in a series of articles on ASP.NET 2.0's site navigation functionality.
Part 1 - shows how to create a simple site map
using the default XML-based site map provider and how to display a
TreeView and SiteMapPath (breadcrumb) based on the site map data.
Part 2 - explores programmatically accessing
site map data through the SiteMap class;
includes a thorough discussion of the SiteMapPath (breadcrumb) control.
Part 3 - examines how to use base the site map's
contents on the currently logged in user and the authorization rules
defined for the pages in the site map.
Part 4 - delves into creating a custom site
map provider, specifically one that bases the site map on the website's physical, file system structure.
Part 5 - see how to customize the markup
displayed by the navigation controls, and how to create your own custom navigation UI.