Tuesday, August 25, 2009

Adding QueryString Parameters to the SiteMapNode

There is no way to add querystring parameters to the SiteMapNode in a SiteMap control "out of the box." I'm quite surprised there is no option but luckily the SiteMap uses the provider model. After a Live search, I was able to find a solution provided by Bobby DeRosa. By creating a custom provider, this can be accomplished. The project I was on is an update of an ASP.net app written in VB.net so here is my VB.net port:

Imports System.Collections.Specialized
Imports System.Web

Namespace Configuration

Public Class ExtendedSiteMapProvider
Inherits XmlSiteMapProvider

Public Overrides Sub Initialize(ByVal name As String, ByVal attributes As NameValueCollection)
MyBase.Initialize(name, attributes)
Dim resolveHandler As New SiteMapResolveEventHandler(AddressOf SmartSiteMapProvider_SiteMapResolve)
AddHandler Me.SiteMapResolve, resolveHandler
End Sub

Function SmartSiteMapProvider_SiteMapResolve(ByVal sender As Object, ByVal e As SiteMapResolveEventArgs) As SiteMapNode
If (SiteMap.CurrentNode Is Nothing) Then Return Nothing

Dim this As New XmlSiteMapProvider
Dim temp As SiteMapNode
temp = SiteMap.CurrentNode.Clone(True)
Dim u As Uri = New Uri(e.Context.Request.Url.ToString())
Dim tempNode As SiteMapNode = temp

While Not tempNode Is Nothing
Dim qs As String = GetQueryString(tempNode, e.Context)
If Not qs Is Nothing Then
If Not tempNode Is Nothing Then
tempNode.Url += qs
End If
End If
tempNode = tempNode.ParentNode
End While

Return temp
End Function

Private Function GetQueryString(ByVal node As SiteMapNode, ByVal context As HttpContext) As String
If node("queryStringToInclude") Is Nothing Then Return Nothing

Dim values As NameValueCollection = New NameValueCollection
Dim vars() As String = node("queryStringToInclude").Split(",".ToCharArray())
Dim s As String

For Each s In vars
Dim var As String = s.Trim()
If context.Request.QueryString(var) Is Nothing Then Continue For
values.Add(var, context.Request.QueryString(var))
Next

If values.Count = 0 Then Return Nothing

Return NameValueCollectionToString(values)
End Function

Private Function NameValueCollectionToString(ByVal col As NameValueCollection) As String
Dim parts(col.Count - 1) As String
Dim keys() As String = col.AllKeys

For i As Integer = 0 To keys.Length - 1
parts(i) = keys(i) & "=" & col(keys(i))
Next

Dim url As String = "?" & String.Join("&", parts)

Return url
End Function

End Class

End Namespace

I added the following to the web.config:

<siteMap defaultProvider="ExtendedSiteMapProvider" enabled="true">
<providers>
<clear />
<add name="ExtendedSiteMapProvider" type="Configuration.ExtendedSiteMapProvider" siteMapFile="web.sitemap" securityTrimmingEnabled="true" />
</providers>
</siteMap>

If I had a page of products on products.aspx, displayed the details of the product on details.aspx, and had a separate update.aspx page, my web.sitemap might look like this:

<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode url="~/" title="Main Page">
<siteMapNode url="~/products.aspx" title="Products" >
<siteMapNode url="~/details.aspx" title="Product Details" queryStringToInclude="ProductID" >
<siteMapNode url="~/update.aspx" title="Updating a Product" />
</siteMapNode>
</siteMapNode>
</siteMapNode>
</siteMap>

So, on /update.aspx?ProductID=3, the SiteMapNode for Product Details would have a url of /details.aspx?ProductID=3.

Hopefully a similar feature will be added soon to the ASP.net SiteMap control.

<span style="font-weight:bold;">C#:</span>
Great job! I took your C# code and had to make a few small changes to get it working correctly (on VS2008 & .NET 3.5 sp1):

#region Using Directives

using System.Collections.Specialized;

using System.Web;

#endregion

namespace Configuration

{

public class ExtendedSiteMapProvider : XmlSiteMapProvider

{

public override void Initialize(string name, NameValueCollection attributes)

{

base.Initialize(name, attributes);

this.SiteMapResolve += SmartSiteMapProvider_SiteMapResolve;

}

static SiteMapNode SmartSiteMapProvider_SiteMapResolve(object sender, SiteMapResolveEventArgs e)

{

if ((SiteMap.CurrentNode == null)) return null;

SiteMapNode temp = SiteMap.CurrentNode.Clone(true);

SiteMapNode tempNode = temp;

while (tempNode != null)

{

string qs = GetQueryString(tempNode, e.Context);

if (qs != null)

{

tempNode.Url += qs;

}

tempNode = tempNode.ParentNode;

}

return temp;

}

private static string GetQueryString(SiteMapNode node, HttpContext context)

{

if (node["queryStringToInclude"] == null) return null;

NameValueCollection values = new NameValueCollection();

string[] vars = node["queryStringToInclude"].Split(",".ToCharArray());

foreach (string s in vars)

{

string var = s.Trim();

if (context.Request.QueryString[var] == null) continue;

values.Add(var, context.Request.QueryString[var]);

}

if (values.Count == 0) return null;

return NameValueCollectionToString(values);

}

private static string NameValueCollectionToString(NameValueCollection col)

{

string[] parts = new string[col.Count];

string[] keys = col.AllKeys;

for (int i = 0; i <= keys.Length - 1; i++)

{

parts[i] = keys[i] + "=" + col[keys[i]];

}

return "?" + string.Join("&", parts);

}

}

}

1 comment:

Jose said...

Thank You, Excelent