Adding dynamic nodes to ASP.NET site maps at runtime by deriving from StaticSiteMapProvider

 

Adding a static sitemap to an ASP.NET website is straightforward. Creating a dynamic sitemap is harder, but there are several articles that Google finds describing how. Adding dynamic items to an existing sitemap seems harder still: you can't add items by deriving from XmlSiteMapProvider, as the list of SiteMapNodes is read only.

The method for adding items to a sitemap has a few steps:
  1. Create your sitemap file in the usual way.
  2. Derive a class from StaticSiteMapProvider.
  3. Add your sitemap to the Web.Config file.
  4. Check that this works by adding a tree view connected to a sitemap data source.
  5. Implement the overridden base-class methods.
  6. Read the sitemap file into an XmlDocument.
  7. Dynamically add new elements to the XmlDocument.
  8. Recurse through the XmlDocument creating a tree of SiteMapNodes.


In step 1, your sitemap file may look something like this:

<?xml version="1.0" encoding="utf-8" ?>

<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >

    <siteMapNode url="~/default.aspx" title="Home"  description="Home page">

        <siteMapNode url="~/contact.aspx" title="Contact"  description="Contact us" />

        <siteMapNode url="~/products.aspx" title="Products"  description="Our products" />

    </siteMapNode>

</siteMap>



For step 2, just create the class at the moment:

namespace Harriyott.Web

{

    public class DynamicSiteMapProvider : StaticSiteMapProvider

...

You'll be prompted (by a tiny rectangle) to implement the abstract class, which you should do, and replace the exceptions with return null; for now.

For step 3, add the site map provider in the usual way, but change the type to your new class name, including the namespace:

<system.web>

    <siteMap defaultProvider="main">

        <providers>

            <add siteMapFile="Web.sitemap"  name="main" type="Harriyott.Web.DynamicSiteMapProvider"/>

        </providers>

