Unfortunately, this is not supported by default. But you can implement the SiteMap.SiteMapResolve
event in your Global.asax
to catch such extended urls, and call SiteMapProvider.FindSiteMapNode
with the correct url:
private void Application_Start(object sender, EventArgs e)
{
SiteMap.SiteMapResolve += ResolveCustomNodes;
}
private SiteMapNode ResolveCustomNodes(object sender, SiteMapResolveEventArgs e)
{
// catch ~/Image.aspx and ~/Headline.aspx
if (e.Context.Request.AppRelativeCurrentExecutionFilePath.Equals(
"~/Image.aspx", StringComparison.OrdinalIgnoreCase)
|| e.Context.Request.AppRelativeCurrentExecutionFilePath.Equals(
"~/Headline.aspx", StringComparison.OrdinalIgnoreCase))
{
string location = context.Request.QueryString["location"];
if (location != null) // ignore everything except location=
return e.Provider.FindSiteMapNode(
e.Context.Request.AppRelativeCurrentExecutionFilePath
"?location=" + HttpUtility.UrlEncode(location));
}
return null; // use default implementation;
}
No need for custom SiteMapProvider
s, this works with any provider.
Now, if you want to be more dynamic, you can do several things, for example (there are may ways):
Flag all <siteMapNode>
tags with partial querystring matching with a special attribute, and load this list by iterating the entire sitemap. The problem with that approach is that this can be very inefficient for some sitemap providers (the file-based provider is an example of a provider that is a good match for such an approach). What you'd do then, is to say something like
<siteMapNode url="~/Image.aspx?location=Our Products"
queryStringField="location"
title="Our Products" description="Our Products" />
In code you could find such nodes recursively by starting at the root node, and remembering all nodes with a queryStringField
attribute:
private IEnumerable<SiteMapNode> FindNodesWithQueryString(SiteMapNode node)
{
if (node["queryStringField"] != null)
yield return node;
foreach (SiteMapNode childNode in node.ChildNodes)
{
foreach (SiteMapNode matchingNode in FindNodesWithQueryString(childNode))
{
yield return matchingNode;
}
}
}
With this list in hand, and some hand waving, you should be able to do the same trick. Note that you should probably need to cache this list, because the SiteMapResolve
event can be called more often than you'd expect. Especially for database-type SiteMapProvider
s.
private SiteMapNode ResolveCustomNodes(object sender, SiteMapResolveEventArgs e)
{
string path = e.Context.Request.AppRelativeCurrentExecutionFilePath;
foreach (var candidate in from node in FindNodesWithQueryString(
SiteMap.RootNode)
select new {
Url = node.Url,
UrlNoQuery = node.Url.Split('?')[0],
QueryStringField = x.Node["queryStringField"],
Node = node
} into x
where path.Equals(x.UrlNoQuery,
StringComparison.OrdinalIgnoreCase)
select x)
{
string paramValue = context.Request.QueryString[
candidate.QueryStringField];
if (paramValue != null)
{
string url = candidate.UrlNoQuery + "?" + candidate.QueryStringField
+ "=" + HttpUtility.UrlEncode(paramValue);
if (url.Equals(candidate.Url, StringComparison.OrdinalIgnoreCase))
return candidate.Node;
}
}
return null;
}