Examining ASP.NET 2.0's Site Navigation - Part 4By Scott Mitchell
The goal of ASP.NET's site navigation feature is to allow a developer to specify a site map that describes his website's logical structure. A site map is constructed of an arbitrary number of hierarchically-related site map nodes, which typical contain a name and URL. The site navigation API, which is available in the .NET Framework via the
SiteMapclass, has properties for accessing the root node in the site map as well as the "current" node (where the "current" node is the node whose URL matches the URL the visitor is currently on). As discussed in Part 2 of this article series, the data from the site map can be accessed programmatically or through the navigation Web controls (the SiteMapPath, TreeView, and Menu controls).
The site navigation features are implemented using the provider
model, which provides a standard API (the
SiteMap class) but allows developers to plug in their own
implementation of the API at runtime. ASP.NET 2.0 ships with a single default implementation,
with which the developer can define the site map through an XML file (
Web.sitemap); Part 1
of this article series looked at defining this XML file. However, our site's structure might already be specified by existing
database data, or perhaps by the folders and files that makeup our website. Rather than having to mirror the database or
file system structure in a
Web.sitemap file, we can create a custom provider that exposes the database
or file system information as a site map.
Thanks to the provider model we can provide a custom implementation of the site navigation subsystem, but one that still
is accessible through the
SiteMap class. In essence, with a custom provider the
and navigation Web controls will work exactly as they did with the
XmlSiteMapProvider. The only difference will
be that the site map information will be culled from our own custom logic, be it from a database, a Web service, the file system,
or from whatever data store our application may require. In this article we'll look at how to create a custom site
navigation provider and build a file system-based custom provider from the ground-up. Read on to learn more!
The Job of a Custom Site Map Provider
In English, the responsibility of a site map provider is to return the site map upon request, where a site map is a collection of hierarchically-related site map nodes. More concretely, each site map node is implemented in the .NET Framework as an instance of the
SiteMapNodeclass. A custom site map provider, then, will need to perform the following:
- Get the site navigation information (this may be in a database, based on the file system, etc.)
- Iterate through the site navigation information, creating a
SiteMapNodefor each logical section
- Adding nodes to the site map, forming a hierarchy of site map nodes, keeping in mind that there can be only one root node.
Web.configfile, the custom site map provider's
Initialize()method is called and the attribute names and value specified for the provider in
Web.configare passed along. The custom provider's
Initialize()method can read these attribute names and values and save them as needed. For example, a site map provider that accessed site structure information from a database would need a connection string specified in the provider markup in the
Web.configfile. In the
Initialize()method this connection string value could be saved to a member variable in the class, so later, when constructing the site map, it could be used to connect to the database.
The .NET Framework offers a
StaticSiteMapProviderclass that contains the core functionality needed for a custom site map provider. To build our own site map provider, then, we merely need to create a class that inherits the
StaticSiteMapProviderclass and provides an implementation for the following two methods:
GetRootNodeCore()- returns the root of the site map.
BuildSiteMap()- constructs the site map and returns the root node.
Initialize()method for any custom providers that need to read custom settings from
Building a File System-Based Custom Site Map Provider
For simple websites that are only composed of a handful of static pages, web designers typically structure the file system to mimic the navigational structure of the website. For example, the demo website examined in the previous three parts of this article series used the following navigational structure:
The files and folder structure in the website mimicked this logical structure. There was a
Books folder with
History.aspx and so on. Similarly, there were
Computers. Rather than having to mirror this
file system information in the
Web.sitemap file (and remember to update it when adding new pages, renaming directories
or files, or removing pages altogether), we could create a custom site map provider that based the site map's content
on the file system structure.
I've created such a custom site map provider and named it
FileSystemSiteMapProvider. This class, along with an
ASP.NET 2.0 website that uses the custom provider, is available for download at the end of this article.
|The Practicality of a File System-Based Site Map Provider|
Does a file system-based site map provider make sense for real-world web applications? It depends. For small websites
that have a tight affinity between the site's file system layout and its navigational structure, such a site map provider
makes sense. For data-driven sites that have pages whose content is dynamically generated based on parameters like querystring
values, or for sites where there's no connection between the file system layout and navigational structure, the file system
site map provider is ill-fitted.
For data-driven sites you'll likely want to use a custom site map provider that builds the site map based on the contents from a database. While we won't look at building such a provider in this article, refer to Jeff Prosise's article The SQL Site Map Provider You've Been Waiting For for information on building a site map provider that hits against a SQL Server database. I also have an implementation of a SQL site map provider available in this example.
FileSystemSiteMapProvider Provider's Properties
By default the
FileSystemSiteMapProviderprovider enumerates every ASP.NET page and folder in the web application, excluding only the
Binfolder and folders that being with the
App_prefix. However, there may be some ASP.NET pages or folders that you do not want to appear in the site map. The
FileSystemSiteMapProviderprovider offers two optional properties that can be specified to achieve this aim:
ExcludeFileList- a list of files that should not be included in the site map. Each file must be fully qualified. For example, in the diagram shown above, imagine that we wanted to exclude the
Novels.aspxpage from the site map. We'd need to add
ExcludeFoldeerList- a list of folders that should not be included in the site map. If a file is excluded, all of its files and subfolders are excluded as well. Likewise, the folder path must be fully specified, like
StringDictionaryobjects; furthermore, they are both
Protected, meaning that you can't work with this property directly from your ASP.NET page. Instead, you'll need to use the appropriate helper methods for adding, removing, and enumerating these properties. (See the
SiteMap_Info.aspxpage in the download for an illustration of using these helper methods.) However, you can set these properties to their initial values through the
Web.configfile, as we'll see shortly.
In addition to these two exclusion properties,
FileSystemSiteMapProvider contains four other properties:
RootUrl- the fully qualified path to the page that should serve as the root node in the site map. If this value isn't specified, the default value is
RootTitle- the title to display for the root
SiteMapNode. Defaults to "Home"
UseDefaultPageAsFolderUrl- a Boolean property that indicates whether or not to use the
DefaultPageas the folder URL. If this property is True (the default) then each
SiteMapNodecreated in the site map for a folder has its
Urlproperty set to
~/FOLDER/DefaultPage.aspx(if the file exists). Furthermore, the file
~/FOLDER/DefaultPage.aspx(if it exists) is not added as a child of the folder.
To understand this property's implications, refer to the diagram shown earlier of the site's logical structure. Imagine that the site has a
~/Books/Default.aspxpage. If you want the Books node in the site map to be clickable and take the user to
~/Books/Default.aspx, leave this property set to True. If, however, you don't want the Books node to be clickable, and instead want to add a fourth child of the Books node that sends the user to
~/Books/Default.aspx, then set this property to False.
DefaultPageName- the file name for the page that is the default document. Defaults to
Building the Site Map
A site map provider that extends
StaticSiteMapProvider(like ours does) must implement the
BuildSiteMap()method, whose purpose is to construct the site map (if needed) and return the root
SiteMapNode. Since this method can be called multiple times per page request (since a page might have multiple navigation Web controls), it behooves us to utilize caching as much as possible. That is, we don't want to have to hit the file system and rebuild the site map every single time this method is invoked. Rather, we want to build it once and cache this tree until there's some file system-level change.
Caching the tree is simple enough - we just create a
SiteMapNode variable at the class level and
assign this variable to the root of the SiteMap. This approach builds the site map just once, and then caches it until
the web application is restarted or the
FileSystemSiteMapProvider class is edited. This caching is too aggressive,
however, because if new ASP.NET pages are added to the file system, or existing ones deleted, the site map will not pick up
To alleviate this problem I utilize the
CacheDependency object, which can be used to monitor a set of files and/or
folders. Specifically, I point it to the root folder (
~/) and whenever
BuildSiteMap() is called I
check to see if the file system has been changed since
BuildSiteMap() was last called. If not, then I simply
_root reference, since there's no need to rebuild the site map. If, however, there has been a change,
I recreate the dependency, clear out the site map, and rebuild it.
BuildSiteMap() method and the recursive
BuildSiteMapFromFileSystem() shown below are the workhorses
for creating the site map. I've left off a couple of the helper methods (
for example), for brevity. These methods can be explored by downloading the complete code at the end of this article.
FileSystemSiteMapProviderimplementation provided here uses a very simply algorithm for determining the title to display for the
SiteMapNodes for the files and folders - it just uses the file or folder name, replacing underscores (
_) with spaces. However, you may want to base the file name on the value in the
<title>element (if it exists), or based on some custom
<meta>tag or some other criteria. You can easily add this functionality by extending the
FileSystemSiteMapProviderclass and overridding the
GetFolderTitle()methods. These two methods return the title used by the
SiteMapNodefor a specified file or folder path.
FileSystemSiteMapProvider Provider Into Your Website
FileSystemSiteMapProvidercustom provider complete, the last step is to plug it into your website and to start using it in place of the default
XmlSiteMapProvider. To accomplish this, add the following markup to your application's
All of the attributes in the
<add> element are optional (except for
Refer to the "The
FileSystemSiteMapProvider Provider's Properties" section earlier in this article for a rundown
on this custom provider's properties and their default values.
One of the key benefits of ASP.NET 2.0 is its extensibility. With its myriad of subsystems built atop the provider model, ASP.NET 2.0 provides a standardized API that permits custom implementation. In this article we saw how to utilize the provide model and site navigation, creating a custom site map provider that is based on the file system structure. Such a site map provider will likely prove useful for those developing small, relatively static websites whose file system structure closely matches the site's navigational structure.