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 the following URL:
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
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
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.
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.
Return the cloned node by using the following line of code.
return currentNode;
Return currentNode
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.
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" />
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 ASP.NET Validation Controls.
See Also
Reference
Concepts
Events in ASP.NET Master and Content Pages
Securing ASP.NET Site Navigation
Securing Data Access in ASP.NET