July 6, 2010 15 Comments
SharePoint uses the provider model for supplying data to its navigation controls. The v4.master, for instance, uses an AspMenu control for the top navigation whose DataSourceId is set declaratively to an instance of a SiteMapDataSource. The data source control is wrapped in a delegate control so it can be easily swapped out using a feature.
The data source control has a SiteMapProvider property which can be set to any of the named sitemap providers declared in the web.config file.
This arrangement works well when you stay within a site collection. However, if you need to show navigation nodes from another site collection you will need another approach. The problem is that the SiteMapProvider is limited to returning only nodes in the current site collection. If you have created, for example, a My Site Host site collection, and want to display the root site collection’s top navigation at the top of the page instead of the default My Site Host navigation, you need a way to read the root site collection’s navigation nodes.
The solution provided below is for a publishing site and uses the PortalSiteMapProvider. The PortalSiteMapProvider has a method called GlobalNavSiteMapProvider which can be used to get the top navigation nodes as a SiteMapNodeCollection. We can interate over these nodes in a custom control and build up the navigation for our site in whatever way we want.
But, to return a SiteMapNodeCollection from another site collection we need a reference to the PortalSiteMapProvider in the context of the other site collection. One way to do this is to use an HTTP handler that can be called from the Layouts folder (e.g., _layouts/handlers/navigation.ashx) and then add the appropriate site collection’s URL. If we look at the following three site collections, their URL’s all point to the same HTTP handler:
If we call the first URL from either the Search Center or My Site Host site collections, the PortalSiteMapProvider, if it is referenced in the handler, will return navigation nodes from the root site collection. Note that this method will only work within a single web application. If we attempt to reach across applications this would amount to cross-site scripting which is prevented for security reasons.
The custom handler can then be called asynchronously from client code and a serialized set of navigation nodes returned to the browser and formatted using client script.
To implement this solution, add an HTTP handler to your project. The following assumes you have a mapped Layouts folder in a Visual Studio 2010 SharePoint project. Right-click the Layouts folder and Add an ASP.NET Handler from the Web item list.
This will create a class that inherits from IHttpHandler which requires you to implement a single method, ProcessRequest, and a single property, IsReusable.
The IsReusable property allows subsequent requests to reuse the same instance of the handler. ProcessRequest acts as our entry point and, to ensure our method that retrieves navigation nodes has sufficient privileges, we call it with RunWithElevatedPrivileges.
The GetGlobalNav method then gets a reference to the PortalSiteMapProvider which then gets a SiteMapNodeCollection based on the root node. This collection is first passed into a method that converts it into a collection that can be easily serialized and then finally we call SerializeWrite to output JSON to the client.
Since the generic List<t> can be easily serialized if we use a custom struct, all we need to do in GetSerializableSiteMap is build up a List using the specified SiteMapNodeCollection.
The custom handler can be called in a user control using the appropriate site collection URL and the response data formatted using jQuery. We have added a user control to the top of the master page in our My Site Host site collection that calls our handler using a relative reference to the root site collection ("/"). So while we display the navigation in the My Site Host site collection, the nodes will be pulled from the root (main) site collection using the URL "/_layouts/handlers/navigation.ashx". The user control loads an empty unordered list element whose list items are built up asynchronously based on the response from our handler.
This is a simple approach to simply reading and displaying navigation nodes from one site collection in another. We have not tried to merge navigation nodes using this method but I don’t see why one could not.
In addition, we have only used our handler to return data to the client. If you need to return it to server-side code, you can simply create a WebRequest object on the server that calls the handler. The data should then be serialized as XML and not JSON so you can use the .NET XML object model to handle the response.