ASP.NET VERSIONS: 2.0
LANGUAGES: VB .NET
Where Was I?
Site navigation becomes much, much easier in ASP.NET 2.0.
By Dave Sussman
Here's the situation: In a vain attempt to get cheap flights to exotic locations I'm setting up a holiday company, allowing flights, car hire, and hotel reservations to be booked online. I want a simple menu structure and an easy-to-use menu. I can easily pick one of the numerous menu controls, but being the fickle person I am, it's likely I'll change the site design every few months. Today I want a tree-view type of menu, but tomorrow ... who knows? I might go for one of those cool horizontal menus. But I have two problems: How to store the menu structure and how to switch between menu styles without rewriting that structure.
With ASP.NET 1.x, I can't solve these problems without doing a considerable amount of work (did I also mention I'm lazy?). I can easily store my site structure, but I might want to move to a completely database driven site. I don't want to have to write reams of code just to display the menu. In short, I want an easy solution to my navigational woes.
ASP.NET 2.0 solves these problems, not by doing away with the need for third-party menu controls or by defining a fixed storage system for the menu organization, but by providing an infrastructure within which page - and control - developers can work. This infrastructure is based around Site Maps. As with much of ASP.NET 2.0, Site Maps are based on a configurable set of Providers, which make the data available to ASP.NET pages and controls. In the technical preview, the only Site Map Provider, which provides a simple hierarchical list of nodes, is for XML files. For example, the Site Map for my travel company could be:
url="flights.aspx?to=USA" /> url="flights.aspx?to=world" />
This simply defines a Home page, plus a Flights page with two subitems to narrow down the choice of flights. I'll have more menu items in reality, but this example gives you enough to understand the structure. All that's required to activate this as the Site Map for a site is for it to be placed in a file called app.SiteMap (this will be called web.sitemap by Beta 1) in the application root. (Note that it can be placed in locations other than the application root, though placing it in the app root is probably the most common approach.) However, on its own, this doesn't do anything visible; there's no automatic menu generation. To do that you'll need two things: a way to get the data from the Site Map Provider and a way to display it.
Fetching the data from the Site Map Provider is simple because there's a new server control that does it for you:
The SiteMapDataSource control reads the data from either the default Site Map provider (as shown), or, optionally, from a nondefault provider, and presents it to server controls for display. It's easiest to use it with a TreeView control:
The DataSourceId attribute should be set to the SiteMapDataSource control's ID. This is the new codeless data binding style for ASP.NET 2.0; simply link the data provider and data consumer controls together.
After creating a basic page with just two server controls and a bit of layout, I have a site (see Figure 1).
Figure 1. Here a TreeView control bound to a SiteMapDataSource control shows how the XML Site Map file is displayed in a hierarchical manner. The title attribute of the XML file is bound to the Text property of the tree node and the URL attribute is bound to the NavigateUrl property.
Where Am I?
Simple, isn't it? One XML file and two server controls and you have a workable menu. However, this isn't the end of my problems because I want each page to show where it's located in the menu hierarchy. This might not seem important because you have a tree of all the pages, but it's easy to collapse the tree branches, and then you're unsure of your place in the menu structure. Once again ASP.NET 2.0 comes to the rescue with a server control:
Because the SiteMapPath is integrated with the Site Map infrastructure (the overall infrastructure is referred to as Site Navigation), it automatically shows the navigational path. For example, when I create the Flights page and place a SiteMapPath control on it, my site looks like that shown in Figure 2.
Figure 2. This site has a SiteMapPath control added to the page. All levels in the navigational structure are visible with hyperlinks for levels other than the current.
By default the SiteMapPath control shows the root of the menu structure on the left and the current page (or node) on the right, but this is configurable. In fact, the entire layout is configurable; you can change the font of the nodes, how they're displayed, the separator, and so on. For example, consider this code (note that the font name property will be "font-names" in Beta 1):
NodeStyle-Font-Names="Castellar" NodeStyle-Font-Underline="true" NodeStyle-Font-Bold="true" RootNodeStyle-Font-Names="Forte" RootNodeStyle-Font-Bold="false" CurrentNodeStyle-Font-Names="Verdana" CurrentNodeStyle-Font-Size="12pt" CurrentNodeStyle-Font-Bold="true" CurrentNodeStyle-ForeColor="red" CurrentNodeStyle-Font-Underline="false" PathSeparator="
-::- " /> Here several attributes are
configured. The NodeStyle defines the style for all nodes, unless RootNodeStyle
and CurrentNodeStyle override it. In this code the RootNodeStyle and
CurrentNodeStyle are defined; therefore, NodeStyle only affects nodes between
the root node and the current node. The PathSeparator allows you to specify a
string to separate the nodes. The code I've just demonstrated produces the
image shown in Figure 3. If just changing the separator to a
string isn't suitable, you can use the PathSeparatorTemplate:
Figure 3. Here's the SiteMapPath control with set styles, which changes the look of each node.
PathSeparator=" -::- " />
Here several attributes are configured. The NodeStyle defines the style for all nodes, unless RootNodeStyle and CurrentNodeStyle override it. In this code the RootNodeStyle and CurrentNodeStyle are defined; therefore, NodeStyle only affects nodes between the root node and the current node. The PathSeparator allows you to specify a string to separate the nodes. The code I've just demonstrated produces the image shown in Figure 3.
If just changing the separator to a string isn't suitable, you can use the PathSeparatorTemplate:
The string used for the separator is ignored and the contents of the template are used instead. The nodes themselves can also be customized in this fashion by using the RootNodeTemplate, NodeTemplate, and CurrentNodeTemplate items. This gives you complete control over how the SiteMapPath control is displayed.
To use the SiteMapPath control, you don't have to use a SiteMapDataSource control, or indeed have any form of navigation. If the page is within the Site Map file that is associated with the default Site Map provider, the SiteMapPath will display correctly. ASP.NET automatically looks up the current page within the Site Map file if required and constructs the appropriate path.
At this point you still haven't written any code - it's all been done declaratively - but there's a time when you might want to. For example, the TreeView control might not be to your liking as a menu. You might want to have a similar indented view but use simple links for navigation, or perhaps you want a horizontal menu when you select a menu item and it shows the subitems. It's likely that there will be controls to do this for you, but there's no reason why you can't write them yourself. In the Alpha product, the Site Map is exposed to the developer through a Page-level property called SiteMap. In Beta 1, the Page-level property disappears; however, you call static properties on the SiteMap class instead. For example, you call SiteMap.CurrentNode.
The SiteMap object has properties such as RootNode. RootNode is an instance of a SiteMapNode; SiteMapNodes have properties such as HasChildNodes and ChildNodes to allow navigation through the Site Map itself. For instance, to build a simple vertical menu using links, you could iterate through the ChildNodes collection recursively, as I've shown in Figure 4.
Sub Page_Load(ByVal Sender As Object, _
ByVal E As EventArgs)
Sub BuildMenu(ByRef Level As Integer, _
ByVal nodes As SiteMapNodeCollection)
Level += 1
For Each n As SiteMapNode In nodes
If n.HasChildNodes Then
Level -= 1
Sub BuildMenuItem(ByVal Level As Integer, _
ByVal n As SiteMapNode)
For l As Integer* = 1 To Level
mymenu.Controls.Add(New LiteralControl(" "))
mymenu.Controls.Add(New LiteralControl(" "))
mymenu.Controls.Add(New LiteralControl(" "))
Dim h As New HyperLink
h.Text = n.Title
h.NavigateUrl = n.Url
Figure 4. This code is recursively iterating through the SiteMapNodes using the HasChildNodes property to see if a node has children. Child nodes are indented by three spaces. (An interesting aside: Note how a variable is declared in the beginning of the For loop. This is a new feature of VB .NET where variables can be declared in line with loops, avoiding the need for a separate declaration.)
Figure 4 shows how simple it is to access the Site Map structure and construct your own navigation. This example can be placed directly in a page, within a user control, or, more usefully, within a custom server control. Building any type of menu is simply a matter of rendering the content in a suitable style.
The Site Map XML file isn't limited to just two attributes (title and URL) for each node. In fact, there are seven attributes; the others are description, keywords, roles, siteMapFile, and provider. The keywords attribute is a comma-separated list that can be accessed through the SiteMapNode.Keywords property, allowing custom data to be stored along with the node. Note that by Beta 1 there will also be an Attributes property that contains unrecognized attribute/value pairs. This will be the preferred way of storing custom data within a siteMapNode element. The roles property is a comma-separated list of roles, and it is integrated with the new ASP.NET 2.0 authorization features so that only users with the required roles can see the node when viewing a site's navigation structure. (We don't want developers to think that the roles property is an alternative security mechanism from Url authZ or File authZ.) The siteMapFile attribute indicates the name of another Site Map file, allowing the site structure to be split across multiple files; this is useful for sites where multiple groups provide separate areas of a site. Finally, the provider attribute indicates the Site Map Provider for the node, allowing different providers to supply site structure.
The Provider model allows for a pluggable architecture. For example, you might have an existing site managed by Microsoft FrontPage and decide to use the FrontPage structure to provide the menuing. Writing a custom Site Map Provider allows you to do this. Simply create the provider and modify the configuration file.
The default configuration is held in the machine configuration file (machine.config):
provider ..." type="System.Web.XmlSiteMapProvider, ..." siteMapFile="app.sitemap" />
description="SiteMap provider ..."
This provider model is common to several areas on ASP.NET 2.0 and allows existing parts of ASP.NET to be added to, or replaced by, custom code.
None of what I've discussed here has been impossible previously, but the solution was never integrated. Each control uses its own form of storage, meaning that any changes always result in extra work. With the integrated site-navigation solution in ASP.NET 2.0, you now have simplicity, flexibility, and easy extendibility. Adding menuing to your site has never been easier.
Dave Sussman is a freelance author, trainer, and consultant specializing in Microsoft Web and Data technologies. He's been working closely with the ASP.NET team on version 2.0 for the last six months and has coauthored two new books about .NET 2.0. He can be contacted at mailto:email@example.com.