Skip to content

Advanced Node Visibility with ISiteMapNodeVisibilityProvider

NightOwl888 edited this page Aug 15, 2014 · 8 revisions

In some situations, nodes should be visible in the breadcrumb trail but not in a complete sitemap. This can be solved using the concept of ISiteMapNodeVisibilityProvider, that can be specified globally for every node in the sitemap or granularly on a specific sitemap node.

It is possible to implement your own ISiteMapNodeVisibilityProvider, as is done in the sample application. There is also a standard FilteredSiteMapNodeVisibilityProvider, which filters sitemap node visibility based on the control that is currently rendering the sitemap node.

VisibilityAffectsDescendants Setting

You can control the setting of whether the visibility of the parent node will be inherited by its children using the VisibilityAffectsDescendants setting. This setting can be applied SiteMap wide or specified explicitly on by calling one of the overloads that accept the visibilityAffectsDescendants parameter on the Menu or SiteMap HTML helpers. Although it is not the default setting, we recommend setting this value to false for the most flexibility with visibility providers, which will allow you to make visible children of invisible parent nodes.

For internal DI you can set the property SiteMap wide using the following setting in web.config:

<appSettings>
    <add key="MvcSiteMapProvider_VisibilityAffectsDescendants" value="false"/>
</appSettings>

For external DI, you can just set the variable on at the top of the module file, which is passed into the constructor of the SiteMapBuilderSet class.

bool visibilityAffectsDescendants = false;

Using FilteredSiteMapNodeVisibilityProvider

In order to make use of FilteredSiteMapNodeVisibilityProvider, the following steps should be taken:

1 - If the FilteredSiteMapNodeVisibilityProvider should be used globally for all nodes in the sitemap, modify Web.config and set the appSettings MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider attribute to "MvcSiteMapProvider.FilteredSiteMapNodeVisibilityProvider, MvcSiteMapProvider". Here's an example when using the internal DI container:

<appSettings>
	<add key="MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider" value="MvcSiteMapProvider.FilteredSiteMapNodeVisibilityProvider, MvcSiteMapProvider"/>
</appSettings>

If using an external DI container, you need to set the defaultProviderName constructor argument of SiteMapNodeVisibilityProviderStrategy in your MvcSiteMapProvider module (the name of the module varies depending on the DI container). This is how that would look with Ninject:

this.Kernel.Bind<ISiteMapNodeVisibilityProviderStrategy>().To<SiteMapNodeVisibilityProviderStrategy>()
    .WithConstructorArgument("defaultProviderName", "MvcSiteMapProvider.FilteredSiteMapNodeVisibilityProvider, MvcSiteMapProvider");

2 - For every node, specify the visibility attribute. Here's an example in XML:

<mvcSiteMapNode title="Administration" area="Admin" clickable="false" visibility="SiteMapPathHelper,!*" />

And here is the same example using SiteMapNodeAttribute.

[MvcSiteMapNode(Title = "Administration", Clickable = false, Attributes = @"{ ""visibility"": ""SiteMapPathHelper,!*"" }")]

Note: Due to data type restrictions of .NET attributes, the Attributes must be declared as a JSON string.

The visibility attribute can contain a comma-separated list of controls in which the sitemap node should be rendered or not. These are processed left-to-right and can be inverted using an exclamation mark. Here's a break down of the above example:

Directive Meaning
SiteMapPathHelper The node is visible in the SiteMapPathHelper.
!* The node is invisible in any other control.

Note that an implicit * (The node is visible in any control) is added at the end of the list.

Here is a list of the types that can be specified in the FilteredSiteMapNodeVisibilityProvider.

Type What it Affects
CanonicalHelper The Canonical HTML Helper
MenuHelper The Menu HTML Helper
MetaRobotsHelper The Meta Robots HTML Helper
SiteMapHelper The SiteMap HTML Helper
SiteMapPathHelper The SiteMapPath HTML Helper
SiteMapTitleHelper The Title HTML Helper
XmlSiteMapResult The sitemaps XML output of the /sitemap.xml endpoint

Named HTML Helper Instances

As of v4.4.8 you can use the FilteredSiteMapNodeVisibilityProvider to specify named instances of the Menu and other HTML helpers instead of specifying the type of control. This is helpful if for example you wanted to have a main menu and a footer menu.

To name an HTML helper, call one of the overrides that has a sourceMetadata argument and pass the name.