    </siteMap>



To see what we have so far, step 4 is to add a new .aspx page with a tree view displaying the site map.

<asp:TreeView ID="treeSiteMap" runat="server" DataSourceID="smdsHarriyott" />

<asp:SiteMapDataSource ID="smdsHarriyott" runat="server" />



Although this should run ok, no items are displayed yet. This is because the StaticSiteMapProvider class doesn't actually process the sitemap XML, because we're returning null. To see the static site map items, switch the type in the Web.Config back to the default:

type="System.Web.XmlSiteMapProvider"



OK, so step 5 is to implement the overridden methods and properties properly. Or properlies property.

private String _siteMapFileName;

private SiteMapNode _rootNode = null;

 

public override SiteMapNode RootNode

{

    get { return BuildSiteMap(); }

}

 

public override void Initialize(string name, NameValueCollection attributes)

{

    base.Initialize(name, attributes);

    _siteMapFileName = attributes["siteMapFile"];

}

 

protected override SiteMapNode GetRootNodeCore()

{

    return RootNode;

}

 

protected override void Clear()

{

    lock (this)

    {

        _rootNode = null;

        base.Clear();

    }

}



The first bit is quite straightforward. There's a root node to add sitemap nodes to, and we're saving the filename of the sitemap file. The interesting bit is to create the nodes. This is done in BuildSiteMap():

private const String SiteMapNodeName = "siteMapNode";

 

public override SiteMapNode BuildSiteMap()

{

    lock (this)

    {

        if (null == _rootNode)

        {

            Clear();

            // Load the sitemap's xml from the file.

            XmlDocument siteMapXml = LoadSiteMapXml();

            // Create the first site map item from the top node in the xml.

            XmlElement rootElement =

                (XmlElement)siteMapXml.GetElementsByTagName(

                SiteMapNodeName)[0];

            // This is the key method - add the dynamic nodes to the xml

            AddDynamicNodes(rootElement);

            // Now build up the site map structure from the xml

            GenerateSiteMapNodes(rootElement);

        }

    }

    return _rootNode;

}



Four main things going on here. (Four things. Please don't be confused by me saying things like "And fourthly, step 8", as I'm still trying to keep track of the list at the beginning.) Firstly, in step 6, the XML file is loaded:

private XmlDocument LoadSiteMapXml()

{

    XmlDocument siteMapXml = new XmlDocument();

    siteMapXml.Load(AppDomain.CurrentDomain.BaseDirectory + _siteMapFileName);

    return siteMapXml;

}



Secondly, we're selecting the top siteMapNode from the loaded XML. Thirdly, and this is the important step 7, we're going to add our dynamic nodes:

private void AddDynamicNodes(XmlElement rootElement)

{

    // Add some football teams

    XmlElement teams = AddDynamicChildElement(rootElement, "", "Football Teams", "List of football teams created dynamically");

    AddDynamicChildElement(teams, "~/teams.aspx?name=Watford", "Watford", "Watford's team details");

    AddDynamicChildElement(teams, "~/teams.aspx?name=Reading", "Reading", "Reading's team details");

    AddDynamicChildElement(teams, "~/teams.aspx?name=Liverpool", "Liverpool", "Liverpool's team details");

 

    XmlElement sheffield = AddDynamicChildElement(teams, "", "Sheffield", "There is more than one team in Sheffield");

    AddDynamicChildElement(sheffield, "~/teams.aspx?name=SheffieldUnited", "Sheffield United", "Sheffield United's team details");

    AddDynamicChildElement(sheffield, "~/teams.aspx?name=SheffieldWednesday", "Sheffield Wednesday", "Sheffield Wednesday's team details");

 

    XmlElement manchester = AddDynamicChildElement(teams, "", "Manchester", "There is more than one team in Manchester");

    AddDynamicChildElement(manchester, "~/teams.aspx?name=ManchesterUnited", "Manchester United", "Manchester United's team details");

    AddDynamicChildElement(manchester, "~/teams.aspx?name=ManchesterCity", "Manchester City", "Manchester City's team details");

}



I'm just doing this in memory to keep the example short, but you could generate stuff from your database. The AddDynamicChildElement returns a new child that's been added to the current node:

private static XmlElement AddDynamicChildElement(XmlElement parentElement, String url, String title, String description)

{

    // Create new element from the parameters

    XmlElement childElement = parentElement.OwnerDocument.CreateElement(SiteMapNodeName);

    childElement.SetAttribute("url", url);

    childElement.SetAttribute("title", title);

    childElement.SetAttribute("description", description);

 

    // Add it to the parent

    parentElement.AppendChild(childElement);

    return childElement;

}



And fourthly, step 8 is to generate the site map nodes in, er, GenerateSiteMapNodes():

private void GenerateSiteMapNodes(XmlElement rootElement)

{

    _rootNode = GetSiteMapNodeFromElement(rootElement);

    AddNode(_rootNode);

    CreateChildNodes(rootElement, _rootNode);

}

 

private void CreateChildNodes(XmlElement parentElement, SiteMapNode parentNode)

{

    foreach (XmlNode xmlElement in parentElement.ChildNodes)

    {

        if (xmlElement.Name == SiteMapNodeName)

        {

            SiteMapNode childNode = GetSiteMapNodeFromElement((XmlElement)xmlElement);

            AddNode(childNode, parentNode);

            CreateChildNodes((XmlElement)xmlElement, childNode);

        }

    }

}



What's going on here is that we're creating a root sitemap node from the root element in the XML. Then we're recursively finding the XML element's children and adding coresponding sitemap nodes to match the hierarchy. Here's the method that creates a sitemap node from the XML element:

private SiteMapNode GetSiteMapNodeFromElement(XmlElement rootElement)

{

    SiteMapNode newSiteMapNode;

    String url = rootElement.GetAttribute("url");

    String title = rootElement.GetAttribute("title");

    String description = rootElement.GetAttribute("description");

 

    // The key needs to be unique, so hash the url and title.

    newSiteMapNode = new SiteMapNode(this,

        (url + title).GetHashCode().ToString(), url, title, description);

 

    return newSiteMapNode;

}



So that's it. We now have a static site map with items dynamically added to it. Just to prove it, here's what mine looks like on an Angel Delight coloured background:

Site map screen shot

To save you piecing this all together in a new class, you can download the full source file.

[Tags: ]
<script src="http://feeds.feedburner.com/~s/harriyott/simonssoftwarestuff?i=http://www.harriyott.com/2007/03/adding-dynamic-nodes-to-aspnet-site.aspx" type="text/javascript" charset="utf-8"></script> <script src="http://feeds.feedburner.com/~d/static/site-tracker.js" type="text/javascript" charset="utf-8"></script>
12 Comments:
Thomas said...

Nice, but you REALLY need some CODE formatter...
Check out the one I use (LGPL licensed) at e.g. http://ajaxwidgets.com/Blogs/thomas/javascript_ajax_intellisense_i.bb
But I got some tips about the Sitemap functionality of ASP.NET 2.0 that I found amusing here :)

