I just spent a bunch of time working around a SharePoint bug related to manipulating the order that navigation items appear in a MOSS Publishing site on the TopNavigationBar. Here I will spell out what you need to get to get various pieces of this to work- but first, let's define what we are trying to accomplish.
Say you have a site that has been created in some way. It doesn't really matter if it was declaritively deployed from a Site Definition, restored from a backup, generated by a SharePoint Deployment Wizard CMP, or built up manually from code. The thing is once it's done being created you need to modify from code various aspects of the top navigation bar including manually setting the order of the items. Again, it doesn't matter where this code is running from, whether it be a PowerShell script, a command line application, or a suicide webpart placed on the site. Ok, all that being said, *MY* specific requirements were to:
- Change Top (global) Nav to Include Subwebs, Hide Pages, and support manual (non-automatic) ordering
- Hide one of the subwebs
- Reorder the other subwebs to a custom (ie. arbitrary) order
- All changes must be done fully in code with no "manual human steps" required as it was part of an automated deployment script.
The good news is that to do the first two bullets, things pretty much work like they are supposed to, although finding exactly how to do some of that took a bit of work, so I'll reproduce them here. Unfortunately for the third bullet there's a bug that gets in the way and so weird workarounds are required.
First of all, my code segments below will assume you have referenced Microsoft.SharePoint.dll and Microsoft.SharePoint.Publishing.dll and have the following usings at the top of your file:
1: using Microsoft.SharePoint;
2: using Microsoft.SharePoint.Publishing;
3: using Microsoft.SharePoint.Navigation;
4: using System.Collections.Generic;
To set the different properties of the root web's navigation settings simply do the following:
1: using (var site = new SPSite(webUrl))
2: {
3: var rootPubweb = PublishingWeb.GetPublishingWeb(site.RootWeb);
4: rootPubweb.NavigationOrderingMethod = OrderingMethod.Manual;
5: rootPubweb.IncludeSubSitesInNavigation = true;
6: rootPubweb.IncludePagesInNavigation = false;
7: rootPubweb.Update();
8: }
Now, if you want to hide a particular subweb from the top navigation, you actually need to set the property of the subweb, not a property in the root web or it's navigation collections. So, you would add the following (inside the SPSite using statement):
1: var subwebs = rootPubweb.GetPublishingWebs();
2: subwebs["subwebToHide"].IncludeInGlobalNavigation = false;
3: subwebs["subwebToHide"].Update();
4: rootPubweb.Update();
So far so good. Now when it gets to actually specifying the order of the nodes, the documentation that you will find will direct you to get an SPNavigationNodeCollection from rootWebInstance.Navigation.TopNavigationBar. If you iterate over the SPNavigationNode 's in this collection you can set their order by using the .MoveToFirst, .MoveToLast, and .Move methods. Unfortunately on a newly build publishing site this collection will not contain any of your subwebs, only the top level pages (even though we specified to NOT include pages and to include subwebs). In scouring the internet I found that others had indeed come across this bug as well. What's odd is that if you use the SharePoint GUI and go to the root web's navigation settings (/_layouts/AreaNavigationSettings.aspx), select any subweb in the "Global Navigation" tree and click one of the Move buttons, then some magic code runs behind the scenes and the Navigation.TopNavigationBar collection will now be properly populated (and you won't need the following work around any more for that particular site). However, as my requirement was to not require any manual steps I had to go on to develop the workaround below.
So I embarked on an effort to reproduce the "magic code" that initialized the navigation collection properly. There was much trial and error involved while making incremental changes and then interogating object properties with PowerShell, etc... In the end the successful strategy was to create the navigation nodes manually (in code). The trickiest part though was getting them to be set as "internal" and not "external" links (this is a read-only property on each NavigationNode)-- it turned out the node's Url property had to be set just right for it to work. Here is my workaround code:
1: using (var site = new SPSite(webUrl))
2: {
3: var rootPubweb = PublishingWeb.GetPublishingWeb(site.RootWeb);
4: var subwebs = rootPubweb.GetPublishingWebs();
5:
6: //Build a temporary hash of the node titles (so we can use them instead of index numbers)
7: SPNavigationNodeCollection nodes = site.RootWeb.Navigation.TopNavigationBar;
8: Dictionary<string, SPNavigationNode> nodeHash = new Dictionary<string, SPNavigationNode>();
9: for (int x = 0; x < nodes.Count; x++)
10: {
11: nodeHash.Add(nodes[x].Title, nodes[x]);
12: }
13:
14: //This block fixes the evil SharePoint defect ------------------------
15: foreach (PublishingWeb subweb in subwebs)
16: {
17: if (!nodeHash.ContainsKey(subweb.Title))
18: {
19: string relUrl = subweb.ParentPublishingWeb.Uri.MakeRelativeUri(subweb.Uri).ToString();
20: SPNavigationNode nn =
21: Microsoft.SharePoint.Publishing.Navigation.SPNavigationSiteMapNode.CreateSPNavigationNode(
22: subweb.Title, relUrl, NodeTypes.Area, site.RootWeb.Navigation.TopNavigationBar);
23: nn.Update();
24: }
25: }
26: site.RootWeb.Update();
27: //---------------------------------------------------------------------
28:
29: //Rebuild the hash table since now we should have the correct nodes!
30: nodeHash = new Dictionary<string, SPNavigationNode>();
31: for (int x = 0; x < nodes.Count; x++)
32: {
33: nodeHash.Add(nodes[x].Title, nodes[x]);
34: }
35:
36: string[] subwebTitlesInOrder = {"FirstNavItem", "SecondNavItem", "ThirdNavItem", "FourthNavItem"};
37:
38: //Order the nodes in the proper order
39: SPNavigationNode currentNode;
40: SPNavigationNode previousNode=null;
41:
42: for (int i = 0; i < subwebTitlesInOrder.Length; i++)
43: {
44: currentNode = nodeHash[subwebTitlesInOrder[i]];
45: if (i == 0)
46: {
47: currentNode.MoveToFirst(nodes);
48: }
49: else
50: {
51: currentNode.Move(nodes, previousNode);
52: }
53: previousNode = currentNode;
54: }
55: site.RootWeb.Update();
56: }
And that's it!