Click here to Skip to main content
13,348,874 members (78,224 online)
Click here to Skip to main content
Add your own
alternative version


82 bookmarked
Posted 21 Dec 2006

Building a dynamic SiteMap in ASP.NET 2.0 for a large website

, 21 Dec 2006
Rate this:
Please Sign up or sign in to vote.
Practical approach to building a dynamic site map on a large website in ASP.NET.

dynamic sitemap rendered


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


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.


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;


 // create the root node
 public override void Initialize(string name, 
        NameValueCollection attributes)
     base.Initialize(name, attributes);
     mRootNode = new SiteMapNode(this, "Home", 
                 "Default.aspx", "Home");

 public override SiteMapNode FindSiteMapNode(string rawUrl)
     return base.FindSiteMapNode(rawUrl);

 // stack a node under the root
 public SiteMapNode Stack(string title, string uri)
     return Stack(title, uri, mRootNode);

 // stack a node under any other node
 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);
         else if (node.Title != title)
         {<BR>             // support renaming documents
             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.

<?xml version="1.0"?>
  <siteMap enabled="true" 

    <add name="SiteMapDataProvider" 

       type="DBlock.SiteMapDataProvider, DBlock.SiteMapDataProvider" />

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.


  • 2006/12/16: Initial release.


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

Team Leader Application Security Inc.,
United States United States
Daniel Doubrovkine has been in software engineering for twelve years and is currently development manager at Application Security Inc. in New York City. He has been involved in many software ventures, including Xo3 and Vestris Inc, was a development lead at Microsoft Corp. in Redmond, and director of Engineering at Visible Path Corp. in New York City. Daniel also builds and runs a foodie website,

You may also be interested in...


Comments and Discussions

Generalyour code is running...but one question Pin
sahil31_mohali30-Sep-07 23:23
membersahil31_mohali30-Sep-07 23:23 
GeneralRe: your code is running...but one question Pin
dB.1-Oct-07 2:42
memberdB.1-Oct-07 2:42 
General[Message Deleted] Pin
sahil31_mohali30-Sep-07 4:26
membersahil31_mohali30-Sep-07 4:26 
GeneralRe: Error:From where u inherit StaticSiteMapProvider Pin
dB.30-Sep-07 4:27
memberdB.30-Sep-07 4:27 
GeneralRe: Error:From where u inherit StaticSiteMapProvider Pin
dB.30-Sep-07 4:29
memberdB.30-Sep-07 4:29 
GeneralRe: again problem Pin
dB.30-Sep-07 4:20
memberdB.30-Sep-07 4:20 
GeneralRe: problem with build dll Pin
dB.30-Sep-07 3:38
memberdB.30-Sep-07 3:38 
GeneralBrilliant | Cause of confusion [modified] Pin
Ghostnet22-Sep-07 20:16
memberGhostnet22-Sep-07 20:16 
This a great solution. It's so simple and effective, I can't believe this hasn't been posted elsewhere.

However, I do see a cause of confusion for some people.
It's pretty easy to sort this out once you take a look at the source.

This solution is essentially the "skeleton" for building a fully dynamic (and size independent) sitemap.
Let's say this example for a table called "Pages" :
ID | ParentID | Title
1  | 0        | Home
2  | 1        | About
3  | 2        | History
4  | 2        | Services
5  | 1        | Contact
6  | 1        | Discussion Boards

You may specify an additional field (E.G. a "Homepage" boolean) that the script will use to get the Index.
Now if I visit "Default.aspx" What is retrieved is essentially "Home".

To retrieve "Default.aspx?ID=4" (Our "Services" page) it's quite simple...

//Put this somewhere above
List<KeyValuePair<string, Uri>> nodes = new List<KeyValuePair<string, Uri>>();

//Where "PageID" comes from the querystring "Default.aspx?ID=4"
public GetPathData(int PageID)
       SqlConnection conn = new SqlConnection(ConnectionString);
       SqlCommand cmd = new SqlCommand("SELECT [PageID], [ParentID], [Title] FROM [Pages] " +
                 "WHERE [PageID] = @PageID;", conn);
        SqlDataReader reader = null;

                //Start the recursive query using the same reader and node
                GetParent(conn, reader, cmd, PageID);
                // Finish the stack
                ((SiteMapDataProvider) SiteMap.Provider).Stack(nodes);
                // Do nothing
                if (conn.State == ConnectionState.Open)

/// Recursive function to get subsequent parent
private void GetParent(SqlConnection conn, SqlDataReader reader, SqlCommand cmd, int ParentID)
        if (conn.State == ConnectionState.Open)
                if (cmd.CommandText != null || cmd.CommandText != "")
                        cmd.Parameters.Add("@PageID", SqlDbType.Int).Value = ParentID;
                        reader = cmd.ExecuteReader(CommandBehavior.SingleRow);
                        while (reader.Read())
                             nodes.Add(new KeyValuePair<string, Uri>(reader.GetString(2), new Uri("Default.aspx?ID="+ reader.GetInt32(0), "Default.aspx?ID="+ ParentID)));
                             // Adding a new URI as the "Title", "PageURI", "ParentURI"
                        if (ParentID > 0)
                                GetParent(conn, reader, cmd, ParentID);

Now that's a very rough example, but you see how this can be incorporated.
Please check for any errors and correct/improve as needed. I only took a quick glance at the code, so I may be missing something.

Hope that clears things up for people...

-- modified at 1:23 Sunday 23rd September, 2007
GeneralRe: Brilliant | Cause of confusion Pin
dB.23-Sep-07 6:18
memberdB.23-Sep-07 6:18 
GeneralRe: Brilliant | Cause of confusion Pin
sahil31_mohali4-Oct-07 20:37
membersahil31_mohali4-Oct-07 20:37 
Questionhow to transfer this table to sitemap control Pin
serkanweb19-Sep-07 3:34
memberserkanweb19-Sep-07 3:34 
AnswerRe: how to transfer this table to sitemap control Pin
dB.19-Sep-07 6:08
memberdB.19-Sep-07 6:08 
QuestionWhat? Pin
Mark Steadman1-Aug-07 3:33
memberMark Steadman1-Aug-07 3:33 
AnswerRe: What? Pin
dB.1-Aug-07 4:49
memberdB.1-Aug-07 4:49 
General!Thanks, great one Pin
hartertobak25-Jun-07 6:22
memberhartertobak25-Jun-07 6:22 
GeneralRe: !Thanks, great one Pin
dB.1-Aug-07 4:50
memberdB.1-Aug-07 4:50 
Generalmaster page Pin
poortl910925-Apr-07 1:29
memberpoortl910925-Apr-07 1:29 
GeneralRe: master page Pin
dB.25-Apr-07 3:22
memberdB.25-Apr-07 3:22 
QuestionAdding two web.sitemaps Pin
kumarrajt17-Apr-07 0:50
memberkumarrajt17-Apr-07 0:50 
AnswerRe: Adding two web.sitemaps Pin
dB.17-Apr-07 3:34
memberdB.17-Apr-07 3:34 
QuestionMissing abstract members Pin
thomas.bjorndahl2-Apr-07 5:05
memberthomas.bjorndahl2-Apr-07 5:05 
AnswerRe: Missing abstract members Pin
dB.3-Apr-07 4:58
memberdB.3-Apr-07 4:58 
QuestionCould not load file or assembly 's2k7.SiteMapDataProvider' or one of its dependencies Pin
Maxipower23-Dec-06 2:29
memberMaxipower23-Dec-06 2:29 
AnswerRe: Could not load file or assembly 's2k7.SiteMapDataProvider' or one of its dependencies Pin
dB.23-Dec-06 5:11
memberdB.23-Dec-06 5:11 
AnswerRe: Could not load file or assembly 's2k7.SiteMapDataProvider' or one of its dependencies Pin
Maxipower27-Dec-06 9:17
memberMaxipower27-Dec-06 9:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.180111.1 | Last Updated 21 Dec 2006
Article Copyright 2006 by dB.
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid