MvcSiteMapProvider icon indicating copy to clipboard operation
MvcSiteMapProvider copied to clipboard

Is it possible to setup non accessible nodes and readonly/non-clickable?

Open TheTFo opened this issue 8 years ago • 2 comments

Hello,

I have a case where I'm creating a breadcrumb where I want the full path to be shown for all users. The only difference I want is that for a user that doesn't have access to particular node, it's is simply not clickable or readonly.

Currently, I'm using the AuthorizeAttributeAclModule and the default visibility provider, and this doesn't support that. I don't believe overriding either of these would allow it either. Any ideas?

Thanks!

TheTFo avatar Mar 28 '16 17:03 TheTFo

You could do that with a global MVC filter. MvcSiteMapProvider already has a concept of "non-clickable", so all you need to do is check accessibility and set clickable to false when the user doesn't have access. When you write to the Clickable property, the value is request cached, so other users will not see the change.

using MvcSiteMapProvider;
using MvcSiteMapProvider.Security;
using System;
using System.Web.Mvc;

namespace MvcSiteMap_VisibleNonAccessible
{
    public class AccessibleResultFilter : IResultFilter
    {
        private readonly IAclModule aclModule;

        public AccessibleResultFilter(IAclModule aclModule)
        {
            if (aclModule == null)
                throw new ArgumentNullException("aclModule");

            this.aclModule = aclModule;
            this.SiteMapCacheKey = "default";
        }

        public string SiteMapCacheKey { get; set; }

        public void OnResultExecuted(ResultExecutedContext filterContext)
        {
            // Do nothing
        }

        public void OnResultExecuting(ResultExecutingContext filterContext)
        {
            ISiteMap siteMap = SiteMaps.GetSiteMap(this.SiteMapCacheKey);

            this.RecurseNodes(siteMap.RootNode, this.SetAccessibility);
        }

        private void SetAccessibility(ISiteMapNode node)
        {
            bool isAccessible = this.aclModule.IsAccessibleToUser(node.SiteMap, node);

            if (!isAccessible)
            {
                node.Clickable = false;
            }
        }

        private void RecurseNodes(ISiteMapNode node, Action<ISiteMapNode> action)
        {
            action(node);

            foreach (var childNode in node.ChildNodes)
            {
                RecurseNodes(childNode, action);
            }
        }
    }
}

And then in your FilterConfig.cs file:

using System.Web;
using System.Web.Mvc;
using MvcSiteMapProvider;
using MvcSiteMapProvider.Security;
using MvcSiteMapProvider.Web.Mvc;
using MvcSiteMapProvider.Web.Mvc.Filters;

namespace MvcSiteMap_VisibleNonAccessible
{
    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            // Add accessible filter for MvcSiteMapProvider
            filters.Add(new AccessibleResultFilter(
                new AuthorizeAttributeAclModule(
                    new MvcContextFactory(), 
                    new ControllerDescriptorFactory(), 
                    new ControllerBuilderAdapter(ControllerBuilder.Current), 
                    new GlobalFilterProvider())));

            filters.Add(new HandleErrorAttribute());
        }
    }
}

You could use external DI to make creating the filter easier, but there is no reason you have to.

You could also change the display templates in /Views/Shared/DisplayTemplates/ to change the default behavior of how non-clickable nodes behave.

NightOwl888 avatar Mar 28 '16 18:03 NightOwl888

Thanks for the comment. This appears to work well, except that on the first run through the application, things evaluate to clickable, and after refreshing, they aren't anymore.

I don't have time to dive further, so I need to shelve this, but thanks for your quick response!

TheTFo avatar Mar 29 '16 14:03 TheTFo