Click here to Skip to main content
15,029,278 members
Articles / Web Development / ASP.NET
Posted 7 Jan 2006


90 bookmarked

DotNetNuke SiteMap

Rate me:
Please Sign up or sign in to vote.
4.54/5 (16 votes)
15 May 2006CPOL6 min read
A SiteMap module for DotNetNuke 4, which retrieves data on demand, and is still quick for websites with lots of pages.


I migrated a part of a big site (> 500 pages) to DotNetNuke which is an Open Source .NET CMS. After migration, the loading of each page was very slow (10 sec!). After investigating, I found out that the SiteMap (a TreeView control) was the source of the time delay. So, I tested three other SiteMap controls, which I found on the Internet. All had nearly the same result, they were much too slow. My first thought was that the reading of 500 pages was time consuming. So I made a test program, and found out that the reading from the DB was finished in no time, the building of a tree data structure with pages as nodes (with parents) was finished in no time, even the filling of the .NET 2.0 web TreeView control was very quick. The bottleneck was the rendering of the MS web TreeView control. I assume that the other, slower, SiteMaps always filled the complete tree, which then took the TreeView control to render that long, even if most of the nodes were collapsed. And that was my idea, to speed that process up because you normally have only a small bit of your tree expanded, you simply don't need the data from other leaves, because no one is watching them. And if the user expands a node, then I get the children from the DB and fill just that one node. Another feature of DnnSiteMap is that it realizes on which page, which is called a Tab in DNN slang ;-), it is, and expands itself to that node and selects it.

Important: DnnSitemap is for DotNetNuke 4 and APS.NET 2.0 only! For installation, you must follow the install instructions (see below).


DotNetNuke is an open source .NET Content Management System. It is derived from the IBuySpyPortal, which is a best practice from Microsoft to show the capabilities of ASP.NET. Currently, DotNetNuke (DNN) is in version 4 which is based on the new ASP.NET 2.0 and is programmed in VB.NET. Because of its big community support, MS is supporting the DNN project. DNN is programmed by a core team, lead by Shaun Walker.


  • Quick SiteMap because the tree is only filled on demand, from the DB.
  • Root Tab (Page) can be defined via settings.
  • Show Lines can be set via settings.
  • Node text word wrap can be set via settings.
  • Predefined icon set (many included) can be set via settings.
  • Panel control (collapse all/expand all/ current) can be shown or hidden via settings.
  • Flag if only tabs are visible, from which the user has permissions to view them.
  • Node indent in pixels can be set via settings.

Using the code

I did a bit of over-commenting inline of the code, so that anyone can understand what each step is doing. Basically, the code is straightforward. All the data and the business logic is in the App_Code folder, and the UI code is in the ViewDnnSiteMap.ascx.cs file.

In the data layer, you will find the following functions:

/// <summary>
/// Gets all tabs that have no ParentId 
/// and are not deleted and visible
/// </summary>
/// <returns>IDataReader: TabId (int), TabName (string), 
/// Children (int)</returns>
public abstract IDataReader GetRootNodesFromDb();

/// <summary>
/// Gets all tabs that are children of the specified tab
/// </summary>
/// <param name="parentTabId">TabId of the parent tab</param>
/// <returns>IDataReader: TabId (int), TabName (string), 
/// Children (int)</returns>
public abstract IDataReader GetChildNodesFromDb(int parentTabId);

/// <summary>
/// Gets parent tab for specified tab
/// </summary>
/// <param name="childTabId">TabId of the child tab</param>
/// <returns>IDataReader: ParentTabId (int), ParentName (string);
/// (should be max one row)</returns>
public abstract IDataReader GetParentFromDb(int childTabId);

/// <summary>
/// Gets the Tab, that hosts the given module
/// </summary>
/// <param name="tabModuleId">TabModuleId of the module</param>
/// <returns>IDataReader: ParentTabId (int), ParentName (string);
/// (should be max one row)</returns>
public abstract IDataReader GetTabViaTabModuleIdFromDb(
                                         int tabModuleId);

/// <summary>
/// Gets node with specified TabId from Db
/// </summary>
/// <param name="nodeTabId">TabId for node</param>
/// <returns>IDataReader: TabId (int), TabName (string), 
/// Children (int)</returns>
public abstract IDataReader GetNodeFromDb(int nodeTabId);

