SharePoint 2010: Custom Provider – cross site collection navigation

Recently I get a request to have a consistent top navigation across site collection, in which I need to retrieve the navigation items from a site collection to be used in different site collections (in the same web application).  As it is known that SharePoint is using Provider framework to build its navigation, and for Publishing site it is using PortalSiteMapProvider that provides additional caching mechanism. The issue with the provider is that it cannot live cross site collection boundary.

In this post, I would like to share how to build the custom navigation provider utilizing the PortalSiteMapProvider from different site collection (in this post I call it source site collection), I provide the source code at the end of this post. Let’s get started:

  1. We need to create a custom HttpHandler (ashx) that hosts the source site collection’s PortalSiteMapProvider. The ashx is deployed to layouts directory. This excellent post shows how to build the custom HttpHandler, in fact the source code is based on the post with some modification. Basically the handler will build JSON object tree based on the navigation structure of the source site collection.
  2. Then create a custom SiteMap provider that calls the ashx created at step 1. The provider will call the handler using WebRequest with the user credential, and deserialize the received JSON into a object structure used by the provider.
  3. Replace the implementation of OOB site map data source with our own. In v4.master the navigation data sources (SiteMapDataSource) are enclosed in delegate controls, below are the delegate controls in the master page
    Navigation Location Delegate Ctl ID
    Top navigation TopNavigationDataSource
    Quick launch QuickLaunchDataSource

    With this knowledge, we can replace the OOB data source with my own implementation. Below is the definition in my elements.xml to replace the top navigation data source with our own implementation.

    <Control Sequence="30"
       Id="TopNavigationDataSource"
       ControlClass="System.Web.UI.WebControls.SiteMapDataSource"
       ControlAssembly="System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
       <Property Name="ID">topSiteMap</Property>
       <Property Name="SiteMapProvider">SymprgNavigationProvider</Property>
       <Property Name="EnableViewState">false</Property>
       <Property Name="ShowStartingNode">true</Property>
     </Control>
    

    You can see that I replaced the top navigation data source implementation to use my custom provider: SymprgNavigationProvider. And also make sure the control sequence number is less than the OOB publishing control defined in Navigation feature (14 hive/TEMPLATE/FEATURES/Navigation /NavigationSiteSettings.xml- the OOB Publishing sequence number is 50).  Put this elements.xml to a web scoped feature. By activating the feature we can replace the top navigation.

  4. The last step is adding the custom navigation handler to the web.config inside siteMap > providers element as shown below. The custom provider (Symprg.NavProvider.NavigationProvider) has two custom attributes:
    • sourceUrl: the source site collection url that provides the navigation items
    • depth: the depth of the navigation, by default is 1 if not specified.
      <siteMap defaultProvider="CurrentNavigation" enabled="true">
        <providers>
           ---------------------
           <add name="SymprgNavigationProvider" type="Symprg.NavProvider.NavigationProvider, Symprg.NavProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d7699941b48f79bc" sourceUrl="http://aSiteCollection" depth="2"/>
        </providers>
      </siteMap>
    

You can find the source code here.