Cheers!

March 28, 2007 9:18 PM  
Simon said...

Hi Thomas,

Glad the sitemap stuff was amusing. You're absolutely right about the formatting. I use the CopySourceAsHtml plugin, which is fine, but my template isn't wide enough for it, and the line spacing is too big. The one on your site looks much better, unless javascript is disabled. I'll take your comment on board though, and see if I can find something better.

March 28, 2007 11:01 PM  
Anonymous said...

You R's!

March 29, 2007 9:46 AM  
Frank said...

Thanks for an excellent article! I'm putting this to use right now with a site but I wanted to be able to dynamically add siteMapNode elements at a specified location in the XML ... took me far too long to figure this bit out
XmlNamespaceManager nsmgr = new XmlNamespaceManager(siteMapXml.NameTable);

string docNsURIName = siteMapXml.DocumentElement.NamespaceURI;

nsmgr.AddNamespace("map", docNsURIName );

XmlElement targetElement = (XmlElement)siteMapXml.SelectSingleNode("/map:siteMap/map:siteMapNode/map:siteMapNode/map:siteMapNode[@title='Dynamically added content']", nsmgr);


// This is the key method - add the dynamic nodes to the xml

//AddDynamicNodes(rootElement);
AddDynamicNodes(targetElement);

April 07, 2007 12:01 AM  
Simon said...

Hi Frank,

Glad you're using this, I was hoping somebody would find it useful. Thanks for the additional code - that makes it more useful.

April 07, 2007 1:11 PM  
Anonymous said...

Hi, thanks for the article it is just what i was looking for. but i have a noob problem. I want to change the nodes when the page loads (in the page load event). how do i do this with your code?

thanks
daniel

April 09, 2007 10:47 AM  
Anonymous said...

and another noobie question. if i dynamically change the sitemap like this, wouldn't it effect the other users?, i.e someone else using the site, will everybody see there own custom dynamically generated sitemap?

thanks again
daniel

April 09, 2007 11:01 AM  
Simon said...

Hi Daniel,

I don't think you can actually change this in the page load event, which is why I had to go through all this in the first place!

As far as affecting the other users, this will only be a problem if you are generating user-specific links in the sitemap. If not, then everyone will see the same dynamic sitemap.

April 11, 2007 11:20 AM  
Christoph said...

This rocks. Exactly what I was looking for. Now if I can just figure out how to dynamically set images for my treeview nodes at runtime...

April 19, 2007 5:42 PM  
Pushkar Joshi said...

How it write in web.sitemap file?

May 08, 2007 8:49 AM  
Pushkar Joshi said...

There is one function AddNode(), where the AddNode Function is written? and what id does?

May 08, 2007 9:43 AM  
Simon said...

Hi Pushkar,

Firstly, it doesn't actually write into the web.sitemap file, it loads the file, and adds new nodes to the structure in memory. It doesn't write the changes back to the web.sitemap file.

Secondly, AddNode is in the .NET framework base class, StaticSiteMapProvider, which my class derives from. The full details are here: http://tinyurl.com/3afh3c

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值