asp:feature
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:
<siteMap>
<siteMapNode title="Home"
url="default.aspx">
<siteMapNode
title="Flights" url="flights.aspx">
<siteMapNode
title="National"
url="flights.aspx?to=USA" />
<siteMapNode
title="International"
url="flights.aspx?to=world" />
</siteMapNode>
</siteMapNode>
</siteMap>
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:
<asp:SiteMapDataSource id="menuData"
runat="server" />
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:
<asp:TreeView DataSourceId="menuData"
runat="server" />
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:
<asp:SiteMapPath runat="server" />
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):
<asp:SiteMapPath runat="server"
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.
Figure 3. Here's the SiteMapPath control with set styles, which changes
the look of each node.
If just changing the separator to a
string isn't suitable, you can use the PathSeparatorTemplate:
<asp:SiteMapPath runat="server">
<PathSeparatorTemplate>
<img
src="arrow.gif" height="20" width="40" />
</PathSeparatorTemplate>
</asp:SiteMapPath>
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.
Customize
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.
<script runat="server">
Sub Page_Load(ByVal Sender As Object, _
ByVal E As
EventArgs)
BuildMenu(-1,
SiteMap.RootNode.ChildNodes)
End Sub
Sub BuildMenu(ByRef Level As Integer, _
ByVal
nodes As SiteMapNodeCollection)
Level += 1
For Each n As
SiteMapNode In nodes
BuildMenuItem(Level,
n)
If n.HasChildNodes Then
BuildMenu(Level,
n.ChildNodes)
End If
Next
Level -= 1
End Sub
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(" "))
Next
Dim h As New HyperLink
h.Text = n.Title
h.NavigateUrl = n.Url
mymenu.Controls.Add(h)
mymenu.Controls.Add(New LiteralControl("<br />"))
End Sub
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):
<siteMap defaultProvider="AspNetXmlSiteMapProvider"
enabled="true">
<providers>
<add
name="AspNetXmlSiteMapProvider"
description="SiteMap
provider ..."
type="System.Web.XmlSiteMapProvider, ..."
siteMapFile="app.sitemap" />
</providers>
</siteMap>
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:davids@ipona.com.