16 thoughts on “SharePoint 2010: Custom Provider – cross site collection navigation

  1. Great post! I only encountered one problem. The credential used to do the request to the HttpHandler are the credentials of the IUSR account instead of the credentials of the currently logged in user. This leads to the behavior that the handler only returns the root site but not its children. How can I pass the current user’s credentials to the request to the handler?

    [Reply]

    Daniel Laksana Reply:

    What is the authentication of your web application. I have tested this on Windows integrated security.
    If your authentication is claim based, I think you still need Kerberos to pass the credential across.

    [Reply]

  2. Great post! I get a request similarly yours with the different that the site collections are in different web applications. Do you know which changes I must do in your script?

    [Reply]

    Daniel Laksana Reply:

    You need to do these:

    1. In the NavigationHandler.ashx.cs. line 55 & 78. change to Url = siteUrl + node.Url(or rootNode.Url)
      where siteUrl is
      string requestUrl = HttpContext.Current.Request.Url.ToString();
      string siteUrl = requestUrl.Substring(0,requestUrl.IndexOf(requestUrl);
    2. In lin 84 of NavigationHandler.ashx.cs changes to serializableSiteMapNode.ChildNodes = GetSerializableSiteMap(node.ChildNodes, level + 1, siteUrl+ node.Url);
    3. In PortalNavProvider.cs, FindSiteMapNode(string rawUrl) method add this one below before the if block
      if (!rawUrl.Contains(“http”)) rawUrl = NavigationSource + rawUrl;

    I haven’t really tested it but I think it would do the job.

    [Reply]

  3. Hi Everyone,

    Could someone please elaborate on how to implement the solution. So far I have deployed the wsp and turned on the feature at website scope. But then what ? Are we supposed to change the master page site provider ?

    If someone could shed some light on how to implement the solution that would fantastic.

    [Reply]

    Daniel Laksana Reply:

    Do the 4th step. Editing web.config.

    [Reply]

  4. I have the same tab highlighted in all site collections, instead of them highlighting depending on where I am currently. Is there a way to resolve this?

    If not is there a way to remove the highlight from that particular tab too? So that the users don’t feel they are in that site collection when actually they are somewhere else.

    [Reply]

  5. Let me explain my problem, I have 10 Country tabs in the source site collection, each Country tab has a drop down of states (2 level navigation)
    I have total of 10 site collections representing each country and the states are their sub-sites. I deploy this feature and all the site collections get the same navigation.
    Previously when I clicked in one of the drop down links in other site collection the navigation would highlight the source site collection tab. I fixed this by providing the full URL of the sub-sites in the source site collection navigation settings.
    But now I creating City sub-sites under the State sub-sites, the City sub-sites use the same navigation as their parents. I display the City sub-sites in their parent State page using Table of Contents Web Part. When I click to reach the City sub-sites I am receiving the same problem as before of the source site collection getting highlighted, instead of the site collection where this city belongs to.

    Can anyone help or provide suggestions on how to resolve this.

    [Reply]

  6. Does any one get this error?
    System.Web.HttpException: The SiteMapProvider ‘SymprgNavigationProvider’ cannot be found.
    at System.Web.UI.WebControls.SiteMapDataSource.get_Provider()
    at System.Web.UI.WebControls.SiteMapDataSource.GetHierarchicalView(String viewPath)
    at System.Web.UI.WebControls.HierarchicalDataBoundControl.GetData(String viewPath)
    at System.Web.UI.WebControls.Menu.DataBindItem(MenuItem item)
    at System.Web.UI.WebControls.Menu.PerformDataBinding()
    at System.Web.UI.WebControls.HierarchicalDataBoundControl.PerformSelect()
    at System.Web.UI.WebControls.BaseDataBoundControl.EnsureDataBound()
    at System.Web.UI.WebControls.Menu.EnsureDataBound()
    at Microsoft.SharePoint.WebControls.AspMenu.OnPreRender(EventArgs e)
    at System.Web.UI.Control.PreRenderRecursiveInternal()
    at System.Web.UI.Control.PreRenderRecursiveInternal()
    at System.Web.UI.Control.PreRenderRecursiveInternal()
    at System.Web.UI.Control.PreRenderRecursiveInternal()
    at System.Web.UI.Control.PreRenderRecursiveInternal()
    at System.Web.UI.Control.PreRenderRecursiveInternal()
    at System.Web.UI.Control.PreRenderRecursiveInternal()
    at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)

    [Reply]

    Daniel Laksana Reply:

    You need to add the SiteMapProvider ‘SymprgNavigationProvider’ to the web.config.

    [Reply]

  7. I have a web applications with variations enabled and this only works in the root site collection, does anyone know why?

    regards

    [Reply]

  8. Great Post thanks!

    I was able to implement this without problems and everything is functioning well. However we recently discovered a snag. When we crawl our site to update our search index, any site collection with this feature turned on doesn’t get crawled! Deactivate the feature, and everything crawls fine. I have been wracking my brain looking for the issue, and my best guess is the code that replaces the standard site map provider with the custom one.

    Has anyone else has this issue and/or a work around?

    [Reply]

  9. Sounds good. I havent tried it.

    What do you suggest for SharePoint foundation as it does not have Publishing.

    I am thinking a list or xml to provide the navigation.

    Also search is very important to me. It would be great if can reply to Andrew’s comments as well.

    [Reply]

  10. Found one problem with this code, Publishing navigation allow Heading with no url or 2 node with the same url without problem.
    Using a Dictionary to store node by url, make this kind of scenario impossible.
    Did you have an idea to bypass this kind of issue ?

    [Reply]

Leave a Reply

Your email address will not be published. Required fields are marked *


− six = 3

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">