+1-888-365-2779
Try Now
More in this section
Categories
Bloggers
Blogs RSS feed

Extending Sitefinity SiteMap and navigation. Simple MVC navigation

by Nikola Zagorchev

The navigation is one of the most important parts of a site, which aims at providing topnotch user experience. It is important what pages will be shown to the user and to which he can navigate to. That is why, in this blog post will be described how the Sitefinity navigation could be customized in order to show different nodes depending on the user role, on the pages role permissions or to present all nodes. This could be done by extending the SitefinitySiteMap, which the navigation uses to get the nodes to be shown.

Extending the SitefinitySiteMap. Registering the custom one

We should simply create a class and inherit from the SitefinitySiteMap. Then register our site map in the web.config of the application, in the siteMap section:

<add name="CustomSitefinitySiteMap" type="SitefinityWebApp.CustomSitefinitySiteMap, SitefinityWebApp" rootNode="F669D9A7-009D-4D83-DDAA-000000000002" />

The rootNode is the application root node id - SiteInitializer.CurrentFrontendRootNodeId or the root_Id of all pages on first level in the sf_page_node table. You can also get it by browsing the pages section and take a look the request executed: /Sitefinity/Services/Pages/PagesService.svc/?root=f669d9a7-009d-4d83-ddaa-000000000002&...

Showing all nodes

By default, the nodes will be filtered based on the user roles and permissions and nodes that they do not have access to, will not be displayed in the navigation. We can easily skip this, and show all nodes. When the user navigates to a page that they cannot view, they will be either prompted to login or will be shown a message that they do not possess the rights to view the page content. The method that needs to be overridden is IsAccessibleToUser, which is called for all nodes and returns a boolean indicator. To show all nodes just return true:

public override bool IsAccessibleToUser(HttpContext context, SiteMapNode node)
      {     
          return true;
      }

Set the default navigation to use our site map provider

This could be done through the navigation widget's advanced settings. Set the SiteMapProviderName to your custom SitefinitySiteMap provider, for instance:

Filtering nodes based on role permission

We can filter the nodes in the provider based on whether or not they are protected. We can get a collection of those nodes which are protected by the role permission: 

public List<SiteMapNode> ProtectedNodes { get; set; }
        private Role role;
 
        public Role Role
        {
            get
            {
                if (role == null)
                {
                    role = RoleManager.GetManager("AppRoles").GetRoles()
                        .Where(a => a.Name == "BackendUsers").SingleOrDefault();
                }
 
                return role;
            }
        }
         
        public override bool IsAccessibleToUser(HttpContext context, SiteMapNode node)
        {
            var isProtected = node.Roles.Cast<Guid>().Any(p => Role.Id == p);
 
            if (isProtected)
            {
                var pageSiteNode = node as PageSiteNode;
                if (pageSiteNode != null)
                {
                    if (pageSiteNode.DeniedRoles != null && pageSiteNode.DeniedRoles.Count > 0)
                    {
                        isProtected = pageSiteNode.DeniedRoles.Any(p => Role.Id == p);
                    }
                }
 
                if (isProtected)
                {
                    if (ProtectedNodes == null)
                    {
                        ProtectedNodes = new List<SiteMapNode>();
                    }
 
                    ProtectedNodes.Add(node);
                }
            }
 
            return true;
        }

Using MVC widget to show the navigation nodes

Using a MVC widget we will display the navigation, getting the nodes from our custom SitefinitySiteMap provider and style the protected nodes in the navigation. We will use recursion in order to both convert the nodes to a model and show the hierarchy in the view. Getting the nodes and building the navigation models hierarchy:

public List<PageNodeModel> nav = new List<PageNodeModel>();
        public List<SiteMapNode> protectedNodes = new List<SiteMapNode>();
        /// <summary>
        /// This is the default Action.
        /// </summary>
        public ActionResult Index()
        {
            SiteMapProvider mapPro = SitefinitySiteMap.GetSiteMapProvider("CustomSitefinitySiteMap");
 
            var rootNode = mapPro.FindSiteMapNodeFromKey(SiteInitializer.CurrentFrontendRootNodeId.ToString());
            var nodes = mapPro.GetChildNodes(rootNode);
 
            protectedNodes = (mapPro as CustomSitefinitySiteMap).ProtectedNodes;
             
            var model = new ProtectedNavigationModel();
            BuildNavigation(nodes);
            model.Nav = nav;
 
            return View("Default", model);
        }
 
        public void BuildNavigation(SiteMapNodeCollection nodes)
        {
            var rootNodes = new List<SiteMapNode>();
            foreach (SiteMapNode node in nodes)
            {
                if (node.ParentNode.Key.ToLowerInvariant() == SiteInitializer.CurrentFrontendRootNodeId.ToString().ToLowerInvariant())
                {
                    var pageSiteNode = node as PageSiteNode;
                    if (pageSiteNode != null && pageSiteNode.ShowInNavigation && pageSiteNode.Visible == true)
                    {
                        PageNodeModel model = PageNodeModel.ToModel(node);
                        model.IsProtected = protectedNodes.Contains(node);
 
                        nav.Add(model);
 
                        rootNodes.Add(node);
                    }             
                }
            }
 
            if (nav.Count > 0)
            {
                for (int i = 0; i < nav.Count; i++)
                {
                    AddChildren(nav[i], rootNodes[i]);
                }
            }
        }
 
        private void AddChildren(PageNodeModel parent, SiteMapNode parentNode)
        {
            foreach (SiteMapNode item in parentNode.ChildNodes)
            {
                var pageSiteNode = item as PageSiteNode;
                if (pageSiteNode != null && pageSiteNode.ShowInNavigation && pageSiteNode.Visible == true)
                {
                    PageNodeModel itemModel = PageNodeModel.ToModel(item);
                    itemModel.IsProtected = protectedNodes.Contains(item);
                    parent.ChildNodes.Add(itemModel);
 
                    AddChildren(itemModel, item);
                }
            }
        }

In the view we define and use a helper:

@helper NavigationView(List<
SitefinityWebApp.Mvc.Models.PageNodeModel> nodes)
{
    foreach (var item in nodes)
    {
    <li>
        @if (item.ChildNodes.Count > 0)
        {
            if (item.IsProtected)
            {
            <span class="protected"><a href="@Url.Content(item.Url)">@item.Title</a></span>
            }
            else
            {
            <span><a href="@Url.Content(item.Url)">@item.Title</a></span>
            }
            
            <ul>
                @NavigationView(item.ChildNodes)
            </ul>
        }
        else
        {
            if (item.IsProtected)
            {
            <span class="protected"><a href="@Url.Content(item.Url)">@item.Title</a></span>
            }
            else
            {
            <span><a href="@Url.Content(item.Url)">@item.Title</a></span>
            }
        }
    </li>
    }
}

The result we build and style with kendo:

 

The blog post files could be download from here: SitefinitySiteMap and navigation.

Video of the sample MVC navigation and the protected pages:

Unable to display content. Adobe Flash is required.

Leave a comment