The views expressed on this weblog are mine alone and do not necessarily reflect the views of my employer, Avanade.

« Maping from SPList Field Types to .Net Types | Main | Using a Custom List to Store Configuration Data »

Customizing the Quick Launch menu with SPNavigationNode, SPNavigationNodeCollection and Audiences

I am starting to feel like I have a love hate relationship with MOSS 2007. On the one hand, the WSS API's are powerful enough to really save time when building solutions. But other times, those same API's are just so bizarre, it makes me wonder if it's even worth it.

When you install the Visual Studio 2005 extensions for Windows SharePoint Services 3.0, you get a nice tool called SharePoint Solution Generator. Using this tool, you can point in an existing SharePoint Site and it will generate a Visual Studio 2005 solution that can be compiled into a SharePoint Solution file, or wsp file that can then be used to install the solution as a site definition on any server.

Unfortunetely, the solution that the tool generate omits a lot of the manual customizations that you made to your site in the first place. I plan on writing a blog post just about this in the next couple of days. Today I want to focus on just one of those ommissions, the Quick Launch Bar.

By default, the Quick Launch bar will lose any of the manual custmization you made before exporting your site. Lucky for us, we can use the WSS object model to add custom code to re-customize the Quick Launch naviagtion section when the site is activated. We do this by adding code to the OnActivated method of the Site Definition class that was generated for you in the Site Provisioning Handler folder of the solution that was created for you.

What I discovered when I attempted to do this, is that the API for managing the quick launch navigation doesn't behave they way you would expected it. The first thing I did was try to add code to delete the existing navigation so I can add my custom navigtion without worrying what was there by default.

SharePoint refers to each navigation item as SPNavigationNode. The SPWeb object has a Navigation property which itself has a QuickLaunch property that exposes a SPNavigationNodeCollection which is a collection of those nodes which can be nested just like a tree node.

So to get the collection I wrote the folowing code: (web is the current SPWeb object for the site I was working on)

SPNavigationNodeCollection nodes = web.Navigation.QuickLaunch;

Then I figured to write  a foreach loop as follows:

foreach (SPNavigationNode node in nodes)

But when I tried to run this code, I would get a weird exception saying that it couldn't be completed. Stepping through the code, I saw that for a list of five list items, none of which had any nested items, it would always crap out after the third one. I tried to switch to a normal for loop, but had the same problem. I also noticed while debugging that the nodes "Count" property never changed, even after the first few nodes were deleted. I also noticed that if I called the Delete method of each node in the collection directly, from 0 to 5, after I deleted the "[2]" node, the  references to "[3]" and "[4]" were no longer valid. But if I deleted them backward, it worked fine. So the code I ultimatley ended up using was as follows:

            for(int i = nodes.Count - 1; i >= 0; i--)

This enabled me to delete the items, but I had to figure out how to create them too. There were two types of links I wanted to add: headings and normal links. I also wanted to control who can see some of the links using audiences. The normal SPNavigation node has no such properties to allow control of this. Searching google for an answer, I found another object, the SPNavigationSiteMapNode. This object had all the properties I needed, but it offerred no way of casting down to a SPNavigationNode that I could find. There was also no comprable collection object. I couldn't find a way to add an instance of that class back to the QuickLaunch SPNavgationNodeCollection object.

But the SPNavigationSiteMapNode has a static method called CreateSPNavigationNode that takes four parameters: the link name, the url, the link type, and the node collection it should be added to. The link type allowed you to specify whether it is a regular link or a heading. Here is the code I used to add a normal link:

SPNavigationNode node = SPNavigationSiteMapNode.CreateSPNavigationNode("Link Title", web.Url + "/url.aspx", Microsoft.SharePoint.Publishing.NodeTypes.AuthoredLinkPlain, nodes);

This still does not solve the audience problem. While debugging, I notced the SPNavigatonNode object had a Properties collection. This collection contained a number of key value pairs like a HashTable. While stepping through code, I noticed that the links for which I had manually assigned an audience had a property named "Audience" that had a value of four semicolons followed by the group that I had named as the audience. So I tried out the following code:

SPNavigationNode node = SPNavigationSiteMapNode.CreateSPNavigationNode("Link Title", web.Url + "url2.aspx", Microsoft.SharePoint.Publishing.NodeTypes.Heading, nodes);
node.Properties.Add("Audience", ";;;;" + "Name of SPGroup");

And it worked like a charm.

As you can see, coding against the SharePoint object model isn't as straight forward or well documented as you might think. But I guess that makes it more interesting too. Hopefully these war stories can help alleviate some of the thrashing you might otherwise face.

Happy coding.

EmailEmail Article to Friend

Reader Comments (22)

good article on quicklaunch, its the same challenge that i am facing while trying to create CUSTOM HEADINGS AND Links under it as opposed to the Default ones created while provisioning the Site using Site Definition.

I would like to create a ADMIN section with links to my own set of list and webparts.

I couldnot get any help from the net.

January 17, 2008 | Unregistered CommenterParshuram

I would try to Add a Header and Links with Audience set to your admins group. That will make it only appear for those in that group.

