SharePoint 2013: how get the managed navigation from CSOM and REST

SharePoint 2013 has introduced the Managed Metadata Navigation.

To get the managed navigation from client side code, an excellent introduction for JSOM is presented in Get Navigation Taxonomy term tree in SharePoint App, where we have the following points:

  1. get the taxonomy session from the current client context
  2. get the current navigation settings, to retrieve the term store id and the term set id of the terms storing the navigation for the current site
  3. load the term set
  4. load the navigation term set, i.e. the term set "casted" as navigation term set, so offering the additional navigational properties (note: the navigation term set doesn't inherit from from the standard term set)

I needed to do the same from C# code, containing CSOM calls, and here are more or less the same steps, even if we need first to convert the claim token to a Windows identity. Here is the CSOM file: SharePointNavigationProvider_CSOM.cs (7.4KB)

        private WindowsIdentity GetWindowsIdentity()
        {
            WindowsIdentity windowsIdentity = null;

            SPSecurity.RunWithElevatedPrivileges(() =>
            {
                ClaimsIdentity identity = (ClaimsIdentity)Thread.CurrentPrincipal.Identity;
                string upnFromClaim = identity.Claims.Where(c => c.ClaimType == ClaimTypes.Upn).First().Value;

                try
                {
// To get the current Windows identity, we need the Claims to Windows Token Service started on all frontends of the SharePoint farm
                    windowsIdentity = S4UClient.UpnLogon(upnFromClaim);
                    if (windowsIdentity == null)
                        throw new ApplicationException("windowsIdentity == null");
                }
                catch (System.ServiceModel.EndpointNotFoundException ex)
                {
                    throw new ApplicationException("The Claims to Windows Token Service is not running on this frontend", ex);
                }
                catch (System.ServiceModel.Security.SecurityAccessDeniedException ex)
                {
                    throw new ApplicationException("Unable to communicate with the domain controller", ex);
                }
            });

            return windowsIdentity;
        }

        private TermSet GetTermSet(ClientContext clientContext)
        {
            TaxonomySession taxonomySession = TaxonomySession.GetTaxonomySession(clientContext);
            StandardNavigationSettings navigationSettings = new WebNavigationSettings(clientContext, clientContext.Web).CurrentNavigation;
            clientContext.Load(navigationSettings,
                navSettings => navSettings.TermStoreId,
                navSettings => navSettings.TermSetId);
            clientContext.ExecuteQuery();

            TermStore termStore = taxonomySession.TermStores.GetById(navigationSettings.TermStoreId);
            TermSet termSet = termStore.GetTermSet(navigationSettings.TermSetId);
            clientContext.Load(termSet);
            return termSet;
        }

        private void GetNavigationTermSet(ClientContext clientContext, TermSet termSet,
            out NavigationTermCollection navTerms, out NavigationTermCollection allNavTerms)
        {
            NavigationTermSet navigationTermSet = NavigationTermSet.GetAsResolvedByWeb(clientContext, termSet, clientContext.Web, "GlobalNavigationTaxonomyProvider");
            clientContext.Load(navigationTermSet);

            navTerms = navigationTermSet.Terms;
            clientContext.Load(navTerms,
                terms => terms.Include(term => term.Id));

            allNavTerms = navigationTermSet.GetAllTerms();
            clientContext.Load(allNavTerms,
                terms => terms.Include(
                    term => term.FriendlyUrlSegment,
                    term => term.HoverText,
                    term => term.Id,
                    term => term.LinkType,
                    term => term.SimpleLinkUrl,
                    term => term.Terms,
                    term => term.Title));

            clientContext.ExecuteQuery();
        }

        List<NavigationNodeInfo> BuildRecursiveNodeList(string url, NavigationTermCollection allNavTerms, NavigationTermCollection currentNavTerms, int maxDepth)
        {
            List<NavigationNodeInfo> Result = new List<NavigationNodeInfo>(currentNavTerms.Count);
            foreach (NavigationTerm navTerm in currentNavTerms)
            {
                NavigationTerm currentTermInAllTerms = allNavTerms.First(term => term.Id == navTerm.Id);
                NavigationNodeInfo navNodeInfo = new NavigationNodeInfo
                {
                    Title = currentTermInAllTerms.Title.Value,
                    Url = currentTermInAllTerms.LinkType == NavigationLinkType.FriendlyUrl ?
                        SPUtility.ConcatUrls(url, currentTermInAllTerms.FriendlyUrlSegment.Value) :
                        currentTermInAllTerms.SimpleLinkUrl,
                    HoverText = currentTermInAllTerms.HoverText
                };

                if (maxDepth > 1 && currentTermInAllTerms.Terms.Any())
                    navNodeInfo.Childs = BuildRecursiveNodeList(url, allNavTerms, currentTermInAllTerms.Terms, maxDepth - 1);
                
                Result.Add(navNodeInfo);
            }
            return Result;
        }


This solution, even if technically correct, has the disadvantage that these multiple CSOM calls are very slow.
An alternative to improve the performances is to use the REST calls (still after having impersonated the calling user). Here is the REST file: SharePointNavigationProvider_REST.cs (5.3KB)

private XDocument GetWebAppNavigationRest(WindowsIdentity windowsIdentity, string url)
{
using (new SPMonitoredScope("SZH.IntraZueri.Core.Providers.Navigation.SharePointNavigationProvider.GetWebAppNavigationRest"))
{
XDocument Result;

// Impersonating the user requires Kerberos in place
using (WindowsImpersonationContext ctx = windowsIdentity.Impersonate())
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(SPUtility.ConcatUrls(url,
"_api/navigation/menustate(mapprovidername='GlobalNavigationTaxonomyProvider')")); //CurrentNavigationSwitchableProvider
request.Method = "GET";
//request.ContentType = "application/json";
request.Credentials = CredentialCache.DefaultNetworkCredentials;

using (new SPMonitoredScope("SZH.IntraZueri.Core.Providers.Navigation.SharePointNavigationProvider.GetWebAppNavigationRest.ServiceInvoke"))
//using (WebResponse webResponse = request.GetResponse())
using (Task<WebResponse> asyncResponse = webReq.GetResponseAsync())
{
asyncResponse.Wait();
using (WebResponse response = asyncResponse.Result)
using (Stream webStream = webResponse.GetResponseStream())
Result = XDocument.Load(webStream);
}

return Result;
}
}
}

