Introduction
Sitemaps and breadcrumbs (SiteMapPath) are useful and easy to implement for a static site with a sitemap file. For dynamic sites, something as simple seems to get much more complicated.
When I started reading about sitemaps for dynamic sites, I found a common approach: generate a static site map for the whole website from, for example, a data source. Re-generate periodically. Use the XmlSiteMapProvider. Cache. The technique is described in this CodeProject article.
This doesn't work for my site at all. I need to provide a site map for a large number of pages, potentially hundreds of thousands. The site is deep and dynamic, and only a small unpredictable set of pages is accessed frequently. There're also two hundred different object types in the back-end database which is not coupled with the user-interface at all. Querying everything once is not trivial. Generating an accurate site map for the whole site would take too long, would be a lot of code, and would definitely take too much memory to cache.
This article demonstrates a simpler and practical solution chosen. You can see it working live on www.foodcandy.com.
Architecture
The core of the architecture is a relatively simple dynamic data provider, SiteMapDataProvider
, based on the StaticSiteMapProvider
. The provider is used as the default provider throughout the application, and can stack nodes that appear in the site map path. Each document that renders dynamic content stacks itself with the appropriate hierarchy of parent nodes.
Implementation
The SiteMapDataProvider
is straightforward. It will create a root node, and can stack a node behind any existing node, creating a path.
public class SiteMapDataProvider : StaticSiteMapProvider
{
private SiteMapNode mRootNode = null;
...
public override void Initialize(string name,
NameValueCollection attributes)
{
base.Initialize(name, attributes);
mRootNode = new SiteMapNode(this, "Home",
"Default.aspx", "Home");
AddNode(mRootNode);
}
public override SiteMapNode FindSiteMapNode(string rawUrl)
{
return base.FindSiteMapNode(rawUrl);
}
public SiteMapNode Stack(string title, string uri)
{
return Stack(title, uri, mRootNode);
}
public SiteMapNode Stack(string title, string uri,
SiteMapNode parentnode)
{
lock (this)
{
SiteMapNode node = base.FindSiteMapNodeFromKey(uri);
if (node == null)
{
node = new SiteMapNode(this, uri, uri, title);
node.ParentNode =
((parentnode == null) ? mRootNode : parentnode);
AddNode(node);
}
else if (node.Title != title)
{
node.Title = title;
}
return node;
}
}
}
I have put the above implementation in a DBlock.SiteMapProvider.dll assembly, and have referenced it in web.config as the default provider.
="1.0"
<configuration>
<system.web>
<siteMap enabled="true"
defaultProvider="SiteMapDataProvider">
<providers>
<add name="SiteMapDataProvider"
type="DBlock.SiteMapDataProvider, DBlock.SiteMapDataProvider" />
</providers>
</siteMap>
</system.web>
</configuration>
I've added a simple master page with a SiteMapPath and a default page. This yields a Home (root) site map node on Default.aspx.
Now remember, the goal is to have a dynamic site map. I've added a button that redirects to Default.aspx?id=<guid> for demo purposes. To display the site map accordingly, I've built a linked list of site map nodes, then stacked them into the site map. Below is the Default.aspx Page_Load
and one more helper function in SiteMapDataProvider
to do the stack.
string id = Request["id"];
if (!string.IsNullOrEmpty(id))
{
List<KeyValuePair<string, Uri>> nodes =
new List<KeyValuePair<string, Uri>>();
nodes.Add(new KeyValuePair<string,
Uri>("Dynamic Content", new Uri(Request.Url, "Default.aspx?id=")));
nodes.Add(new KeyValuePair<string, Uri>(Request["id"], Request.Url));
((SiteMapDataProvider) SiteMap.Provider).Stack(nodes);
}
public void Stack(List<KeyValuePair<string, Uri>> nodes)
{
SiteMapNode parent = RootNode;
foreach (KeyValuePair<string, Uri> node in nodes)
{
parent = Stack(node.Key, node.Value.PathAndQuery, parent);
}
}
Points of Interest
This works very well for pages that retrieve dynamic content. My project typically retrieves objects from the database or cache, and displays them on each page. Adding two-three lines of code to each page generates the complete site map of pages that are actually being hit.
The obvious disadvantage of this technique is that you have to add code in each page to generate the site map, and that you must carefully track the URL of your intermediate nodes (the Default.aspx?id=, with a blank ID, in the example above) to keep things consistent. It would be great to describe the relationship between dynamic pages in some other form that can be verified at compile time. Maybe, a better (yet more complex) approach is to extend the XML provider to allow dynamic binding in nodes?
You may also want to limit the size of the site map for large sites to avoid consuming too much memory. Simply clear the site map once it has reached your size limit.
History
- 2006/12/16: Initial release.