Using an External Link in a Custom Site Map Provider

Joe Audette

Updated

In my previous post, I showed the code for a custom SiteMapProvider I’m building for mojoPortal. Later I ran into another issue.

I wanted to be able to link to an external site from my menu but it was raising an error in the AddNode method:

[HttpException (0x80004005): 'http://www.google.com' is not a valid virtual path.]
System.Web.Util.UrlPath.CheckValidVirtualPath(String path) +745179
System.Web.Util.UrlPath.Combine(String appPath, String basepath, String relative) +155
System.Web.StaticSiteMapProvider.AddNode(SiteMapNode node, SiteMapNode parentNode) +302
…

I tried just taking off the http:// but that didn’t work either. When you add the node it wants a virtual path that exists in the site.

The solution required a few things
I had to turn off securityTrimming in the Web.config.

External Link in a Custom Site Map

Having this enabled would automatically show or hide menu items based on your security roles, turning it off means the SiteMapProvider will serve up all nodes to all users regardless of their role membership so you have to filter the menu yourself.

This is not a real problem because you can hide things in the MenuItemDataBound event of the Menu Control by doing your own role-checking. My method for this looks like this:

void pageMenu_MenuItemDataBound(object sender, MenuEventArgs e)
{
System.Web.UI.WebControls.Menu menu = (System.Web.UI.WebControls.Menu)sender;
SiteMapNode mapNode = (SiteMapNode)e.Item.DataItem;
if (SiteUser.IsInRoles(mapNode.Roles))
{
System.Web.UI.WebControls.MenuItem itemToRemove = menu.FindItem(mapNode.Title);

if (itemToRemove != null)
{
    menu.Items.Remove(menu.FindItem(mapNode.Title));
}

}

Now the other thing you need to do to avoid this error is to set the Url for the node to an internal link before you add it to the nodes collection, then set it back to the external link after it is already in the collection.

Apparently, when you add it to the nodes collection, the base class makes sure it points to a real virtual path in the site, but after it makes this check you are free to change it.

My code for this looks like this:

SiteMapNode node = CreateSiteMapNode(page, i);
AddNode(node, GetParentNode(page));
if ((page.UseUrl) && (page.Url.StartsWith("http")))
{
node.Url = page.Url;
}

CreateSiteMapNode has logic to set it to an internal link if it sees that the URL starts with http. Internal links have a relative path.

So after the node is in the nodes collection you can get away with changing the Url to an external link.

Hope this helps anyone having the same problem.