You can find the implementation of these functions in the SqlDataProvider.cs file. They are basically simple SQL SELECT statements. In future releases, these will be in a stored procedure, to gain some extra performance.

The business layer can be found in the controller class in DnnSiteMapController.cs. The functions are:

/// <summary>
/// Retrieves all visible root nodes from Db
/// </summary>
/// <returns>List of root nodes as ExtendedTreeNode
/// </returns>
public List<ExtendedTreeNode> GetRootNodesFromDb()

/// <summary>
/// Retrieves Child Nodes from Db for given Node
/// </summary>
/// <param name="parentNode">ParentNode, 
/// for which the children should be retrieved</param>
/// <returns>List of children as ExtendedTreeNode
/// </returns>
public List<ExtendedTreeNode> GetChildNodesFromDb(
                                       TreeNode parentNode)

/// <summary>
/// Gets the navigation path for a given Tab to the root
/// </summary>
/// <param name="childTab">Tab for 
/// which the path should be retrieved</param>
/// <returns>List of Tabs, begining with the root 
/// and ending with the Child</returns>
public List<Structs.Tab> GetNavigationPathFromDb(
                                      Structs.Tab childTab)

/// <summary>
/// Gets the Tab, that hosts the given module
/// </summary>
/// <param name="tabModuleId">TabModuleId of the module
/// </param>
/// <returns>Dnn TabId</returns>
public Structs.Tab GetTabViaTabModuleIdFromDb(int tabModuleId)

/// <summary>
/// Gets node with specified TabId from Db
/// </summary>
/// <param name="nodeTabId">TabId for node</param>
/// <returns>Specified node; null if node is not found
/// </returns>
public ExtendedTreeNode GetNodeFromDb(int nodeTabId)

The UI code is in the ViewDnnSiteMap.ascx.cs file. In the Page_Load event, the settings are applied and the root nodes are retrieved from the DB. Then, the tree expands to the current tab (the page which is hosting the control):

protected void Page_Load(System.Object sender, 
                               System.EventArgs e)
        if (!IsPostBack)
            // controller class
            DnnSiteMapController objDnnSiteMaps = 
                           new DnnSiteMapController();

            // config settings
            ConfigurationSettings settings = 
                 new ConfigurationSettings(this.Settings);

            // set show lines
            this.TreeView1.ShowLines = settings.ShowLines;

            // set image set
            this.TreeView1.ImageSet = settings.ImageSet;

            // set node wrap
            this.TreeView1.NodeWrap = settings.NodeWrap;

            // set show controls
            this.pnlControls.Visible = settings.ShowControls;

            // set node indent
            this.TreeView1.NodeIndent = settings.NodeIndent;

            // fill root nodes or specified rootNode

            // get current TabId from DNN and expand to it
    catch (Exception exc) //Module failed to load
        Exceptions.ProcessModuleLoadException(this, exc);

In the TreeNodeExpanded event, I check if the node already has its children, if not, I retrieve them from the DB:

protected void TreeView1_TreeNodeExpanded(object sender, 
                                       TreeNodeEventArgs e)
    // if node has DummyNode, else data was 
    // already retrieved from Db
    if (NodeHelper.HasDummyNode(e.Node))
        // controller class
        DnnSiteMapController objDnnSiteMaps = 
                         new DnnSiteMapController();

        // clear child nodes

        // for all child nodes
        foreach (ExtendedTreeNode childNode in 
            // if root has children, add dummy node
            if (childNode.HasChildren)

            // add children to expanded node

    // select current node

The private ExpandToTab(int tabId) method is used in the Page_Load event. This method is used to expand the tree to a specified node and select it. This is very handy in the Page_Load event, because you can set the tab to the current page:

private void ExpandToTab(int tabId)
    // controller class
    DnnSiteMapController objDnnSiteMaps = 
                      new DnnSiteMapController();

    // collapses all nodes; IMPORTANT to use this function
    // instead directly TreeView.CollapseAll(), because
    // it can loose all nodes

    // find node in tree view (no roundtrip to Db)
    TreeNode node = 
        NodeHelper.GetNode(this.TreeView1.Nodes, tabId);

    // check if node is already in tree view
    if (node != null)
        TreeNode currentNode = node;

        // expand to node
        while (currentNode != null)
            currentNode = currentNode.Parent;
    else // get parent path from Db
        List<Structs.Tab> parentTabs = 
                 new Structs.Tab(tabId, String.Empty));
        TreeNode currentNode = null;

        // expand all nodes along path
        foreach (Structs.Tab nodeTab in parentTabs)
            currentNode = 

            if (currentNode != null)

    // select current node

