Using an External Link in a Custom Site Map Provider 

Friday, February 03, 2006 5:58:24 PM
Rate this Content 0 Votes

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

<siteMap enabled="true" defaultProvider="mojoSiteMapProvider">
    <providers>
        <add name="mojoSiteMapProvider"
          type="mojoPortal.Web.mojoSiteMapProvider"
          securityTrimmingEnabled="false"
           />
    </providers>
</siteMap>
    
Having this enabled would automatically show or hide menu items based on your security roles, turning it off means the SiteMapProvider will server 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 is making 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.

Share This Using Popular Bookmarking Services
Copyright 2003-2010 Joe Audette

re: Using an External Link in a Custom Site Map Provider

Sunday, February 05, 2006 11:20:31 AM Keith J. Farmer [MSFT]

As I recall, many of the changes for ASP.NET 2.0 were driven by portal needs (DotNetNuke in particular).  This problem would be worth submitting to the feedback site for inclusion in either an SP or in Orcas.

 


re: Using an External Link in a Custom Site Map Provider

Wednesday, February 08, 2006 2:33:39 PM Joe
Hi Kieth,

Judging by some googling others have reported this too so it may already be known. Where is the feedback site you mention? I'd be happy to post it.

There are actually 2 sitemapprovider issues I've seen folks complaining about in my googling, one is the external link issue and the other is the limitation of requiring unque urls. I haven't tried yet to see if my workaround solves the second one but I'm hoping it does. In populating a menu its perfectly reasonable to have more than one menu item point to the same page in the site

Anyway thanks for the attention!

Cheers,

Joe

re: Using an External Link in a Custom Site Map Provider

Tuesday, March 07, 2006 2:27:56 AM unknown
Hi Joe,

Let´s say I have an issue similar regarding securityTrimmingEnabled. I have this set to true in order to have nodes filtered automatically according to the user role. The issue I´m having is when I log out and re log as another users with different roles, my sitemap is not re-creating the nodes properly. (if I kill the aspnet_wp.exe it´s ok) so I believe it happens because the sitemap is an in memory object (performance reasons).  I´m trying to figure out how to call the overidden method Clear() from inside the BuildSiteMap method when the user log out the system. any ideas?

Or I could do what you did in the MenuItemDataBound ... which one would be better? Probably your approach, because if I call BuildSiteMap again, it will make a call to the server.

re: Using an External Link in a Custom Site Map Provider

Tuesday, March 07, 2006 3:13:20 AM Ken
Hi All,

I forgot to put my name in the last post: Tuesday March 7 2006 07:27 AM, so i´m the unkown guy. Besides this, It just came in to my mind that maybe the sitemapprovider should bring the nodes already filtered from the database, eg: in the default way it selects all the nodes and then it filters them, instead I could pass the logged user role to the getSiteMap procedure ...

re: Using an External Link in a Custom Site Map Provider

Tuesday, March 07, 2006 3:31:03 AM Joe Audette
Hi Ken,

I think that using the menu databound is the best approach because the SiteMap does get cached so reconstructing it differently for different users or roles is not ideal. With SecurityTrimming enabled it will filter by role but still all nodes are cached. However if you want to have links to external sites enabling SecurityTrimming breaks it.

Using the menu databound event, the SteMap is still cached with all nodes, its just filtered on presentation by the menu and I can still link to external pages.

Hope it helps,

Joe

re: Using an External Link in a Custom Site Map Provider

Tuesday, March 07, 2006 5:42:41 AM unknown
Thanx Joe ... that´s clarifying ... I´ll try to plug a similar solution as your MenuItemDataBound into my pages

re: Using an External Link in a Custom Site Map Provider

Tuesday, March 07, 2006 6:31:50 AM Ken

