The SiteMap class is new in .NET 2.0 and provides a new way to manage navigation in 2.0. Before I get too much further you can download the ExtendedXmlSiteMapProvider I wrote here. There are some limiting factors that I found and had to deal with.
- It is not possible to add nodes at runtime to an existing node using the default SiteMapProvider which is the XmlSiteMapProvider.
- The Web.sitemap does not support a definition for the key in the SiteMapNode definition.
My first bullet point was easy to solve, since the SiteMap uses the provider pattern I was able to create my own provider which derived from the XmlSiteMapProvider. First a little more background on the problem. Why was I not able to add to the ChildNodes collection, well this collection is readonly which does not help much. There are protected methods for adding nodes on the XmlSiteMapProvider so I just exposed public methods to add nodes which called the protected add method. To replace the default provider add the below lines to your web.config.
<siteMap enabled="true" defaultProvider="ExtendedXmlSiteMapProvider">
<providers>
<add name="ExtendedXmlSiteMapProvider"
type="ERA.PT.Common.Web.ExtendedXmlSiteMapProvider"
siteMapFile="Web.sitemap" />
</providers>
</siteMap>
Whenever you want to dynamically add nodes you probably want to also remove/clear them at some point. I chose to be able clear child nodes for the current node or specifying a specific node. The add methods and clear methods I used are shown below.
/// <summary>
/// Calls base functionality to add a new node.
/// </summary>
/// <param name="node">new node to add</param>
/// <param name="parentNode">parent node for the new node</param>
private void AddNewNode(SiteMapNode node, SiteMapNode parentNode)
{
if (node != null && parentNode != null)
{
base.AddNode(node, parentNode);
}
}
/// <summary>
/// Adds the new node.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="url">The URL.</param>
/// <param name="title">The title.</param>
/// <param name="parentNode">The parent node.</param>
/// <returns></returns>
public SiteMapNode AddNewNode(string key, string url, string title, SiteMapNode parentNode)
{
SiteMapNode node = new SiteMapNode(this, key, url, title);
this.AddNewNode(node, parentNode);
return node;
}
/// <summary>
/// Adds the new node to current.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="url">The URL.</param>
/// <param name="title">The title.</param>
/// <returns></returns>
public SiteMapNode AddNewNodeToCurrent(string key, string url, string title)
{
return this.AddNewNode(key, url, title, this.CurrentNode);
}
/// <summary>
/// Clears the nodes under the current node.
/// </summary>
public void ClearCurrentChildNodes()
{
this.ClearChildNodes(this.CurrentNode);
}
/// <summary>
/// Clears the nodes.
/// </summary>
/// <param name="parentNode">The parent node.</param>
public void ClearChildNodes(SiteMapNode parentNode)
{
if (parentNode != null && parentNode.ChildNodes.Count > 0)
{
SiteMapNodeCollection nodes = parentNode.ChildNodes;
for (int i = nodes.Count - 1; i >= 0; i--)
{
base.RemoveNode(nodes[i]);
}
}
}
For my second bullet point of the Web.sitemap not supporting the definition of a key. Well I have not done anything with this one yet. By default your key is the url defined in the Web.sitemap so that is what I use to find nodes. What I would like to do is write my own BuildSiteMap in the provider that would use an attribute in siteMapNode element defined as key to set the key of the node. Keys are much easier to manage than url’s when it comes to finding nodes. Not sure when I will get around to this, if ever.
One question I asked myself is: How do I set the current node? You may ask why would you want to do that. If you have a link in a page that corresponds to the navigation and you would like the bread crumb to show the location. This can be done by listing to the SiteMapResolved event which gets called each time the current node is asked for. In the handler for SiteMapResolved you can search for what is your current node or you could expose a method to set the current node. I did this using the code shown below.
private SiteMapNode currentNode;
/// <summary>
/// Initializes a new instance of the <see cref="T:ExtendedXmlSiteMapProvider"/> class.
/// </summary>
public ExtendedXmlSiteMapProvider()
{
this.SiteMapResolve += new SiteMapResolveEventHandler(ExtendedXmlSiteMapProvider_SiteMapResolve);
}
private SiteMapNode ExtendedXmlSiteMapProvider_SiteMapResolve(object sender, SiteMapResolveEventArgs e)
{
SiteMapNode node = null;
node = e.Provider.FindSiteMapNode(e.Context);
if (node == null)
node = e.Provider.FindSiteMapNode(e.Context.Request.Url.AbsoluteUri);
if (node == null)
node = currentNode;
else
currentNode = null;
return node;
}
/// <summary>
/// Sets the current node.
/// </summary>
/// <param name="currentNode">The current node.</param>
public void SetCurrentNode(SiteMapNode currentNode)
{
this.currentNode = currentNode;
}
If you did not see the link at the top you can download the ExtendedXmlSiteMapProvider here. If anyone knows an easier way to do what this please let me know.