January 17, 2008 | Registered CommenterDavid San Filippo
Please note that in order for the "node.Properties.Add("Audience", ";;;;" + "Name of SPGroup")" bit to work the site collection feature 'Office SharePoint Server Publishing Infrastructure' MUST be activated
January 18, 2008 | Unregistered Commentersurjit mattu
could you let me know the syntax to add links to the above node.

January 19, 2008 | Unregistered CommenterRam

i would like to access the HEADING [List], so that i could add my own custom links using feature receiver event.

How do i get access to the List Heading using the above Class.

February 1, 2008 | Unregistered CommenterRam
Thanks for your nice article. I am facing a problem with the solution generator. I created a sitecollection with some lists and subsites. through solution generator i created a visual studio solution file. then i deployed the solution file to a .wsp under sharepoint scope. Now i created a new sitecollection based on the .wsp(i.e. custom template) file. In this point i am not able to find the subsites i created under the site collection. Can anyone suggest me where i am going wrong? OR is this a limitation of the sharepoint solution generator?

Thanks in advance
June 25, 2008 | Unregistered Commentermahendran
So the SharePoint Solution Generator probably wont give you quite what you are looking for out of the box. As you realized, when you deploy it, it creates a custom template that you need to manually provision after you deploy it. There is no easy way to get the newly provisioned site to include subsites without writing code. You need to use the SharePoint object model to create the subsites you want in the site provisioning handler. (In the onActivated method)

What you really need to ask yourself is do you really need to create a template? Do your customers/clients need to be able to provision sites this way. If you are just doing this to be able to deploy to different envionments, It might make more sense to backup/restore. You'll still need to create a solution package to deploy features and other customizations, but it may be more straight forward.

Good Luck.
June 25, 2008 | Registered CommenterDavid San Filippo
Nice Post!
However it does not work for me completely. It adds the link but it does not display on the screen. Basically when I try to add the Area nodetype, it does not display, however Heading nodetype works. There's something more that I am missing. I am using the CurrentNavSiteMapProvider datasource which is part of PublishingNavigation.

Any pointers would greatly help.

July 8, 2008 | Unregistered CommenterChetanya
Please note that in order for the "node.Properties.Add("Audience", ";;;;" + "Name of SPGroup")" bit to work the site collection feature 'Office SharePoint Server Publishing Infrastructure' MUST be activated

This is wrong! You do not need to activate Office SharePoint Server Publishing Feature

Thanks for this great article

October 5, 2008 | Unregistered CommenterTimo
This articale is Great !

How can i add a webpart/Event handler to a list while making WSP

November 3, 2008 | Unregistered CommenterRoji
Really... I mean really? You should need a license to blog...
March 26, 2009 | Unregistered CommenterYour a Idiot
What's the problem?

I don't mind criticism, but at least make it constructive.
April 13, 2009 | Registered CommenterDavid San Filippo
I was wondering why you had to do that for the Quicklaunch. The Onet.xml file has the quicklaunch info in it and I used that to create my custom quicklaunch. Just curious....
April 28, 2009 | Unregistered CommenterDan
As with most .NET collections, you shouldn't attempt to modify the collection itself (ie add/delete) while in the process of iterating the collection.

The pattern typically used to remove items from a collection is to iterate the nodes and add each node to be deleted to a List<>. Then iterate that List<> and delete each item from the orginal collection.

Feels cumbersome, but that's how it's done safely. Any adds/deletes to the actual collection you're iterating could potentially affect the iteration process itself. ie, you can't be sure what the "next" / "previous" / "current" element would be if you start removing/inserting elements during iteration.

Thanks for the blog!
May 22, 2009 | Unregistered CommenterJeff Hare
Thanks for the information... I, too, tried to use foreach loop and other methods, but your analysis and debugging explanation helped tremendously...
July 31, 2009 | Unregistered CommenterClarence
Thanks for the post. Here's an other way to delete nodes from the collection.

SPNavigationNodeCollection nodes = web.Navigation.QuickLaunch;
foreach (SPNavigationNode node in nodes)
September 21, 2009 | Unregistered Commenter0baz
This only works for Publishing sites and for team sites with publishing enabled. I have tested it.

If it had worked for you for team site with out enabling publishing then let me know.
July 7, 2010 | Unregistered CommenterBharat Reddy Basani
Good morning,
I'm following your post but I'm not able to understand how to add the property "Audience" and if it's necessary to start the feature "Office SharePoint Server Standard Site features".
In my site I have a Sharepoint group named "pippo" but if I had in my code the row
normalNode14.Properties.Add("Audience", ";;;;pippo"); and I try to login to the site using a user into the group I see nothing.
It's the same if I use an administrator user.
If I stop the feature "Office SharePoint Server Standard Site features" I see all the link in the quicklaunch; so I don't know how to use the Audience property.

Thanks in advance
August 24, 2010 | Unregistered CommenterAlessandro
This is an amazing solution to a headache I've been wrestling with for a few days! Thanks for sharing the detailed steps too :)
October 5, 2011 | Unregistered CommenterAlex C
Thanks... helpd me a lot to handle the audiences automatically :)
November 7, 2011 | Unregistered Commenterhank

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
All HTML will be escaped. Hyperlinks will be created for URLs automatically.