RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Nine ASP.NET Site Navigation Problem Solutions: Part 2 : Page 3

Discover how to grant or deny users access to pages in your sites, and how to create dynamic site maps using database-driven data.

#9 Keeping Your Navigation Fresh
The problem of adding dynamic content to a site map reveals the full beauty of ASP.NET's provider model. It requires little effort to extend the provider model to insert database-driven siteMapNodes into any section of the site map and continue to enjoy the benefits of the Web.sitemap approach described in the previous eight solutions. And with a little more effort you can cache the database content, refreshing it only when the database changes. This last solution shows how to surmount these two challenges.

Author's Note: The caching discussion in the second half of this solution focuses solely on SQL Server 2005 and ASP.NET 2.0's new dependency technology.

Build a Custom Site Map Provider
While a custom site map provider provides the most flexible and maintainable solution, there are two alternate approaches worth mentioning. First, one could reuse a single page in multiple siteMapNodes, because Web.sitemap supports querystring parameters in its url attribute. The problem is that maintaining the Web.sitemap for pages that might change frequently, such as message board posts, would be a Webmaster's nightmare.

Second, the siteMapNode supports a siteMapFile attribute. This attribute allows a siteMapNode to retrieve its content from a separate .sitemap file. You could employ this technique by creating/overwriting a .sitemap file from inside a database trigger; however, this approach would limit the scalability of your application, because the database and Web server would need to be on the same machine. A more appropriate use of the technique might involve using a manually maintained Web.Sitemap that pulls data from a code-generator-maintained .sitemap file (Blue Ink uses this technique, see my bio for details).

The best approach will most likely involve developing a custom site map provider.
Regardless, the best approach will most likely involve developing a custom site map provider. The process requires developing a new class that inherits from SiteMapProvider, identifying it to the Web.config file, and referencing it from the Web.sitemap. You reference a new provider from Web.sitemap using the final siteMapNode attribute: provider.

   <siteMapNode provider="CategoriesProvider" />
The provider attribute allows a siteMapNode to retrieve its content from a provider specified in Web.config. The value CategoriesProvider shown in the example references the Northwind.CategoriesSiteMapProvider class:

   <siteMap defaultProvider="XmlSiteMapProvider" enabled="true">
       <clear />
       <add name="XmlSiteMapProvider"
         description="Default SiteMap provider"
         securityTrimmingEnabled="true" />
       <add name="CategoriesProvider"
         description="Displays Categories From Northwind"
         type="Northwind.CategoriesSiteMapProvider" />
While a single site map provider must be the default, ASP.NET supports multiple site map providers so they can reference each other. This approach confers the ability to compartmentalize sections of the site map. After configuring the Web.sitemap and Web.config entries, you must develop the custom provider.

As mentioned previously, each site map provider must ultimately inherit from the SiteMapProvider class; however, an intermediary class called StaticSiteMapProvider provides a partial implementation of SiteMapProvider that simplifies the task of loading site map information into memory. The StaticSiteMapProvider adds several methods to the base SiteMapProvider class, including AddNode(), RemoveNode(), Clear(), and BuildSiteMap().

The functions a custom provider will most likely need to override are BuildSiteMap() and Initialize() (from SiteMapProvider). ASP.NET calls Initialize()once throughout the lifetime of the application, making it an ideal place to retrieve configuration settings from the Web.config. In contrast, BuildSiteMap() is called frequently and must return the root siteMapNode. A custom site map provider should implement BuildSiteMap() to return a cached copy of the site map if possible, to increase performance. The following pseudocode shows the general flow:

   private SiteMapNode mnodeRoot = null;
   public override SiteMapNode BuildSiteMap() {
     if (site map has already been built)
       return mnodeRoot;
     SiteMapNode nodeRoot = GetRootNode();
     open a connection to Northwind
     select all categories
     foreach (category) {
       SiteMapNode nodeCategory = CreateCategoryNode();
       AddNode(nodeCategory, nodeRoot);
Unfortunately, the preceding approach suffers from two major problems. The first is that the site map information will never change unless it is manually invalidated (for instance by setting mnodeRoot to null in a maintenance section of the Web site). Ideally the site map information would automatically refresh if the data it displays changes. I'll show you how to use the SqlCacheDependency object to solve this problem shortly.

The second problem is more subtle, but is extremely important. ASP.NET 2.0 implements SiteMapProvider (and in fact all providers) using the singleton pattern. Thus ASP.NET 2.0 shares one instance of any custom site map providers among all page requests—and thus among multiple threads. One benefit of this approach is that custom site map providers can store persistent data in member variables, but the disadvantage is that custom site map providers must consider thread safety in any code that modifies the site map provider's state. Therefore, you must modify the approach shown above to account for multiple threads attempting to build the site map at the same time. Here's a modified example:

   private readonly object mobjLock = new object();
   public override SiteMapNode BuildSiteMap() {
     if (site map has already been built)
       return mnodeRoot;
     lock(mobjLock) {  
       SiteMapNode nodeRoot = GetRootNode();
       loop through data {
         call AddNode()
The code above ensures that no two threads will ever try to update the state of the site map provider at the same time. Notice that the code calls lock() only when the state will be updated; calling it earlier it would reduce performance unnecessarily.

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date