Customizing the Quick Launch menu with SPNavigationNode, SPNavigationNodeCollection and Audiences
Saturday, October 6, 2007 at 08:54PM 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)
{
node.Delete();
}
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--)
{
nodes[i].Delete();
}
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");
node.Update();
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.
Reader Comments (20)
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.
Ram
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.
-Dave
could you let me know the syntax to add links to the above node.
Regards
Ram
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.
Ram
Thanks in advance
mahendran
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.
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.
Thanks!
This is wrong! You do not need to activate Office SharePoint Server Publishing Feature
Thanks for this great article
Timo
How can i add a webpart/Event handler to a list while making WSP
Roji
I don't mind criticism, but at least make it constructive.
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!
SPNavigationNodeCollection nodes = web.Navigation.QuickLaunch;
foreach (SPNavigationNode node in nodes)
{
nodes.Delete(node);
}
If it had worked for you for team site with out enabling publishing then let me know.
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
Ale