Hi Joe ... your solution works perfectly ... one question: I was using the public override bool IsAccessibleToUser( HttpContext context, SiteMapNode node )In my SiteMapProvider class, but since this sitemap is in memory, this method will only work once, eg in the first login. To make this to work everytime, i´ll have to call the clear() method and thus have multiples database calls, which is no good. Which in the same time is a bit strange, in case I decide to inherit the SiteMapProvider class instead of the StaticSiteMapProvider i´ll probably be doing something like that ... again very odd. Time for me to study a bit more those providers!

 


re: Using an External Link in a Custom Site Map Provider

Thursday, March 09, 2006 11:58:55 PM Sharpey
Cheers Joe,

Thanks for posting your solution - I was having exactly the same problem myself!

Chris

re: Using an External Link in a Custom Site Map Provider

Wednesday, March 22, 2006 2:33:41 PM Trevor Feldman
Thanks Joe - this blog posting solved my problem!! -- Trevor Feldman

re: Using an External Link in a Custom Site Map Provider

Tuesday, March 28, 2006 8:34:48 AM Jonathan Minond

Hi Joe,

I was wondering...

with the following, if you remove a node, will it also remove all it's children by default? or does it iterate through the item databind for all the children as well? I am wondering if there is a danger of when a parent is removed, if the children are not.

Menu menu = (Menu)sender;MenuItem itemToRemove = menu.FindItem(sn.Title);if (itemToRemove != null)

 

 

menu.Items.Remove(menu.FindItem(sn.Title));

Email me back if you can to jminond at gmail dot com


re: Using an External Link in a Custom Site Map Provider

Tuesday, March 28, 2006 8:41:37 AM Joe Audette
Hi Jonathan,

Yes, since the children are attached to the parent they are also not displayed. I have seen no risk of showing chlid menu nodes when the parent is removed.
It is certainly working correctly for this site and for mojoportal.com which are both running mojoportal software and using the custom sitemapprovider and this technique to filter the menu.

Cheers,

Joe

re: Using an External Link in a Custom Site Map Provider

Tuesday, March 28, 2006 9:08:22 AM Jonathan Minond
Shouldnt it be

MenuItem

FindItem takes a ValuePath to a menu item, not the title. 

While the Title of the SiteMapNode may happen to match the Value of the Menu item sometimes, this isn't garenteed for all cases. 

itemToRemove = menu.FindItem(sn.Url); ?

re: Using an External Link in a Custom Site Map Provider

Tuesday, March 28, 2006 9:17:14 AM Jonathan Minond

Also the whole findmenu piece i think should be siwtched with

e.Item.Parent.ChildItems.Remove(e.Item);


re: Using an External Link in a Custom Site Map Provider

Tuesday, March 28, 2006 9:20:14 AM Joe Audette
valuePath != Url

Its actually the path from the root node to the current node

http://msdn2.microsoft.com/en-US/library/system.web.ui.webcontrols.treenode.valuepath(VS.80).aspx
However in my app it finds the node correctly by title. I think I tried it by url and that did not work.

You may be right as far as that being a better way to remove a node, though my current code works fine.

Cheers,

Joe

re: Using an External Link in a Custom Site Map Provider

Tuesday, March 28, 2006 9:39:11 AM Joe Audette
Hmm,

A quick test shows that using FindItem on the title only works for top level menu items where the title happens to also equal the valuePath so I am going to have to re-visit this code to make it work correctly for hiding sub menus when the parent page is not hidden.

Thanks for the heads up.

Cheers,

Joe

re: Using an External Link in a Custom Site Map Provider

Tuesday, March 28, 2006 1:33:36 PM Joe Audette
OK,

heres a method that works at any menu depth to remove an item:

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 (e.Item.Depth == 0)
    {
        menu.Items.Remove(e.Item);
    }
    else
    {
       System.Web.UI.WebControls.MenuItem parent = e.Item.Parent;
       if (parent != null)
       {
           parent.ChildItems.Remove(e.Item);
       }
    }

    }

}

Comments are closed on this post.
Donate Money to support the mojoPortal Project. View Joe Audette's profile on LinkedIn View Joe Audette's profile on The Guild of Accessible Web Designers site