This post will focus on how to customize SharePoint Navigation controls, such as the top menu and the quicklaunch menu, and the starting point is the MasterPage. I want to achieve the necessary menu customizations re-using SharePoint menus instead of deleting-commenting-replacing them with our custom ascx controls at MasterPage level.
SharePoint menus are not just objects to show menu items, they also apply caching and leverage security completely in synch with the SharePoint Object Model. If I replace those OOTB menu controls with my custom ones, I would also need to consider the effort of re-inventing the wheel to cache and secure their content. So, although initially it might look easier to just use our custom ascx controls, we would end up working a lot more than just re-using what’s been provided to us by Microsoft and the SharePoint team.
Delegate Controls wrapping SiteMapProviders?
When you take a second, or third, look in a MasterPage with the knowledge that gives you how SharePoint works, a world of possibilities appears in front of you.
One of the things that can give you, as a SharePoint developer, a lot of customization power is what’s called DelegateControls. I will not get into a deep dive of DelegateControls either; maybe with another post I will add more meat on the bone to this topic but, just to give you an idea of how powerful DelegateControls can be for you, you can replace objects wrapped in delegate controls with your custom ones, or even add your custom objects without replacing the existing ones. In some scenarios you don’t want to replace existing controls but to add your custom ones to the Delegate Control wrapper. For instance, there is a delegate control for html page header elements, so you could add your own html header elements without affecting the existing ones.
With this in mind, and our SharePoint MasterPage open, we can see the following two delegate controls that are related to the SharePoint menus:
1. Top Navigation Menu
The top navigation menu is rendered with the AspMenu. This control renders the content received from the DataSource. We can see in this vanilla, not customized, SharePoint Masterpage that the AspMenu is using the topSiteMap DataSource
Just following the AspMenu html mark-up we find the SiteMapDataSource with id topSiteMap. The good news about it is that it is wrapped in a DelegateControl and therefore we could replace, or override, that datasource with our custom one. We will use the DelegateControl ControlId TopNavigationDataSource later on in order to replace it.
<SharePoint:DelegateControl runat="server" ControlId="TopNavigationDataSource" Id="topNavigationDelegate">
2. QuickLaunch Menu
The same happens to the QuickLaunch Menu because it is using another AspMenu to render its content and the only difference is that it is being fed from a different SiteMapDataSource, identified as QuickLaunchSiteMap, and also wrapped within a DelegateControl.
<SharePoint:DelegateControl runat="server" ControlId="QuickLaunchDataSource">
Now that we have located what we want to replace, how could we do it? We can use Visual Studio to create our custom SiteMapProvider, publish it to the GAC, as part of our project, and some small editing in web.config will get us up and running without major issues. I would say that editing the web.config manually is a dirty solution and there are ways to avoid editing it manually. You can check it in another of my blog posts. (off-topic: I find writing blog posts an interesting and rewarding experience because you find new topics to clarify and blog about while writing something not entirely related to them)
There is an existing MSDN article showing how to create a SiteMapProvider step by step, but it is not completely accurate and following that article can create more problems than necessary. One of the wrong assumptions in it says that you need to deploy your custom SiteMapProvider to the bin folder and increase the security trust to full-trust, again manually editing the web.config. This will not reach a production environment in any serious organization; at least I would never allow it, because allowing full trust to binaries running in a site collection could indeed put in serious risk the company, server and/or data. Security should be kept to minimal trust in order to protect the server and our organization. ALWAYS. No exceptions.
Creating our custom SiteMapProvider step by step
Create an empty SharePoint project (I’ve named mine as CustomSPNavigationProvider) and make sure you have/add the following two references:
Add a new c# class to the project. I have named my class MyCustomSiteMapProvider and this is where all the logic to create the menu items will take place.
In this example I’m just hardcoding a couple of nodes but you could build your own logic to answer any specific requirement. For instance, creating as many nodes as necessary from a configuration list and reproducing the security at item level while rendering the menu so the user will see links where he/she has access to.
We have our custom PortalSiteMapProvider but if we deploy the solution as it is, SharePoint will not behave any different and we want to switch our navigation on demand. To get us there we need to replace the delegate control TopNavigationDataSource with our custom provider.
To replace a delegate control we need to add a SharePoint Empty Element. I’ve called mine ControlsToOverride because this is exactly what we want to do, override existing delegate controls.
And edit the elements.xml file as follows. The only part that might be different in your environment would be the one to select the appropriate SiteMapProvider, in this case MyCustomNavigationProvider. This name is the one we will add to the SiteMap Providers nodes in the web.config. You will see it below, in the last step.
<?xml version="1.0" encoding="utf-8"?>
ControlAssembly="System.Web, Version=188.8.131.52, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
When you add an Empty Control, a new Web scoped feature is added to your solution and the empty element will be part of that feature. This will leverage a custom Site Map Provider on demand. So when the feature is activated a custom top menu bar will be used and when it will be de-activated, the top menu bar that’s been set up in Site Settings will be used instead.
This is how the complete solution will look like in Visual Studio. As you can see, you don’t need a lot of work to create your custom provider.
The last step is to edit the web.config manually, adding the following line to the <providers> section within siteMap. As I said earlier, this step will be done manually in this blog post, but I would use a better approach to reach a production environment because our custom code needs to be repeatable and completely encapsulated in our WSP’s. (Check this blog post to do this from code)
Important! This web.config information needs to match your assembly/project/Class and PublicKeyToken. The information you see here would work for my project only. (Thanks to some feedback I've seen some people were just copy pasting this into their web.config without editing it to match their own project)
<siteMap defaultProvider="CurrentNavigation" enabled="true">
<add name="MyCustomNavigationProvider" type="CustomSPNavigationProvider.MyCustomSiteMapProvider, CustomSPNavigationProvider, Version=184.108.40.206, Culture=neutral, PublicKeyToken=4a9a625f2ea253ae" NavigationType="Global" />
You are ready now to deploy the solution and activate the feature in any web that needs your custom top navigation.
Before activating it:
After the feature is activated:
[SharePoint 2013 Update] Things will be a lot easier as you can see here...