Background: I am currently working on a project that has a forked workflow, users will be working on one of a number of streams and due to the requirements of the tasks involved will not be able to move back and forth between these streams.
For this reason and to improve usability we wanted to hide specific site map nodes from the main TreeView menu and have these displayed in a seperate colour coded TreeView menu which would appear and disappear based on the current workflow. i.e. Pages with functions specific to any given workflow would only appear once the user had started that workflow (nb. with this approach it is still important to have error handling on each page should the user session not contain expected data for that control/page on load, and either alert the user or direct the user back to the appropriate point in the workflow as users directly type the URL, use back/forward buttons or browser history- simply hiding the link is not enough).
We set up individual workflow TreeView menus with seperate sitemap files containing only those pages and nested from the main sitemap so we could-
- Provide a full sitemap on the custom 404 page
- Use a SiteMapPath to show a breadcrumb
The structure of the sitemaps is therefore-
Web.sitemap
-Workflow1.sitemap
-Workflow2.sitemap
Sitemaps can be nested from the primary sitemap by using the following syntax-
<siteMapNode siteMapFile="Workflow1.sitemap" />
The nesting allows the SiteMapPath to see your individual workflow pages, but also makes these visible to a asp:TreeView control using the primary/default sitemap.
To deal with this I borrowed heavily from a ASP.net site forum thread, which gave me the bulk of the solution- only a small tweak to the code was required to remove the nodes.
In the sitemap files I added a attribute “visible=false” to those pages I did not want to appear on the main menu. The syntax for this is-
<siteMapNode url="~/Workflow1/Page.aspx" title="Page Title" description="Page Description" visible="false" />
I then added a “OnTreeNodeDataBound” event handler to the asp:TreeView control. The syntax for this is-
<asp:TreeView DataSourceID="SiteMapDataSource1" ExpandDepth="0" ID="TreeView1" OnTreeNodeDataBound="TreeView1_TreeNodeDataBound" runat="server"></asp:TreeView>
In the code behind find (Page.aspx.cs) I added the code-
protected void TreeView1_TreeNodeDataBound(object sender, TreeNodeEventArgs e)
{
SiteMapNode node = e.Node.DataItem as SiteMapNode;
// Check for the visible attribute and if false remove the node from the parent
// this allows nodes to appear in the SiteMapPath but not show on the menu
if (!string.IsNullOrEmpty(node["visible"]))
{
bool isVisible = false;
if (bool.TryParse(node["visible"], out isVisible))
{
if (!isVisible)
{
TreeView1.Nodes.Remove(e.Node);
}
}
}
}
This is almost identical to the previously linked thread and credit must go to Dave Sussman for this, but in order to get it to work without the “Object reference not set to an instance of an object” error cited by banksidepoet I had to change the line-
e.Node.Parent.ChildNodes.Remove(e.Node);
To read-
TreeView1.Nodes.Remove(e.Node);
This hides the unwanted nodes from the main menu, allowing us to set up additional asp:TreeView controls with a corresponding asp:SiteMapDataSource. In order to allow this to point the SiteMapDataSource to a different sitemap we need to add providers to the Web.config-
<siteMap defaultProvider="FullSiteMapProvider">
<providers>
<add name="FullSiteMapProvider" siteMapFile="Web.sitemap" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<add name="Workflow1SiteMapProvider" siteMapFile="Workflow1.sitemap" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<add name="Workflow2SiteMapProvider" siteMapFile="Workflow2.sitemap" type="System.Web.XmlSiteMapProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
</providers>
</siteMap>
The SiteMapDataSource control can then have the SiteMapProvider attribute set to tell it to use the seperate .sitemap file containing that subset of pages-
<asp:SiteMapDataSource ID="Workflow1SiteMapDataSource" ShowStartingNode="false" SiteMapProvider="Workflow1SiteMapProvider" runat="server" />
This then needs to be set as the DataSourceID of the TreeView itself-
<asp:TreeView DataSourceID="Workflow1SiteMapDataSource" ID="Workflow1TreeView" runat="server" >
</asp:TreeView>
Lastly, with each of the nested sitemap files (as all sitemaps require a single root siteMapNode) I used the format-
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode title="Workflow 1" visible="false">
<siteMapNode url="~/Workflow1/Page1.aspx" title="Page 1 title" description="Page 1 description" visible="true" />
<siteMapNode url="~/Workflow1/Page2.aspx" title="Page 2 title" description="Page 2 description" visible="true" />
<siteMapNode url="~/Workflow1/Page3.aspx" title="Page 3 title" description="Page 3 description" visible="true" />
</siteMapNode>
</siteMap>
You’ll note there is no URL or description set for the parent node, it is not displayed in our treeview menus because we have set ShowStartingNode=”false” in our SiteMapDataSource but the title is displayed in the breadcrumb. Thus we would see-
Site > Workflow 1 > Page 1 title
Generated by the SiteMapPath control.
I hope this helps save someone time and research.