How to: Programmatically Modify Site-Map Nodes in Memory

Web sites frequently use dynamic URLs containing information that is appended as query strings. For example, the site for a newsgroup or forum might include static URLs that refer to forums or groups as well as dynamic URLs for each post. A URL for a post might be in the following format: https://www.microsoft.com/newsgroups/ShowPost.aspx?ForumID=2\&PostID=53

Updating a site map to list the URL for every post is not an efficient approach because nodes cannot be added to a site map programmatically. However, when a user is viewing a post, you can use the SiteMapPath control to display the navigation path back to the root node and dynamically append query strings to each link in the path, identifying the post, forum, or group. For example, your navigation path to the preceding post might look like the following:

Home > Forum List > Post List

The static Url property of the site-map node for Posts might be set to https://www.microsoft.com/newsgroups/ShowPost.aspx. But in memory, you can modify the URL in the SiteMapPath control to identify the forum and the specific post.

You can change site-map nodes in memory by using the SiteMapResolve event as shown in the following procedure and example.

To programmatically change site-map nodes

  1. In the code for a Web forms page, create a method to handle the SiteMapResolve event. For example, the following declaration creates a method called ExpandForumPaths.

    private SiteMapNode ExpandForumPaths(Object sender, 
                                         SiteMapResolveEventArgs e)
    
    Private Function ExpandForumPaths(ByVal sender As Object, ByVal e As SiteMapResolveEventArgs) As SiteMapNode
    
  2. In your event handler, obtain a reference to the current node and clone it. For example, if the node is a newsgroup posting, your code might look like the following.

    SiteMapNode currentNode = SiteMap.CurrentNode.Clone(true);
    SiteMapNode tempNode = currentNode;
    
    Dim currentNode As SiteMapNode = SiteMap.CurrentNode.Clone(True)
    Dim tempNode As SiteMapNode = currentNode
    

    The tempNode variable returns a site-map node in memory that can be traversed, modifying each Url property or other properties. The reference in nodeCopy is maintained separately because the expected return value from the event handler is a reference to the current node. However, the tempNode variable will be used to recursively move up the navigation structure.

    Note

    Because the cloned node is separate from the static site-navigation structure, the changes to the Url properties do not persist in memory nor are they save to disk.

  3. Change the Url properties of the current node and its parent node to include query-string information that lists the post, forum, and group identifiers.

    For example, the following code example assumes the existence of three methods that obtain the identifiers.

    int forumGroupID = GetMostRecentForumGroupID();
    int forumID = GetMostRecentForumID(forumGroupID);
    int postID = GetMostRecentPostID(forumID);
    
    if (0 != postID)
    {
        tempNode.Url = tempNode.Url + "?PostID=" + postID.ToString();
    }
    
    if ((null != (tempNode = tempNode.ParentNode)) &&
        (0 != forumID))
    {
        tempNode.Url = tempNode.Url + "?ForumID=" + forumID.ToString();
    }
    
    if ((null != (tempNode = tempNode.ParentNode)) &&
        (0 != forumGroupID))
    {
        tempNode.Url = tempNode.Url + "?ForumGroupID=" + forumGroupID.ToString();
    }
    
    Dim forumGroupID As Integer = GetMostRecentForumGroupID()
    Dim forumID As Integer = GetMostRecentForumID(forumGroupID)
    Dim postID As Integer = GetMostRecentPostID(forumID)
    
    If Not (0 = postID) Then
        tempNode.Url = tempNode.Url & "?PostID=" & postID.ToString()
    End If
    
    tempNode = tempNode.ParentNode
    If Not (0 = forumID) And Not (Nothing = tempNode) Then
        tempNode.Url = tempNode.Url & "?ForumID=" & forumID.ToString()
    End If
    
    tempNode = tempNode.ParentNode
    If Not (0 = ForumGroupID) And Not (Nothing = tempNode) Then
        tempNode.Url = tempNode.Url & "?ForumGroupID=" & forumGroupID.ToString()
    End If
    

    Note

    The if statements are used to ensure that query strings are only added to existing site-map nodes, and only if the group, form, and post identifiers are available.

  4. Return the cloned node by using the following line of code.

    return currentNode;
    
    Return currentNode
    
  5. Register your event handler in the Page_Load method. For example, your code might look like the following example.

    SiteMap.SiteMapResolve +=
      new SiteMapResolveEventHandler(this.ExpandForumPaths);
    
    AddHandler SiteMap.SiteMapResolve, AddressOf Me.ExpandForumPaths
    

    Note

    The SiteMapResolve event is raised when the site-map provider accesses the CurrentNode property, such as when the SiteMapPath control is rendering a navigation structure.

  6. Add a SiteMapPath control in your Web Forms page to view the navigation structure. Your SiteMapPath control might look like the following.

    <asp:SiteMapPath
    id="SiteMapPath1"
    runat="server"
    RenderCurrentNodeAsLink="true" />
    
  7. Make sure that there is a node for your Web Forms page in your site-map file. For example, if your Web Forms page is called ShowPost.aspx, your Web.sitemap file might look like the following.

    <?xml version="1.0" encoding="utf-8" ?>
    <siteMap>
      <siteMapNode
        title="Forum Group" 
        description="Forum Group List"
        url="default.aspx">
        <siteMapNode 
          title="Forum" 
          description="Forum List"
          url="ShowForum.aspx">
          <siteMapNode 
            title="Post" 
            description="Post List" 
            url="ShowPost.aspx" />
        </siteMapNode>
      </siteMapNode>
    </siteMap>
    

