Navigation

Wednesday 27 January 2010

Fixed: SPWeb.Navigation.QuickLaunch == null

This one plagued me for quite some time.. you create a new site, and some pages. The navigation menus are all there but when you look in code the SPNavigationNodeCollections are null.

This is the same for both the standard WSS navigation collection (SPWeb.Navigation.QuickLaunch) and the publishing site collection (PublishingWeb.CurrentNavigationNodes).

However ... you navigate the the "Modify Navigation" screen and make any change, click Ok and magically all the nodes appear in code!

Why does this happen?
the main thing is the role of the "SiteMap" providers in SharePoint. These are what really drive your navigation rendering, presenting an XML map of the "nodes" structure of your pages and using controls like the ASP.Net menu to render them.

The problem is, these don't always match up with the data in the content database around navigation settings. I think this follows the similar grounds of "ghosting" in that (for performance reasons) the SPNavigationNodeCollection doesn't get filled up with data until you "customise" something.

This is normally achieved by going to the "Modify Navigation Settings" page and clicking the "ok" button, at which point SharePoint conveniently goes and populates the nodes collection, and it becomes available to you in code.

What can I do about it?
Well, thankfully there is a fix for this.

You can manually create the nodes yourself, but you DO have to add some special node properties so that SharePoint recognises them as being "linked" to the existing pages and sites, otherwise you end up with duplicates all over the place!

The trick is that any node you add for pages you add a custom Property of NodeType set to the value of Page. For sub-sites you set this property to Area.

This way, SharePoint sees your custom nodes as being "Page" and "Site" nodes instead of new custom links!

Simple eh???

The code is listed below:
// use a Publishing Web object so we can access pages


PublishingWeb pWeb = PublishingWeb.GetPublishingWeb(web);

// use this to store the urls that exist in the current nav

List currentUrls = new List();

foreach (SPNavigationNode node in web.Navigation.QuickLaunch)

{

// collect all of the existing navigation nodes

// so that we don't add them twice!

currentUrls.Add(node.Url.ToLower());

}

foreach(PublishingPage page in pWeb.GetPublishingPages())

{

// check to make sure we don't add the page twice

if (!currentUrls.Contains(page.Uri.AbsolutePath.ToLower())

&& page.IncludeInCurrentNavigation)

{

// create the new node

SPNavigationNode newNode = new SPNavigationNode(page.Title, page.Url);

newNode = web.Navigation.QuickLaunch.AddAsFirst(newNode);

// IMPORTANT

// SET THE NodeType to "Page"

newNode.Properties["NodeType"] = "Page";

// save changes

newNode.Update();

}

}

foreach(SPWeb tempWeb in web.Webs)

{

// make sure we don't add the sub-site twice

if (!currentUrls.Contains(tempWeb.ServerRelativeUrl.ToLower()))

{

// create the new node

SPNavigationNode newNode = new SPNavigationNode(tempWeb.Title, tempWeb.ServerRelativeUrl);

newNode = web.Navigation.QuickLaunch.AddAsLast(newNode);

// IMPORTANT

// SET THE NodeType to "Area"

// (this is a throwback to SPS 2003 where it used

// "Portal Area" instead of sub sites)

newNode.Properties["NodeType"] = "Area";

newNode.Update();

}

}

// save changes to the Publishing Web

pWeb.Update();