@Html.MvcSiteMap().Menu(new { name = "MainMenu" })
@Html.MvcSiteMap().Menu(new { name = "FooterMenu" })

You can then use the name instead of the type name to specify visible or not visible, as in the previous example.

<mvcSiteMapNode title="Home" controller="Home" action="Index" visibility="MenuHelper,!*" />
<mvcSiteMapNode title="Contact" controller="Home" action="Contact" visibility="MainMenu,!*" />
<mvcSiteMapNode title="About" controller="Home" action="About" visibility="FooterMenu,!*" />

This example will make the home page visible on all menus (but invisible everywhere else), make the contact page visible only on the main menu, and make the about page visible only on the footer menu.

Visible Only if Selected

You can add the suffix IfSelected to either the HTML helper type name or the named instance name to make the node only visible if it is in the current path (that is, between the current node and the root node, inclusive).

<mvcSiteMapNode title="Contact" controller="Home" action="Contact" visibility="MainMenuIfSelected,!*" />

You can also specify this behavior for all HTML helpers and the /sitemap.xml endpoint at once using IfSelected.

<mvcSiteMapNode title="Contact" controller="Home" action="Contact" visibility="IfSelected,!*" />

Creating a Custom SiteMapNodeVisibilityProvider

In order to create a custom SiteMapNodeVisibilityProvider, to following steps should be taken:

1 - Create a new class implementing SiteMapNodeVisibilityProviderBase

using System.Collections.Generic;
using System.Web;
using MvcSiteMapProvider;

namespace MyCompany
{
    public class MyCustomVisibilityProvider : SiteMapNodeVisibilityProviderBase
    {
        public bool IsVisible(ISiteMapNode node, IDictionary<string, object> sourceMetadata)
        {
            // Is a visibility attribute specified?
            string visibility = node.Attributes["visibility"];
            if (string.IsNullOrEmpty(visibility))
            {
                return true;
            }
            visibility = visibility.Trim();
            
            //process visibility
            switch (visibility)
            {
                case "Condition1":
                    //...
                    return false;

                case "Condition2":
                    //...
                    return false;
            }

            return true;
        }
    }
}

2 - Modify Web.config and set the MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider (internal DI container only) to contain your Visibility Provider namespace and assembly like so:

<appSettings>
	<add key="MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider" value="MyCompany.MyCustomVisibilityProvider, MyWebProject"/>
</appSettings>

MyWebProject should be the name of the dll that is created by your MVC project

3 - For every node, specify the visibility attribute. Here's an example in XML:

<mvcSiteMapNode title="Administration" area="Admin" visibility="Condition1" />
<mvcSiteMapNode title="Settings" area="Admin" visibility="Condition2" />

Passing Custom Information

Note that if you need to, you can pass custom information from the HTML helper declaration to your visibility provider by using the sourceMetadata parameter of the HTML helper (or the /sitemaps.xml endpoint) as shown in the section Named HTML Helper Instances above.

@Html.MvcSiteMap().SiteMapPath(new { someKey = "Some Value" })

It will then be available through the sourceMetadata parameter of the IsVisible method of your custom visibility provider.

public class MyCustomVisibilityProvider : SiteMapNodeVisibilityProviderBase
{
	public bool IsVisible(ISiteMapNode node, IDictionary<string, object> sourceMetadata)
	{
		// Retrieve the value named someKey
		var value = sourceMetadata["someKey"];
		
		// Value of value variable will be "Some Value"
	}
}

Overriding the Default Visibility Provider

It is possible to override the default visibility provider with another one by specifying the visibility provider directly on the mvcSiteMapNode element. This means that each node can have a separate visibility provider if you have different complex logic that needs to be specified for a node or group of nodes.

<mvcSiteMapNode title="Settings" area="Admin" visibility="Condition2" visibilityProvider="Namespace.MyCustomVisibilityProvider2, AssemblyName" />

Want to contribute? See our [Contributing to MvcSiteMapProvider] (https://github.com/maartenba/MvcSiteMapProvider/blob/master/CONTRIBUTING.md) guide.



[Version 3.x Documentation] (Version-3.x-Documentation)


Unofficial Documentation and Resources

Other places around the web have some documentation that is helpful for getting started and finding answers that are not found here.

Tutorials and Demos

Version 4.x
Version 3.x

Forums and Q & A Sites

Other Blog Posts

Clone this wiki locally