Example

The following code example demonstrates how you can handle the SiteMapResolve event on an ASP.NET Web page to modify the target URLs that are displayed by the SiteMapPath control. In this example, the current page is a post page in an online bulletin board or forum. To render more meaningful site navigation, the URLs of the nodes that are displayed by the SiteMapPath control are appended with context-relevant query strings. Use the following code to render the control.

<asp:SiteMapPath
id="SiteMapPath1"
runat="server"
RenderCurrentNodeAsLink="true" />
<asp:SiteMapPath
id="SiteMapPath1"
runat="server"
RenderCurrentNodeAsLink="true" />

When running the example, place your cursor over the links in the SiteMapPath control to see how the URLs are changed.

This example does not add SiteMapNode items in the Web.sitemap file; the Web.sitemap file can only be edited manually.

Note

It is safe to access the CurrentNode property from within the SiteMapResolveEventHandler. The ASP.NET site-navigation infrastructure guards against infinite recursion in this case.

This example assumes that you already have a valid site-map file, and that the current page is at least three nodes deep in the site-map structure. For more information about creating site maps, see ASP.NET Site Maps.

Private Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)

    ' The ExpandForumPaths method is called to handle
    ' the SiteMapResolve event.
    AddHandler SiteMap.SiteMapResolve, AddressOf Me.ExpandForumPaths

End Sub

Private Function ExpandForumPaths(ByVal sender As Object, ByVal e As SiteMapResolveEventArgs) As SiteMapNode
    ' The current node represents a Post page in a bulletin board forum.
    ' Clone the current node and all of its relevant parents. This
    ' returns a site map node that a developer can then
    ' walk, modifying each node.Url property in turn.
    ' Since the cloned nodes are separate from the underlying
    ' site navigation structure, the fixups that are made do not
    ' effect the overall site navigation structure.
    Dim currentNode As SiteMapNode = SiteMap.CurrentNode.Clone(True)
    Dim tempNode As SiteMapNode = currentNode

    ' Obtain the recent IDs.
    Dim forumGroupID As Integer = GetMostRecentForumGroupID()
    Dim forumID As Integer = GetMostRecentForumID(forumGroupID)
    Dim postID As Integer = GetMostRecentPostID(forumID)

    ' The current node, and its parents, can be modified to include
    ' dynamic querystring information relevant to the currently
    ' executing request.
    If Not (0 = postID) Then
        tempNode.Url = tempNode.Url & "?PostID=" & postID.ToString()
    End If

    tempNode = tempNode.ParentNode
    If Not (0 = forumID) And Not (tempNode Is Nothing) Then
        tempNode.Url = tempNode.Url & "?ForumID=" & forumID.ToString()
    End If

    tempNode = tempNode.ParentNode
    If Not (0 = ForumGroupID) And Not (tempNode Is Nothing) Then
        tempNode.Url = tempNode.Url & "?ForumGroupID=" & forumGroupID.ToString()
    End If

    Return currentNode