private List<NavigationNodeInfo> BuildRecursiveNodeList(XElement parentNode, XNamespace ns, string url, int maxDepth)
{
List<NavigationNodeInfo> Result = new List<NavigationNodeInfo>();

foreach (XElement currElement in parentNode.Elements())
{
// Check isHidden flag. If it's hidden, don't include in result set
string isHiddenStr = currElement.Element(ns + "IsHidden").Value;
bool isHidden;
bool.TryParse(isHiddenStr, out isHidden);
if (isHidden)
continue;

string title = currElement.Element(ns + "Title").Value;
string friendlyUrlSegment = currElement.Element(ns + "FriendlyUrlSegment").Value;
string simpleUrl = currElement.Element(ns + "SimpleUrl").Value;
string linkUrl;
if (!string.IsNullOrEmpty(friendlyUrlSegment))
linkUrl = SPUtility.ConcatUrls(url, friendlyUrlSegment);
else
linkUrl = SPUtility.ConcatUrls(url, simpleUrl);
List<NavigationNodeInfo> childs;
if (maxDepth > 1)
childs = BuildRecursiveNodeList(currElement.Element(ns + "Nodes"), ns, url, maxDepth - 1);
else
childs = new List<NavigationNodeInfo>();

NavigationNodeInfo navNodeInfo = new NavigationNodeInfo
{
Title = title,
Url = linkUrl,
Childs = childs
};
Result.Add(navNodeInfo);
}
return Result;
}

Add comment

Loading