Search code examples
asp.netwebformsasp.net-4.0web.sitemap

How can I render an entire sitemap in script as a list, reading both title and url?


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 *

  1. Dim node As SiteMapNode -- this variable is defined before the while loop and represents the "current" node in the while loop.
  2. node = CType(rootNodesChildrenEnumerator.Current, SiteMapNode) -- the node value is updated each pass through the while loop
  3. node.Url -- this is how to read the URL of the "current" node

*This goes in the List_Childnodes function *

  1. Dim node As SiteMapNode
  2. node = CType(childNodesEnumerator.Current, SiteMapNode)
  3. 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>


Solution

  • 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>