End Function



...


' These methods are just placeholders for the example.
' One option is to use the HttpContext or e.Context object
' to obtain the ID.
Private Function GetMostRecentForumGroupID() As Integer
    Return 24
End Function

Private Function GetMostRecentForumID(ByVal forumGroupId As Integer) As Integer
    Return 128
End Function

Private Function GetMostRecentPostID(ByVal forumId As Integer) As Integer
    Return 317424
End Function
private void Page_Load(object sender, EventArgs e)
{
    // The ExpandForumPaths method is called to handle
    // the SiteMapResolve event.
    SiteMap.SiteMapResolve +=
      new SiteMapResolveEventHandler(this.ExpandForumPaths);
}

private SiteMapNode ExpandForumPaths(Object sender, SiteMapResolveEventArgs e)
{
    // The current node represents a Post page in a bulletin board forum.
    // Clone the current node and all of its relevant parents. This
    // returns a site map node that a developer can then
    // walk, modifying each node.Url property in turn.
    // Since the cloned nodes are separate from the underlying
    // site navigation structure, the fixups that are made do not
    // effect the overall site navigation structure.
    SiteMapNode currentNode = SiteMap.CurrentNode.Clone(true);
    SiteMapNode tempNode = currentNode;

    // Obtain the recent IDs.
    int forumGroupID = GetMostRecentForumGroupID();
    int forumID = GetMostRecentForumID(forumGroupID);
    int postID = GetMostRecentPostID(forumID);

    // The current node, and its parents, can be modified to include
    // dynamic querystring information relevant to the currently
    // executing request.
    if (0 != postID)
    {
        tempNode.Url = tempNode.Url + "?PostID=" + postID.ToString();
    }

    if ((null != (tempNode = tempNode.ParentNode)) &&
        (0 != forumID))
    {
        tempNode.Url = tempNode.Url + "?ForumID=" + forumID.ToString();
    }

    if ((null != (tempNode = tempNode.ParentNode)) &&
        (0 != forumGroupID))
    {
        tempNode.Url = tempNode.Url + "?ForumGroupID=" + forumGroupID.ToString();
    }

    return currentNode;
}


...


// These methods are just placeholders for the example.
// One option is to use the HttpContext or e.Context object
// to obtain the ID.
private int GetMostRecentForumGroupID()
{
    return 24;
}

private int GetMostRecentForumID(int forumGroupId)
{
    return 128;
}

private int GetMostRecentPostID(int forumId)
{
    return 317424;
}

Security

An important aspect of working with query strings and form data is validating the data that is passed. ASP.NET provides a set of validation controls that provide an easy-to-use but powerful way to check for errors and, if necessary, display messages to the user. Validation controls were not used in the preceding example in order to focus on the task of changing site-map nodes. For information about how to add validation to your code, see Validation ASP.NET Controls.

See Also

Concepts

Events in ASP.NET Master and Content Pages

Securing ASP.NET Site Navigation

Securing Data Access

Reference

SiteMapResolve

SiteMapNode

SiteMapPath

Context

Other Resources

Validation ASP.NET Controls

Handling and Raising Events

ASP.NET Application Security in Hosted Environments