Points of interest

DNN installation instructions

Step 1: Install the module via DNN

Login in as host, and select in the Host menu "Module Definitions". At the bottom of the page, press "Upload new module". Then, select the .zip file, add it, and then upload it.

Step 2: Alter web.config file (without this, DNN won't work anymore)

The module is written in C#, you must include the following lines in your web.config file, in the <system.web> <compilation> section (it should already be there, but commented out):

   <add directoryName="DnnSiteMap"/>
Value property of the TreeNode class

Because every tab (page) in DNN has a TabId, I save that information with every TreeNode. For that, I use the value field. It is of type string, so, I convert it to int whenever needed.

ExtendedNode class

The ExtendedNode class is derived directly from the System.Web.UI.WebControls.TreeNode class. It adds the boolean field HasChildren to the class. This is used when I retrieve the nodes from the DB; then I set that flag to true, if they have child nodes. With this little trick, I can spare an extra roundtrip to the DB.


The TreeView control shows a plus sign to expand a node, if it has at least one child node. Because I don't want to retrieve nodes as long as the parent is expanded, I fill them with a DummyNode. To differentiate from a normal node, I set their value field to -1.


I encapsulated the settings in the class ConfigurationSettings. It reads the settings out of a Hashtable (e.g., ModuleSettingsBase.TabModuleSettings or PortalModuleBase.Settings), makes them type-safe, and initializes them with default values.


New features can be: using stored procedures; defining custom CSS classes .... So, there is still lots to do.


DNNSiteMap, Copyright (c) 2006 by BitConstruct, Halanek & Co KEG (BitConstruct).

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and the associated documentation files (the "Software"), to deal with the Software without restriction, including without limitation of the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.



  • 2006/01/08 - V 0.1
    • Proof of concept, not for productive use (!).
  • 2006/01/13 - V 1.0
    • Working version with many new features (DNN 4 and ASP.NET 2.0 only).
  • 2006/01/28 - V 1.0.1
    • Minor bug fix: ObjectQualifier for DB objects is now used.
  • 2006/04/24 - V 1.0.2
    • New flag to view only tabs which the user has permissions to view.
    • Highlight current node can be turned off.
  • 2006/05/07 - V 1.0.3
    • DNNSitemap now uses friendly URLs, if enabled in host settings.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Rainer Halanek
Software Developer (Senior)
Austria Austria
Born and living in Vienna, Austria. Started with Pascal in 1993 and MS-DOS 5.0. After that a little C++ in OS/2 and loads of VBA with Access in Windows 95,98, NT. To get more professionel I started C# in 2002 and did some MCP exams on that. After working for my own company I got hired by different companies. Currently I'm employed at the Federal Chambers of Commerce as a Senior Software Engineer.

Comments and Discussions

GeneralChange font size Pin
knappster6-Aug-07 3:51
Memberknappster6-Aug-07 3:51 
QuestionChanging the layout Pin
gvborre2-Jul-07 22:25
Membergvborre2-Jul-07 22:25 
AnswerRe: Changing the layout Pin
Rainer Halanek4-Jul-07 0:33
MemberRainer Halanek4-Jul-07 0:33 
GeneralRe: Changing the layout Pin
gvborre6-Jul-07 5:44
Membergvborre6-Jul-07 5:44 
GeneralError after installation Pin
PeaceTiger1-Jul-07 23:33
MemberPeaceTiger1-Jul-07 23:33 
GeneralWrong behaviour on link-type pages Pin
McZosch19-Jun-07 1:44
MemberMcZosch19-Jun-07 1:44 
GeneralGeneral Question : Pb of performance Pin
Nizar Abdeljaoued27-May-07 22:39
MemberNizar Abdeljaoued27-May-07 22:39 
GeneralInstall Error [modified] Pin
Mike Fulkrod2-Feb-07 10:22
MemberMike Fulkrod2-Feb-07 10:22 
I just tried installing this on a DotNetNuke 4.4.1 portal. I have used this on other portals without any problems. This is the error I get:

StartJob Starting Installation
StartJob Reading files
Info Loading ExtendedTreeNode.cs
Info File ExtendedTreeNode.cs read successfully
Info Loading NodeHelper.cs
Info File NodeHelper.cs read successfully
Info Loading Settings.ascx
Info File Settings.ascx read successfully
Info Loading Settings.ascx.cs
Info File Settings.ascx.cs read successfully
Info Loading Settings.ascx.resx
Info File Settings.ascx.resx read successfully
Info Loading SqlDataProvider.cs
Info File SqlDataProvider.cs read successfully
Info Loading Structs.cs
Info File Structs.cs read successfully
Info Loading Uninstall.SqlDataProvider
Info File Uninstall.SqlDataProvider read successfully
Info Loading ViewDnnSiteMap.ascx
Info File ViewDnnSiteMap.ascx read successfully
Info Loading ViewDnnSiteMap.ascx.cs
Info File ViewDnnSiteMap.ascx.cs read successfully
Info Loading ViewDnnSiteMap.ascx.resx
Info File ViewDnnSiteMap.ascx.resx read successfully
Info Loading 01.00.00.SqlDataProvider
Info File 01.00.00.SqlDataProvider read successfully
Info Loading AssemblyInfo.cs
Info File AssemblyInfo.cs read successfully
Info Loading ConfigurationSettings.cs
Info File ConfigurationSettings.cs read successfully
Info Loading DataProvider.cs
Info File DataProvider.cs read successfully
Info Loading DnnSiteMap.dnn
Info File DnnSiteMap.dnn read successfully
Info Loading DnnSiteMapController.cs
Info File DnnSiteMapController.cs read successfully
EndJob Reading files done.

StartJob Reading DNN file
Info DNN file is in valid 3.0 format.
Info xml loaded.
Info Loading files info
Info Found valid path (App_LocalResources) for ViewDnnSiteMap.ascx.resx.
Info Found valid path (App_LocalResources) for Settings.ascx.resx.
Info Found valid path ([app_code]) for DataProvider.cs.
Info Found valid path ([app_code]) for DnnSiteMapController.cs.
Info Found valid path ([app_code]) for SqlDataProvider.cs.
Info Found valid path ([app_code]) for ExtendedTreeNode.cs.
Info Found valid path ([app_code]) for NodeHelper.cs.
Info Found valid path ([app_code]) for Structs.cs.
Info Found valid path ([app_code]) for ConfigurationSettings.cs.
Info Found valid path ([app_code]) for AssemblyInfo.cs.
Info Loading Modules info
Info Loading Control info for 'DnnSiteMap' module
EndJob Dnn load finished successfully

StartJob Begin Sql execution
Info Executing 01.00.00.SqlDataProvider
StartJob Start Sql execution: 01.00.00.SqlDataProvider file
EndJob End Sql execution: 01.00.00.SqlDataProvider file

EndJob Finished Sql execution

StartJob Creating files
Info Created C:\Inetpub\wwwroot\DotNetNuke\DesktopModules\DnnSiteMap\ViewDnnSiteMap.ascx
Info Created C:\Inetpub\wwwroot\DotNetNuke\DesktopModules\DnnSiteMap\ViewDnnSiteMap.ascx.cs
Info Created C:\Inetpub\wwwroot\DotNetNuke\DesktopModules\DnnSiteMap\Settings.ascx
Info Created C:\Inetpub\wwwroot\DotNetNuke\DesktopModules\DnnSiteMap\Settings.ascx.cs
Info Created C:\Inetpub\wwwroot\DotNetNuke\DesktopModules\DnnSiteMap\App_LocalResources\ViewDnnSiteMap.ascx.resx
Info Created C:\Inetpub\wwwroot\DotNetNuke\DesktopModules\DnnSiteMap\App_LocalResources\Settings.ascx.resx
Failure ExceptionSystem.Xml.XmlException: Reference to undeclared entity 't'. Line 25, position 85. at System.Xml.XmlTextReaderImpl.Throw(Exception e) at System.Xml.XmlTextReaderImpl.HandleGeneralEntityReference(String name, Boolean isInAttributeValue, Boolean pushFakeEntityIfNullResolver, Int32 entityStartLinePos) at System.Xml.XmlTextReaderImpl.HandleEntityReference(Boolean isInAttributeValue, EntityExpandType expandType, Int32& charRefEndPos) at System.Xml.XmlTextReaderImpl.ParseAttributeValueSlow(Int32 curPos, Char quoteChar, NodeData attr) at System.Xml.XmlTextReaderImpl.ParseAttributes() at System.Xml.XmlTextReaderImpl.ParseElement() at System.Xml.XmlTextReaderImpl.ParseElementContent() at System.Xml.XmlTextReaderImpl.Read() at System.Xml.XmlLoader.LoadNode(Boolean skipOverWhitespace) at System.Xml.XmlLoader.LoadDocSequence(XmlDocument parentDoc) at System.Xml.XmlLoader.Load(XmlDocument doc, XmlReader reader, Boolean preserveWhitespace) at System.Xml.XmlDocument.Load(XmlReader reader) at System.Xml.XmlDocument.Load(String filename) at DotNetNuke.Common.Utilities.Config.AddCodeSubDirectory(String name) at DotNetNuke.Modules.Admin.ResourceInstaller.PaDnnInstallerBase.CreateModuleFile(PaFile File, PaFolder Folder) at DotNetNuke.Modules.Admin.ResourceInstaller.PaDnnInstallerBase.CreateFiles(PaFolder Folder) at DotNetNuke.Modules.Admin.ResourceInstaller.PaDnnInstallerBase.Install(PaFolderCollection folders) at DotNetNuke.Modules.Admin.ResourceInstaller.PaInstaller.Install()

Generalall|Collapse All|current and no more... Pin
smslt30-Jan-07 5:59
Membersmslt30-Jan-07 5:59 
GeneralRe: all|Collapse All|current and no more... Pin
ibihab6824-Apr-07 8:44
Memberibihab6824-Apr-07 8:44 
GeneralWeb Config Error when module is deleted Pin
philip_b19-Jan-07 4:51
Memberphilip_b19-Jan-07 4:51 
GeneralRe: Web Config Error when module is deleted Pin
Rainer Halanek19-Jan-07 5:55
MemberRainer Halanek19-Jan-07 5:55 
Generalsortorder differs from solpartmenu Pin
Jan de Vos10-Oct-06 5:53
MemberJan de Vos10-Oct-06 5:53 
GeneralRe: sortorder differs from solpartmenu Pin
Ian Symonds1-Jul-07 21:37
MemberIan Symonds1-Jul-07 21:37 
QuestionStill Cant see anything Pin
Corben17-Oct-06 8:45
MemberCorben17-Oct-06 8:45 
QuestionTable Pin
Corben126-Sep-06 8:24
MemberCorben126-Sep-06 8:24 
AnswerRe: Table Pin
Rainer Halanek26-Sep-06 23:53
MemberRainer Halanek26-Sep-06 23:53 
GeneralRe: Table Pin
Corben127-Sep-06 6:09
MemberCorben127-Sep-06 6:09 
GeneralRe: Table Pin
Rainer Halanek27-Sep-06 6:42
MemberRainer Halanek27-Sep-06 6:42 
GeneralRe: Table Pin
Corben127-Sep-06 7:54
MemberCorben127-Sep-06 7:54 
JokeCan't install it Pin
mrsbeata6-Sep-06 0:04
Membermrsbeata6-Sep-06 0:04 
GeneralRe: Can't install it Pin
Rainer Halanek6-Sep-06 4:33
MemberRainer Halanek6-Sep-06 4:33 
GeneralSolution : Module Installation problem Pin
Dudi Arbiv4-Sep-06 1:45
MemberDudi Arbiv4-Sep-06 1:45 
GeneralRe: Solution : Module Installation problem Pin
Rainer Halanek4-Sep-06 1:52
MemberRainer Halanek4-Sep-06 1:52 
QuestionControl text (collapse all/expand all/ current) Pin
Karsten Brocksieper29-Aug-06 0:48
MemberKarsten Brocksieper29-Aug-06 0:48 

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.