Developers very often customize MOSS 2007 navigation menus just so it bears a fancy look!  Well, not in all cases.  Recently, I worked with a customer who had their MCMS 2002 sites moved to MOSS 2007.  MCMS 2002, unlike MOSS 2007 does not have a framework as solid as in MOSS.  Though some might not like it, some call it a boon as this allows the developer infinite number of possibilities to play around with the site look and feel.

This customer of mine also had a similar look and feel changes in MCMS.  Specifically, the navigation part.  Pull-outs, dynamic back ground and fore ground coloring, anchored non-MOSS site links - whatever you can think of, he had it implemented.  Technically, he used JavaScript and CSS files to achieve this.  While he was able to get the other migrated parts working the way he wanted, he faced issues with navigation.  From a code perspective, he used a navigation data source, iterated through them and applied his JavaScript and CSS files "on-the-fly" to his navigation.  Unfortunately, this logic wouldn't work on MOSS 2007 sites that follows either collaboration or publishing site definitions.

Below is the code he used:

SPSite site = new SPSite("http://<sharePoint server>/");
SPWeb web = site.OpenWeb();
SPNavigation nav = web.Navigation;
SPNavigationNodeCollection nodeColl = nav.QuickLaunch;
string S= "";
foreach (SPNavigationNode node in nodeColl)
{
SPNavigationNodeCollection childnodecol = node.Children;
if (childnodecol.Count > 0)
{
foreach (SPNavigationNode childnode in childnodecol)
{
S = S + "Node title : " + node.Title + ", Node url: " + node.Url + "\n"
}
}
S = S + "Node title : " + node.Title + ", Node url: " + node.Url + "\n"
}
MessageBox.Show(S);

The above code would fail when run on a site using collaboration or publishing site definition.  This is probably due to the inherent difference between the mechanism used in rendering navigation structure for collaboration/publishing site definitions and other site definitions.  While for a normal team site, the navigation structure is persisted in the database, for a collaboration/publishing sites it is not the case.  Sites using collaboration/publishing site definitions use CachedObject interface instead of SPWeb and so using SPNavigationNodeCollection/SPNavigationNode wouldn't work.

There is a work-around, however: goto Site Actions - Site Settings - Modify All Site Settings - choose Navigation under Look and Feel.  In the Navigation Editing and Sorting section, select a node, click Move Up, click Move Down and hit Ok.  The action copied over the navigation structure to the content database and after this the above code will return the navigation structure.

As obvious it might sound, the customer didn't like the work-around very much as he was automating this process and was looking for a way to access these navigation node as in the above code.

Fortunately, the PortalSiteMapProvider came to the rescue.  Below is a sample on how to use this class to enumerate through the navigation nodes in a site using collaboration or publishing site definitions:

using System;
using System.Runtime.InteropServices;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Serialization;
using System.Configuration;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.Publishing.Navigation;
namespace NavProvider
{
[Guid("9c6590e3-94bb-4038-b9ae-eb3febe12505")]
public class SiteMapper : System.Web.UI.WebControls.WebParts.WebPart
{
public SiteMapper()
{
}
protected override void Render(HtmlTextWriter writer)
{
try
{
PortalSiteMapProvider sitemapprovider = (PortalSiteMapProvider)SiteMap.Providers["CurrentNavSiteMapProvider"];
SiteMapNode rootNode = sitemapprovider.CurrentNode;
writer.WriteLine(rootNode.Title + "<br>");
if (rootNode.ChildNodes.Count > 0)
{
foreach (SiteMapNode childNode in rootNode.ChildNodes)
{
writer.WriteLine(" - " + childNode.Title + "<br>");
}
}
}
catch (Exception e)
{
writer.WriteLine("Error: " + e.Message +
System.Environment.NewLine +
"Stack trace: " + e.StackTrace);
}
}
protected override void CreateChildControls()
{
base.CreateChildControls();
}
}
}

There's one caveat though!  While code using SPNavigationNode can run from any type of application (web-based or windows-based), the one that uses PortalSiteMapProvider can run only under a valid web context (in this case a valid SharePoint context).

For this customer, it was okay to use a web part and make use of his own JavaScript and CSS files for customizing the look and feel.  But I thought there might be several others out there who might wonder why SPNavigationNode API wouldn't work on a site using collaboration or publishing portal, so wanted to share this information.

Related Read:

ECM - Branding