Introduction
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, SiteMap
s 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).
Background
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.
Features
- 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:
public abstract IDataReader GetRootNodesFromDb();
public abstract IDataReader GetChildNodesFromDb(int parentTabId);
public abstract IDataReader GetParentFromDb(int childTabId);
public abstract IDataReader GetTabViaTabModuleIdFromDb(
int tabModuleId);
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:
public List<ExtendedTreeNode> GetRootNodesFromDb()
public List<ExtendedTreeNode> GetChildNodesFromDb(
TreeNode parentNode)
public List<Structs.Tab> GetNavigationPathFromDb(
Structs.Tab childTab)
public Structs.Tab GetTabViaTabModuleIdFromDb(int tabModuleId)
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)
{
try
{
if (!IsPostBack)
{
DnnSiteMapController objDnnSiteMaps =
new DnnSiteMapController();
ConfigurationSettings settings =
new ConfigurationSettings(this.Settings);
this.TreeView1.ShowLines = settings.ShowLines;
this.TreeView1.ImageSet = settings.ImageSet;
this.TreeView1.NodeWrap = settings.NodeWrap;
this.pnlControls.Visible = settings.ShowControls;
this.TreeView1.NodeIndent = settings.NodeIndent;
this.FillRootNodes(settings.RootNode);
this.ExpandToTab(this.TabId);
}
}
catch (Exception exc)
{
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 (NodeHelper.HasDummyNode(e.Node))
{
DnnSiteMapController objDnnSiteMaps =
new DnnSiteMapController();
e.Node.ChildNodes.Clear();
foreach (ExtendedTreeNode childNode in
objDnnSiteMaps.GetChildNodesFromDb(e.Node))
{
if (childNode.HasChildren)
{
NodeHelper.AddDummyNode(childNode);
}
e.Node.ChildNodes.Add(childNode);
}
}
this.SelectCurrentNode();
}
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)
{
DnnSiteMapController objDnnSiteMaps =
new DnnSiteMapController();
this.CollapseAll();
TreeNode node =
NodeHelper.GetNode(this.TreeView1.Nodes, tabId);
if (node != null)
{
TreeNode currentNode = node;
while (currentNode != null)
{
currentNode.Expand();
currentNode = currentNode.Parent;
}
}
else
{
List<Structs.Tab> parentTabs =
objDnnSiteMaps.GetNavigationPathFromDb(
new Structs.Tab(tabId, String.Empty));
TreeNode currentNode = null;
foreach (Structs.Tab nodeTab in parentTabs)
{
currentNode =
NodeHelper.GetNode(this.TreeView1.Nodes,
nodeTab.TabId);
if (currentNode != null)
{
currentNode.Expand();
}
}
}
this.SelectCurrentNode();
}
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):
<codeSubDirectories>
<add directoryName="DnnSiteMap"/>
</codeSubDirectories>
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.
DummyNodes
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
.
ConfigSettings
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.
Roadmap
New features can be: using stored procedures; defining custom CSS classes .... So, there is still lots to do.
Copyright
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.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
History
- 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.