My objective is to render the full web.sitemap as a nested unordered list. By full, I mean to produce the entire sitemap from root to all descendants. This list must contain the title and url of each node.
This process is useful when your website is a Windows Authenticated network and you need to create a navigation list with secuirty trimming by Active Directory role.
Context -- This is an asp.net 4.0 webpage using Visual Studio 2010. I use strict = true
and explicit = true
.
Background & What I've done so far
This page https://msdn.microsoft.com/en-us/library/system.web.sitemap(v=vs.110).aspx shows an example of how to generate a sitemap based on "current node," but I want to generate a full sitemap from the root to all children.
The URL gave me the inspiration to set up a recursive function, but my problem is while I can get the node titles I am unable to get the node URL to accompany it.
Update (5/7/2015): I learned that I was missing a critical piece of code necessary to get the URL of the "current" node. The ordered list emphasizes the new additions, and I'm posting my refreshed code.
I have discovered that while I can get two levels deep from the root node, I am not going any further, which leads me to believe that the recursion script is not defined correctly.
*This goes in the LoadMe sub *
Dim node As SiteMapNode
-- this variable is defined before the while loop and represents the "current" node in the while loop.node = CType(rootNodesChildrenEnumerator.Current, SiteMapNode)
-- the node value is updated each pass through the while loopnode.Url
-- this is how to read the URL of the "current" node*This goes in the List_Childnodes function *
Dim node As SiteMapNode
node = CType(childNodesEnumerator.Current, SiteMapNode)
sb.Append(childNodesEnumerator.Current.ToString())
Here is the full (updated) code.
Thank you for any ideas you can provide in improving the recursion and ideas to make the code syntax better.
<%@ Page Title="Sitemap Test" Language="VB" MasterPageFile="~/MasterPage.master" Strict="true" Explicit="true" %>
<script runat="server">
Private Sub LoadMe(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Dim sb As New StringBuilder
' Examine the RootNode, and navigate the SiteMap relative to it.
sb.Append("<ul>")
sb.Append("<li><a href=""" & Page.ResolveClientUrl(SiteMap.RootNode.Url) & """>" & SiteMap.RootNode.Title & "</a></li>")
' What nodes are children of the RootNode?
If (SiteMap.RootNode.HasChildNodes) Then
Dim rootNodesChildrenEnumerator As IEnumerator = SiteMap.RootNode.ChildNodes.GetEnumerator()
Dim node As SiteMapNode
While (rootNodesChildrenEnumerator.MoveNext())
node = CType(rootNodesChildrenEnumerator.Current, SiteMapNode)
sb.Append("<li><a href=""" & node.Url & """>" & rootNodesChildrenEnumerator.Current.ToString() & "</a></li>")
sb.Append(List_Childnodes(CType(rootNodesChildrenEnumerator.Current, SiteMapNode)))
End While
End If
sb.Append("</ul>")
lblSitemap.Text = sb.ToString
End Sub
Function List_Childnodes(Current_Node As SiteMapNode) As String
Dim sb As New StringBuilder
' What nodes are children of the function parameter?
If (Current_Node.HasChildNodes) Then
Dim childNodesEnumerator As IEnumerator = Current_Node.ChildNodes.GetEnumerator()
sb.Append("<ul>")
Dim node As SiteMapNode
While (childNodesEnumerator.MoveNext())
' Prints the Title of each node.
node = CType(childNodesEnumerator.Current, SiteMapNode)
sb.Append("<li>")
sb.Append("<a href=""" & node.Url & """>")
sb.Append(childNodesEnumerator.Current.ToString())
sb.Append("</a>")
sb.Append("</li>")
' Because I didn't get all children, I tried calling the same function here
' to see if I could get all child descendents.
' this didn't work
List_Childnodes(node)
End While
sb.Append("</ul>")
End If
Return sb.ToString
End Function
</script>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
<h1>Sitemap Test</h1>
<p>The <var>LoadMe</var> sub runs on Me.Load and recursively calls all children. The root node is manually moved into the first level for user convenience.</p>
<h2>Sitemap tree</h2>
<asp:Label ID="lblSitemap" runat="server" Text="Label"></asp:Label>
It turned out that all I needed was to write one line differently.
List_Childnodes(node)
should have been sb.Append(List_Childnodes(node))
I verified that security trimming of roles (as defined in web.sitemap) works very well.
Edit: I also realized that I needed to call the List_ChildNodes(node)
function before I closed the </li>
tag. Now the nested UL is properly formed.
Second edit: I also wrapped Page.ResolveClientUrl()
around node.url
to make absolutely sure the links will work properly when viewed from both localhost and the server.
<%@ Page Title="" Language="VB" MasterPageFile="~/MasterPage.master" %>
<script runat="server">
Public strSitemap As String = String.Empty
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
Dim sb As New StringBuilder
' Examine the RootNode, and navigate the SiteMap relative to it.
' What nodes are children of the RootNode?
If (SiteMap.RootNode.HasChildNodes) Then
Dim rootNodesChildrenEnumerator As IEnumerator = SiteMap.RootNode.ChildNodes.GetEnumerator()
Dim node As SiteMapNode
While (rootNodesChildrenEnumerator.MoveNext())
node = CType(rootNodesChildrenEnumerator.Current, SiteMapNode)
If node.Url IsNot Nothing Then
sb.Append("<li><a href=""" & Page.ResolveClientUrl(node.Url) & """>" & rootNodesChildrenEnumerator.Current.ToString() & "</a>" & vbCrLf)
Else
sb.Append("<li>" & rootNodesChildrenEnumerator.Current.ToString() & vbCrLf)
End If
sb.Append(vbTab & List_Childnodes(CType(rootNodesChildrenEnumerator.Current, SiteMapNode)))
sb.Append("</li>")
End While
End If
strSitemap = sb.ToString
End Sub
Function List_Childnodes(Current_Node As SiteMapNode) As String
Dim sb As New StringBuilder
' What nodes are children of the function parameter?
If (Current_Node.HasChildNodes) Then
Dim childNodesEnumerator As IEnumerator = Current_Node.ChildNodes.GetEnumerator()
sb.Append(vbTab & "<ul>")
While (childNodesEnumerator.MoveNext())
' Prints the Title of each node.
Dim node As SiteMapNode = CType(childNodesEnumerator.Current, SiteMapNode)
sb.Append(vbTab & "<li>")
sb.Append("<a href=""" & Page.ResolveClientUrl(node.Url) & """>")
sb.Append(childNodesEnumerator.Current.ToString())
sb.Append("</a>")
' this is how the recursion captures all child nodes
sb.Append(List_Childnodes(node))
sb.Append("</li>" & vbCrLf)
End While
sb.Append("</ul>" & vbCrLf)
End If
Return sb.ToString
End Function
</script>
<asp:Content ID="Content1" ContentPlaceHolderID="head" runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="ContentPlaceHolder1" runat="Server">
<h1>Sitemap Test</h1>
<ul>
<li>
<asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/index.aspx">Homepage</asp:HyperLink></li>
<%= strSitemap%>
</ul>
</asp:Content>