-
Notifications
You must be signed in to change notification settings - Fork 1
Advanced Node Visibility with ISiteMapNodeVisibilityProvider
In some situations, nodes should be visible in some HTML helpers but not others. For example, you may want a node to be visible in the SiteMapPath HTML Helper but not in the rest of the HTML Helpers or the sitemaps XML endpoint. This can be accomplished using concrete implementations of ISiteMapNodeVisibilityProvider
that can be specified globally for every node in the SiteMap or individually on a specific SiteMap node.
Note: Some alternatives to visibility providers are:
- Security Trimming
- Editing the HTML Helper Templates in the
\Views\Shared\DisplayTemplates\
folder- Creating custom named HTML Helper Templates in the
\Views\Shared\DisplayTemplates\
folder, and specifying thetemplateName
in the HTML helper, for example@Html.MvcSiteMap().Menu(templateName: "MyCustomTemplate")
- Creating a custom HTML Helper
Type | Usage |
---|---|
FilteredSiteMapNodeVisibilityProvider | Allows you to use magic string conventions to make nodes visible/invisible based on the HTML helper and/or whether the current node is in selection path |
TrimEmptyGroupingNodesVisibilityProvider | Makes a parent node invisible automatically when all of its children are invisible |
CompositeSiteMapNodeVisibilityProvider | Allows usage of multiple ISiteMapNodeVisibilityProvider instances per node |
It is also possible to implement your own
ISiteMapNodeVisibilityProvider
if you have complex visibility logic.
Modify (or add) the MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider
in the web.config file.
<appSettings>
<add key="MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider" value="MvcSiteMapProvider.FilteredSiteMapNodeVisibilityProvider, MvcSiteMapProvider"/>
</appSettings>
Set the defaultProviderName
constructor argument of SiteMapNodeVisibilityProviderStrategy
in your MvcSiteMapProvider
module.
Note: The name of the module varies depending on the DI container and can be changed after installing the NuGet package, but each module is located under the
\DI\<containerName>\Modules\
folder in your project by default.
This is how that would look with Ninject:
this.Kernel.Bind<ISiteMapNodeVisibilityProviderStrategy>().To<SiteMapNodeVisibilityProviderStrategy>()
.WithConstructorArgument("defaultProviderName", "MvcSiteMapProvider.FilteredSiteMapNodeVisibilityProvider, MvcSiteMapProvider");
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" />
[MvcSiteMapNode(Title = "My View", ParentKey = "ParentController", Key = "MyView", VisibilityProvider = "Namespace.MyCustomVisibilityProvider2, AssemblyName", Attributes = @"{ ""visibility"": ""Condition2"" }")]
You can use several different visibility providers simultaneously to implement individual rules without violating the Single Responsibility Principle. To do so, you can use the CompositeSiteMapNodeVisibilityProvider
.
Since using visibility providers with internal DI requires a default constructor, you need to subclass the CompositeSiteMapNodeVisibilityProvider
in order to use it with the internal DI container, and provide your configuration to the constructor of CompositeSiteMapNodeVisibilityProvider
.
using MvcSiteMapProvider;
using MvcSiteMapProvider.Reflection;
public class MyCompositeVisibilityProvider : CompositeSiteMapNodeVisibilityProvider
{
public MyCompositeVisibilityProvider()
: base(
typeof(MyCompositeVisibilityProvider).ShortAssemblyQualifiedName(),
// Note that the visibility providers are executed in
// the order specified here, but execution stops when
// the first visibility provider returns false.
new FilteredSiteMapNodeVisibilityProvider(),
new TrimEmptyGroupingNodesVisibilityProvider(),
new CustomVisibilityProvider()
)
{ }
}
Note: For internal DI the first parameter must be the name of the type of your custom class. The reason for this is that the name is also used to instantiate the class using
Activator.CreateInstance
. TheShortAssemblyQualifiedName
is an extension method that returns the name of the type including namespace and assembly, but excluding the version, culture, and public key token. You must match this string exactly including casing and the space after the comma when configuring the type globally or in individual nodes.
The above class would need to be referenced as:
<Fully Qualified Namespace>.MyCompositeVisibilityProvider, <Assembly Name>
For example:
<add key="MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider" value="Acme.MvcSiteMapExtensions.MyCompositeVisibilityProvider, Acme"/>
For external DI, you need to inject each CompositeSiteMapNodeVisibilityProvider
configuration into the SiteMapNodeVisibilityProviderStrategy
class constructor. This should be done inside of the MvcSiteMapProvider DI module. Here is an example using StructureMap:
// Visibility Providers
// Explicitly set the visibility providers, using CompositeSiteMapNodeVisibilityProvider to combine the AclModuleVisibilityProvider
// with all other ISiteMapNodeVisibilityProvider implementations.
this.For<ISiteMapNodeVisibilityProviderStrategy>().Use<SiteMapNodeVisibilityProviderStrategy>()
.EnumerableOf<ISiteMapNodeVisibilityProvider>().Contains(x =>
{
x.Type<CompositeSiteMapNodeVisibilityProvider>()
.Ctor<string>("instanceName").Is("filteredAndTrimmedAndCustom")
.EnumerableOf<ISiteMapNodeVisibilityProvider>().Contains(y =>
{
// Note that the visibility providers are executed in
// the order specified here, but execution stops when
// the first visibility provider returns false.
y.Type<FilteredSiteMapNodeVisibilityProvider>();
y.Type<TrimEmptyGroupingNodesVisibilityProvider>();
y.Type<CustomVisibilityProvider>();
});
// TODO: Add additional combined visibility logic if using custom providers, as shown.
//x.Type<CompositeSiteMapNodeVisibilityProvider>()
// .Ctor<string>("instanceName").Is("aclAndOtherCustom")
// .EnumerableOf<ISiteMapNodeVisibilityProvider>().Contains(y =>
// {
// y.Type<AclModuleVisibilityProvider>();
// y.Type<MyOtherCustomVisibilityProvider>();
// });
})
.Ctor<string>("defaultProviderName").Is("filteredAndTrimmedAndCustom");
Note: When using external DI, you don't need to use .NET type names to specify the visibility provider instance to use.
<mvcSiteMapNode title="Custom Combined Visibility" visibility="Condition2" visibilityProvider="aclAndOtherCustom" />
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 MvcSiteMapProvider DI module file, which is passed into the constructor of the SiteMapBuilderSet
class.
bool visibilityAffectsDescendants = false;
Register the FilteredSiteMapNodeVisibilityProvider
either globally or on a specific node.
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 key names are case sensitive, so you should use
visibility
rather thanVisibility
. Multiple attributes can be provided by using a comma as a delimiter (Attributes = @"{ ""visibility"": ""SiteMapPathHelper,!*"", ""myCustomSetting"": ""foo"" }"
).
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: An implicit * (meaning the node is visible in any control) is automatically 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 |
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.
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,!*" />
In order to create a custom ISiteMapNodeVisibilityProvider
, subclass the SiteMapNodeVisibilityProviderBase
abstract class. This class saves you some work by implementing the type comparison logic for you.
Note: It is also possible to implement
ISiteMapNodeVisibilityProvider
directly.
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;
}
}
}
First, register the .NET type name either globally or locally.
Also, in this case we are using the visibility
attribute (a custom attribute). So we need to set it on the nodes that the visibility provider affects. Here's an example in XML:
<mvcSiteMapNode title="Administration" area="Admin" visibility="Condition1" />
<mvcSiteMapNode title="Settings" area="Admin" visibility="Condition2" />
Note: The default behavior of
MvcSiteMapProvider
will automatically make all descendant nodes invisible when a node is made invisible. To change the behavior to allow visible nodes of invisible ancestor nodes, you should change the VisibilityAffectsDescendants Setting.
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"
}
}
Custom attributes can be used on SiteMap nodes to pass information to your custom visibility providers on a per node basis. See Creating a Custom ISiteMapNodeVisibilityProvider
for an example.
- [Wiki Home] (.)
- [Release Notes] (https://github.com/maartenba/MvcSiteMapProvider/releases)
Want to contribute? See our [Contributing to MvcSiteMapProvider] (https://github.com/maartenba/MvcSiteMapProvider/blob/master/CONTRIBUTING.md) guide.
- [Upgrading from v3 to v4] (Upgrading-from-v3-to-v4)
- [Routing Basics] (Routing-Basics)
- Configuring MvcSiteMapProvider
- Defining Sitemap Nodes in XML
- Defining Sitemap Nodes using .NET Attributes
- Defining Sitemap Nodes using IDynamicNodeProvider
- HtmlHelper Extensions
- Controlling URL Behavior
- Using Action Filter Attributes
- Sitemaps XML Protocol Endpoint for Search Engines
- Using Custom Attributes on a Node
- Specifying Node Order
- Advanced Node Visibility
- Multiple Navigation Paths to a Single Page
- [Multiple Sitemaps in One Application] (Multiple-Sitemaps-in-One-Application)
- [Security Trimming] (Security-Trimming)
[Version 3.x Documentation] (Version-3.x-Documentation)
Other places around the web have some documentation that is helpful for getting started and finding answers that are not found here.
- [MvcSiteMapProvider 4.0 - A Test Drive] (http://www.shiningtreasures.com/post/2013/08/07/MvcSiteMapProvider-40-a-test-drive)
- [MvcSiteMapProvider 4.0 - SEO Features Tutorial] (http://www.shiningtreasures.com/post/2013/08/10/mvcsitemapprovider-4-seo-features)
- [How to Make MvcSiteMapProvider Remember a User’s Position] (http://www.shiningtreasures.com/post/2013/09/02/how-to-make-mvcsitemapprovider-remember-a-user-position)
- [MvcSiteMapProvider 4.0 - Cache Configuration] (http://www.shiningtreasures.com/post/2013/08/11/mvcsitemapprovider-4-cache-configuration)
- [MvcSiteMapProvider 4.0 - Extending the Cache] (http://www.shiningtreasures.com/post/2013/08/13/mvcsitemapprovider-4-extending-the-cache)
- [MvcSiteMapProvider 4.0 - Unit Testing with the SiteMaps Static Methods] (http://www.shiningtreasures.com/post/2013/08/14/mvcsitemapprovider-4-unit-testing-with-the-sitemaps-static-methods)
- [Debugging an MvcSiteMapProvider Configuration] (http://www.shiningtreasures.com/post/2013/08/21/debugging-an-mvcsitemapprovider-configuration)
- [Converting from C# to Vb MvcSiteMapProvider] (http://www.developerfusion.com/thread/112710/converting-from-c-to-vb-mvcsitemapprovider/)
- [NightOwl888's MvcSiteMapProvider Demos] (https://github.com/NightOwl888?tab=repositories) - Filter for "MvcSiteMapProvider" to see the most relevant.
- [MvcSiteMapProvider Tutorial and Examples] (http://edspencer.me.uk/2011/02/10/mvc-sitemap-provider-tutorial/)
- [MvcSiteMapProvider Tutorial 2 - Breadcrumbs] (http://edspencer.me.uk/2011/09/20/mvc-sitemap-provider-tutorial-2-breadcrumbs/)
- [Getting Started with MvcSiteMapProvider] (http://blog.danstuken.com/2011/04/29/getting-started-with-mvcsitemapprovider/)
- [Inside the MvcSiteMapProvider - Part 1] (http://xharze.blogspot.com/2012/04/inside-mvcsitemapprovider-part-1.html)
- [Inside the MvcSiteMapProvider - Part 2: Dynamic node providers] (http://xharze.blogspot.com/2012/04/inside-mvcsitemapprovider-part-2.html)
- [Inside the MvcSiteMapProvider - Part 3: The ISiteMapVisibilityProvider] (http://xharze.blogspot.com/2012/04/inside-mvcsitemapprovider-part-3.html)
- [Inside the MvcSiteMapProvider - Part 4: The IAclModule] (http://xharze.blogspot.com/2012/04/inside-mvcsitemapprovider-part-4.html)
- [Inside the MvcSiteMapProvider - Part 5: The ISiteMapNodeUrlResolver] (http://xharze.blogspot.com/2012/04/inside-mvcsitemapprovider-part-5.html)
- [Styling MvcSiteMapProvider with CSS] (http://tutsblog.net/styling-mvc-sitemap-provider-with-css/)
- [Using MvcSiteMapProvider with Twitter Bootstrap] (http://codingit.wordpress.com/2013/05/03/using-mvcsitemapprovider-with-twitter-bootstrap/)
- [ASP.NET MVC Menu using Site Map Provider & Bootstrap Navbar] (http://joeylicc.wordpress.com/2013/06/04/asp-net-mvc-menu-using-site-map-provider-bootstrap-navbar/)
- [StackOverflow MvcSiteMapProvider] (http://stackoverflow.com/questions/tagged/mvcsitemapprovider)
- [StackOverflow MvcSiteMap] (http://stackoverflow.com/questions/tagged/mvcsitemap)
- [StackOverflow ASP.NET MVC SiteMap] (http://stackoverflow.com/questions/tagged/asp.net-mvc-sitemap)
- [CodePlex Discussion Forum (no longer maintained)] (http://mvcsitemap.codeplex.com/discussions/topics/general?size=2147483647)
- [Telerik Forum] (http://www.telerik.com/search.aspx?insection=False&start=0&client=telerik_developer_tools&q=MvcSiteMapProvider&sid=1)
- [maartenba's blog] (http://blog.maartenballiauw.be/search.aspx?q=mvcsitemapprovider)
- [NightOwl888's blog] (http://www.shiningtreasures.com/category/MvcSiteMapProvider)