-
Notifications
You must be signed in to change notification settings - Fork 218
Configuring MvcSiteMapProvider
MvcSiteMapProvider relies on dependency injection for all configuration. Don't let that scare you away if you are not familiar with DI though, as we have created some shortcuts for the most common configurations.
Note: If you are migrating from a prior version of MvcSiteMapProvider, the
<sitemap>
section of the web.config file can be completely deleted as we are no longer using the ASP.NET sitemap provider model.
By default, if you just drop the MvcSiteMapProvider DLL into your MVC project it will use a poor man's DI container that is built in. This container is only capable of supporting a single .sitemap XML file, but will get you by if you just want to add sitemap functionality to a single-tenant web site and configure it with XML.
This DI container is bootstrapped using the code:
MvcSiteMapProvider.DI.Composer.Compose();
Note that this code is automatically executed if using .NET 4.0 or higher by the use of WebActivator, so in most cases you will not need to call it manually. Any code you put into the Application_Start()
event of Global.asax
will run before this is called.
When the DI container starts, it reads settings from the web.config file and uses them to configure some options, as shown below.
<appSettings>
<add key="MvcSiteMapProvider_UseExternalDIContainer" value="false"/>
<add key="MvcSiteMapProvider_SiteMapFileName" value="~/Mvc.sitemap"/>
<add key="MvcSiteMapProvider_ScanAssembliesForSiteMapNodes" value="false"/>
<add key="MvcSiteMapProvider_ExcludeAssembliesForScan" value=""/>
<add key="MvcSiteMapProvider_IncludeAssembliesForScan" value=""/>
<add key="MvcSiteMapProvider_AttributesToIgnore" value=""/>
<add key="MvcSiteMapProvider_CacheDuration" value="5"/>
<add key="MvcSiteMapProvider_ControllerTypeResolverAreaNamespacesToIgnore" value=""/>
<add key="MvcSiteMapProvider_DefaultSiteMapNodeVisibiltyProvider" value=""/>
<add key="MvcSiteMapProvider_VisibilityAffectsDescendants" value="true"/>
<add key="MvcSiteMapProvider_SecurityTrimmingEnabled" value="false"/>
<add key="MvcSiteMapProvider_EnableLocalization" value="true"/>
<add key="MvcSiteMapProvider_EnableSitemapsXml" value="true"/>
<add key="MvcSiteMapProvider_EnableResolvedUrlCaching" value="true"/>
<add key="MvcSiteMapProvider_EnableSiteMapFile" value="true"/>
<add key="MvcSiteMapProvider_IncludeRootNodeFromSiteMapFile" value="true"/>
<add key="MvcSiteMapProvider_EnableSiteMapFileNestedDynamicNodeRecursion" value="false"/>
<add key="MvcSiteMapProvider_UseTitleIfDescriptionNotProvided" value="true"/>
</appSettings>
Directive | Required? | Default |
---|---|---|
No | false | |
Whether to use the internal DI container (false) or use an external one (true). If true, all other appSettings values are ignored - it is assumed that all configuration will be done by the external DI container. | ||
No | ~/Mvc.sitemap | |
The sitemap XML file to use. | ||
No | false; NuGet packages enable this automatically | |
Set to 'true' to enable scanning for [MvcSiteMapNode] attribute. If not using [MvcSiteMapNode] attribute in your project, it is recommended that you set this to 'false' to reduce overhead associated with using .NET Reflection to scan for attributes. | ||
No | (empty) | |
Comma-separated list of assemblies that should be skipped when scanAssembliesForSiteMapNodes is enabled. Note that all assemblies in the ~/bin folder except those referenced in the excludeAssembliesForScan attribute are scanned for sitemap nodes. | ||
No | (empty) | |
Comma-separated list of assemblies that should be included when scanAssembliesForSiteMapNodes is enabled. Note that only assemblies referenced in the includeAssembliesForScan attribute are scanned for sitemap nodes. | ||
No | (empty) | |
Comma-separated list of attributes defined on a sitemap node that should be ignored by the MvcSiteMapProvider. | ||
No | 5 | |
Number of minutes the sitemap is cached on the server before refreshing nodes. | ||
No | (empty) | |
Pipe or semi-colon delimited list of namespaces to exclude from ControllerTypeResolver. Can be used to exclude areas so diagnostic tools that register their own areas are not considered as part of the application. | ||
No | none; defaults to true | |
Default class that will be used to determine visibility for a sitemap node. Can be overridden at the mvcSiteMapNode level. | ||
No | true | |
Determines whether the child nodes will inherit the visibility from the parent node (if true) or if they always respect the value from the visibility provider (if false). | ||
No | false | |
Use security trimming? When enabled, nodes that the user can not access will not be displayed in any sitemap control. | ||
No | true | |
Enables localization of sitemap nodes. This pertains to the Title, Description, ImageUrl, and any custom attributes. | ||
No | true | |
Registers the route "/sitemap.xml" for the XmlSiteMapController, effectively enabling it. | ||
No | true | |
Enables shared caching of the URLs so they are not resolved on every request. If enabled, this setting can be overridden in the node configuration by setting the cacheResolvedUrl attribute/property to false (default is true). Disabling URL resolution caching is required in order for per-request changes to the `RouteValues` dictionary to affect the URL (except when using PreservedRouteParameters). | ||
No | true | |
Enables parsing of the .sitemap XML file. If you set this to false, ScanAssembliesForSiteMapNodes must be set to true. You can safely delete the .sitemap file if this is set to false. | ||
No | true | |
Include the root node from the .sitemap XML file? This option allows you to skip processing the root node in XML and configure it using either MvcSiteMapNodeAttribute or DynamicNodeProvider. | ||
No | false | |
Enables the V3 behavior of calling an XML nested dynamic node N + 1 of the parent dynamic node provider. We don't recommend using this setting unless your upgraded application requires it to function. | ||
No | true | |
If true, sets the default value of the Description property to the same value as Title if no value is provided explicitly for Description. |
Note: Early beta versions of MvcSiteMapProvider 4.0.0 had the ability to register EnableLocalization and SecurityTrimmingEnabled settings inside the Mvc.sitemap file. These settings were moved to appSettings and are no longer supported in Mvc.sitemap.
The purpose of the internal DI container is to make MvcSiteMapProvider "just work" without any configuration. However, to reach the full potential of extensibility you need to use an external DI container. There are several open-source containers to choose from, and they all basically do 2 things:
- Supply an instance of an object to fulfill a dependency
- Supply a collection of object instances to fulfill a dependency
The entire configuration for DI (no matter how complex or scary it looks) is primarily just to do these 2 things.
We have provided Nuget packages to wire up a starting point configuration for [Autofac] (http://autofac.org/), [Ninject] (http://www.ninject.org/), [SimpleInjector] (http://simpleinjector.codeplex.com/), [StructureMap] (http://docs.structuremap.net/), [Unity] (http://unity.codeplex.com/), or [Castle Windsor] (http://docs.castleproject.org/Default.aspx?Page=MainPage&NS=Windsor&AspxAutoDetectCookieSupport=1). Once you have picked a DI container to use, the easiest way to get started is to install the Nuget package for the MVC version and DI container you use.
The recommended approach for DI configuration is to use code. This may seem to go against the grain for making an application flexible at first glance, as it requires a compile of the application to make changes. In the early days of DI, XML configuration was all the rage, but it turned out to be very cumbersome to maintain and difficult to debug. Also, it is still possible to put settings in web.config and use your Code as Configuration to read those settings. So it is only recommended to use XML if you know you will need to reconfigure something major and recompiling is not acceptable. We will not be providing any assistance here for configuring DI in XML, though.
The only exception to the Code as Configuration rule is one appSettings value in web.config, which must be set to true in order to use an external DI container:
<appSettings>
<add key="MvcSiteMapProvider_UseExternalDIContainer" value="true"/>
</appSettings>
Note: All of the other appSettings configuration values shown in the Internal DI Configuration section are ignored when this setting is true. This is because we assume the DI setup will be providing the configuration values through constructor injection. You are free to make your DI setup read from appSettings or a custom web.config section if that is what you prefer.
Our Nuget packages follow the [Composition Root pattern] (http://blog.ploeh.dk/2011/07/28/CompositionRoot/). A composition root is a (preferably unique) place in the application where the rest of the pieces are composed together. This puts the top layer of the application in control of all dependencies, which are "pushed down" into the other layers through the use of DI and constructor injection.
In MvcSiteMapProvider, we have 2 basic types of dependencies - services (which implement an interface or abstract class), and settings (which are primarily primitive types, strings, or arrays of primitive types or strings). The DI container needs to be configured to supply both of these types of dependencies. Usually, primitive types require custom parameter directives in order to inject them because the container requires additional info to match on than just the data type.
Here is the default code that is wired up with StructureMap from the MvcSiteMapProvider.MVC4.DI.StructureMap package.
public class MvcSiteMapProviderRegistry
: Registry
{
public MvcSiteMapProviderRegistry()
{
bool enableLocalization = true;
string absoluteFileName = HostingEnvironment.MapPath("~/Mvc.sitemap");
TimeSpan absoluteCacheExpiration = TimeSpan.FromMinutes(5);
bool visibilityAffectsDescendants = true;
bool useTitleIfDescriptionNotProvided = true;
bool securityTrimmingEnabled = false;
string[] includeAssembliesForScan = new string[] { "YourAssemblyName" };
var currentAssembly = this.GetType().Assembly;
var siteMapProviderAssembly = typeof(SiteMaps).Assembly;
var allAssemblies = new Assembly[] { currentAssembly, siteMapProviderAssembly };
var excludeTypes = new Type[] {
// Use this array to add types you wish to explicitly exclude from convention-based
// auto-registration. By default all types that either match I[TypeName] = [TypeName] or
// I[TypeName] = [TypeName]Adapter will be automatically wired up as long as they don't
// have the [ExcludeFromAutoRegistrationAttribute].
//
// If you want to override a type that follows the convention, you should add the name
// of either the implementation name or the interface that it inherits to this list and
// add your manual registration code below. This will prevent duplicate registrations
// of the types from occurring.
// Example:
// typeof(SiteMap),
// typeof(SiteMapNodeVisibilityProviderStrategy)
};
var multipleImplementationTypes = new Type[] {
typeof(ISiteMapNodeUrlResolver),
typeof(ISiteMapNodeVisibilityProvider),
typeof(IDynamicNodeProvider)
};
// Matching type name (I[TypeName] = [TypeName]) or matching type name + suffix Adapter (I[TypeName] = [TypeName]Adapter)
// and not decorated with the [ExcludeFromAutoRegistrationAttribute].
CommonConventions.RegisterDefaultConventions(
(interfaceType, implementationType) => this.For(interfaceType).Singleton().Use(implementationType),
new Assembly[] { siteMapProviderAssembly },
allAssemblies,
excludeTypes,
string.Empty);
// Multiple implementations of strategy based extension points (and not decorated with [ExcludeFromAutoRegistrationAttribute]).
CommonConventions.RegisterAllImplementationsOfInterface(
(interfaceType, implementationType) => this.For(interfaceType).Singleton().Use(implementationType),
multipleImplementationTypes,
allAssemblies,
excludeTypes,
string.Empty);
// Visibility Providers
this.For<ISiteMapNodeVisibilityProviderStrategy>().Use<SiteMapNodeVisibilityProviderStrategy>()
.Ctor<string>("defaultProviderName").Is(string.Empty);
// Pass in the global controllerBuilder reference
this.For<ControllerBuilder>()
.Use(x => ControllerBuilder.Current);
this.For<IControllerTypeResolverFactory>().Use<ControllerTypeResolverFactory>()
.Ctor<string[]>("areaNamespacesToIgnore").Is(new string[0]);
// Configure Security
this.For<IAclModule>().Use<CompositeAclModule>()
.EnumerableOf<IAclModule>().Contains(x =>
{
x.Type<AuthorizeAttributeAclModule>();
x.Type<XmlRolesAclModule>();
});
// Setup cache
SmartInstance<CacheDetails> cacheDetails;
this.For<System.Runtime.Caching.ObjectCache>()
.Use(s => System.Runtime.Caching.MemoryCache.Default);
this.For(typeof(ICacheProvider<>)).Use(typeof(RuntimeCacheProvider<>));
var cacheDependency =
this.For<ICacheDependency>().Use<RuntimeFileCacheDependency>()
.Ctor<string>("fileName").Is(absoluteFileName);
cacheDetails =
this.For<ICacheDetails>().Use<CacheDetails>()
.Ctor<TimeSpan>("absoluteCacheExpiration").Is(absoluteCacheExpiration)
.Ctor<TimeSpan>("slidingCacheExpiration").Is(TimeSpan.MinValue)
.Ctor<ICacheDependency>().Is(cacheDependency);
// Configure the visitors
this.For<ISiteMapNodeVisitor>()
.Use<UrlResolvingSiteMapNodeVisitor>();
// Prepare for our node providers
var xmlSource = this.For<IXmlSource>().Use<FileXmlSource>()
.Ctor<string>("fileName").Is(absoluteFileName);
this.For<IReservedAttributeNameProvider>().Use<ReservedAttributeNameProvider>()
.Ctor<IEnumerable<string>>("attributesToIgnore").Is(new string[0]);
// Register the sitemap node providers
var siteMapNodeProvider = this.For<ISiteMapNodeProvider>().Use<CompositeSiteMapNodeProvider>()
.EnumerableOf<ISiteMapNodeProvider>().Contains(x =>
{
x.Type<XmlSiteMapNodeProvider>()
.Ctor<bool>("includeRootNode").Is(true)
.Ctor<bool>("useNestedDynamicNodeRecursion").Is(false)
.Ctor<IXmlSource>().Is(xmlSource);
x.Type<ReflectionSiteMapNodeProvider>()
.Ctor<IEnumerable<string>>("includeAssemblies").Is(includeAssembliesForScan)
.Ctor<IEnumerable<string>>("excludeAssemblies").Is(new string[0]);
});
// Register the sitemap builders
var builder = this.For<ISiteMapBuilder>().Use<SiteMapBuilder>()
.Ctor<ISiteMapNodeProvider>().Is(siteMapNodeProvider);
// Configure the builder sets
this.For<ISiteMapBuilderSetStrategy>().Use<SiteMapBuilderSetStrategy>()
.EnumerableOf<ISiteMapBuilderSet>().Contains(x =>
{
x.Type<SiteMapBuilderSet>()
.Ctor<string>("instanceName").Is("default")
.Ctor<bool>("securityTrimmingEnabled").Is(securityTrimmingEnabled)
.Ctor<bool>("enableLocalization").Is(enableLocalization)
.Ctor<bool>("visibilityAffectsDescendants").Is(visibilityAffectsDescendants)
.Ctor<bool>("useTitleIfDescriptionNotProvided").Is(useTitleIfDescriptionNotProvided)
.Ctor<ISiteMapBuilder>().Is(builder)
.Ctor<ICacheDetails>().Is(cacheDetails);
});
}
}
That seems like a lot of code, so let's walk through it step-by-step.
bool enableLocalization = true;
string absoluteFileName = HostingEnvironment.MapPath("~/Mvc.sitemap");
TimeSpan absoluteCacheExpiration = TimeSpan.FromMinutes(5);
bool visibilityAffectsDescendants = true;
bool useTitleIfDescriptionNotProvided = true;
bool securityTrimmingEnabled = false;
string[] includeAssembliesForScan = new string[] { "YourAssemblyName" };
This is simply abstracting some of the configuration settings to common variables. The variables are then used to inject the settings directly into various objects later in the method. If you so desire, you can put these settings into <appSettings>
or even a custom configuration section in your web.config file to make them configurable without a recompile.
var currentAssembly = this.GetType().Assembly;
var siteMapProviderAssembly = typeof(SiteMaps).Assembly;
var allAssemblies = new Assembly[] { currentAssembly, siteMapProviderAssembly };
var excludeTypes = new Type[] {
// Use this array to add types you wish to explicitly exclude from convention-based
// auto-registration. By default all types that either match I[TypeName] = [TypeName] or
// I[TypeName] = [TypeName]Adapter will be automatically wired up as long as they don't
// have the [ExcludeFromAutoRegistrationAttribute].
//
// If you want to override a type that follows the convention, you should add the name
// of either the implementation name or the interface that it inherits to this list and
// add your manual registration code below. This will prevent duplicate registrations
// of the types from occurring.
// Example:
// typeof(SiteMap),
// typeof(SiteMapNodeVisibilityProviderStrategy)
};
var multipleImplementationTypes = new Type[] {
typeof(ISiteMapNodeUrlResolver),
typeof(ISiteMapNodeVisibilityProvider),
typeof(IDynamicNodeProvider)
};
// Matching type name (I[TypeName] = [TypeName]) or matching type name + suffix Adapter (I[TypeName] = [TypeName]Adapter)
// and not decorated with the [ExcludeFromAutoRegistrationAttribute].
CommonConventions.RegisterDefaultConventions(
(interfaceType, implementationType) => this.For(interfaceType).Singleton().Use(implementationType),
new Assembly[] { siteMapProviderAssembly },
allAssemblies,
excludeTypes,
string.Empty);
// Multiple implementations of strategy based extension points (and not decorated with [ExcludeFromAutoRegistrationAttribute]).
CommonConventions.RegisterAllImplementationsOfInterface(
(interfaceType, implementationType) => this.For(interfaceType).Singleton().Use(implementationType),
multipleImplementationTypes,
allAssemblies,
excludeTypes,
string.Empty);
The above block is using some custom conventions we made to register most of the built-in classes in bulk. We decided it would be better to build our own conventions than to deal with the various differences in the way that convention based registration is done in each container.
After some variable declarations, we call our static RegisterDefaultConventions() method and RegisterAllImplementationsOfInterface() method.
RegisterDefaultConventions() scans MvcSiteMapProvider and maps all implementations of interfaces with matching names (I[TypeName] = [TypeName] or I[TypeName] = [TypeName]Adapter) automatically except when the type has a string or primitive type parameter in its constructor or has the [ExcludeFromAutoRegistrationAttribute]. So, for example IControllerTypeResolver will be mapped to a class named ControllerTypeResolver if it exists.
RegisterAllImplementationsOfInterface() scans both MvcSiteMapProvider and your application to find and register all implementations of ISiteMapNodeUrlResolver, ISiteMapNodeVisibilityProvider, and IDynamicNodeProvider. These registrations are then automatically injected as collections into SiteMapNodeUrlResolverStrategy, SiteMapNodeVisibilityProviderStrategy, and DynamicNodeProviderStrategy, respectively where the instances can be called upon later by using a string to identify them.
// Visibility Providers
this.For<ISiteMapNodeVisibilityProviderStrategy>().Use<SiteMapNodeVisibilityProviderStrategy>()
.Ctor<string>("defaultProviderName").Is(string.Empty);
// Pass in the global controllerBuilder reference
this.For<ControllerBuilder>()
.Use(x => ControllerBuilder.Current);
this.For<IControllerTypeResolverFactory>().Use<ControllerTypeResolverFactory>()
.Ctor<string[]>("areaNamespacesToIgnore").Is(new string[0]);
These are classes that didn't follow the default convention and/or require explicit injection of settings.
Also, note the use of the .Ctor() method. This is the syntax to use in StructureMap to specify a specific dependency. When using primitive types, strings, or arrays of primitive types or strings, StructureMap requires that you specify the name of the parameter to avoid ambiguity between other dependencies of the same datatype.
// Configure Security
this.For<IAclModule>().Use<CompositeAclModule>()
.EnumerableOf<IAclModule>().Contains(x =>
{
x.Type<AuthorizeAttributeAclModule>();
x.Type<XmlRolesAclModule>();
});
Above, things are getting more interesting. By default we are using not 1, but 2 IAclModule implementations. They are wired here in a sequence using an instance of CompositeAclModule, which implements the Composite pattern. Therefore, when the application requires IAclModule in a constructor, it will be supplied with a CompositeAclModule that will call upon AuthorizeAttributeAclModule followed by XmlRolesAclModule when determining node accessibility.
It is important to understand that this isn't set in stone - you can freely add and remove implementations from this configuration. Further, these implementations don't necessarily have to be from the MvcSiteMapProvider library. Any class that implements IAclModule can be used.
// Setup cache
SmartInstance<CacheDetails> cacheDetails;
this.For<System.Runtime.Caching.ObjectCache>()
.Use(s => System.Runtime.Caching.MemoryCache.Default);
this.For(typeof(ICacheProvider<>)).Use(typeof(RuntimeCacheProvider<>));
var cacheDependency =
this.For<ICacheDependency>().Use<RuntimeFileCacheDependency>()
.Ctor<string>("fileName").Is(absoluteFileName);
cacheDetails =
this.For<ICacheDetails>().Use<CacheDetails>()
.Ctor<TimeSpan>("absoluteCacheExpiration").Is(absoluteCacheExpiration)
.Ctor<TimeSpan>("slidingCacheExpiration").Is(TimeSpan.MinValue)
.Ctor<ICacheDependency>().Is(cacheDependency);
The above block also does something interesting - it is using variables to pass specific instances of objects as dependencies to other objects. While not strictly required for a default implementation because there is only 1 object to cache, we are providing a convenient way to configure multiple cache elements if needed. Note that in this case StructureMap is special - in most other DI containers you will need to create a "Named" instance that you can refer to later in the configuration by the same name.
You can easily remove the cache dependency by supplying a NullCacheDependency instance rather than a RuntimeFileCacheDependency instance, like this.
var cacheDependency =
this.For<ICacheDependency>().Use<NullCacheDependency>();
You can also substitute virtually everything else - for example you can use the built-in AspNetSiteMapCacheProvider (which uses System.Web.Caching) rather than a RuntimeCacheProvider (which uses System.Runtime.Caching), or you can replace it with your own custom cache provider.
// Configure the visitors
this.For<ISiteMapNodeVisitor>()
.Use<UrlResolvingSiteMapNodeVisitor>();
The SiteMapBuilder recursively iterates every site map node in the tree. It executes the action or actions that are configured here for each node. If you wish, you can replace this with a CompositeSiteMapNodeVisitor instance that has a sequence of ISiteMapNodeVisitor instances injected into it (similar to the IAclModule example above). The default implementation simply resolves the URLs and caches them in the SiteMapNode.
// Prepare for our node providers
var xmlSource = this.For<IXmlSource>().Use<FileXmlSource>()
.Ctor<string>("fileName").Is(absoluteFileName);
this.For<IReservedAttributeNameProvider>().Use<ReservedAttributeNameProvider>()
.Ctor<IEnumerable<string>>("attributesToIgnore").Is(new string[0]);
// Register the sitemap node providers
var siteMapNodeProvider = this.For<ISiteMapNodeProvider>().Use<CompositeSiteMapNodeProvider>()
.EnumerableOf<ISiteMapNodeProvider>().Contains(x =>
{
x.Type<XmlSiteMapNodeProvider>()
.Ctor<bool>("includeRootNode").Is(true)
.Ctor<bool>("useNestedDynamicNodeRecursion").Is(false)
.Ctor<IXmlSource>().Is(xmlSource);
x.Type<ReflectionSiteMapNodeProvider>()
.Ctor<IEnumerable<string>>("includeAssemblies").Is(includeAssembliesForScan)
.Ctor<IEnumerable<string>>("excludeAssemblies").Is(new string[0]);
});
// Register the sitemap builders
var builder = this.For<ISiteMapBuilder>().Use<SiteMapBuilder>()
.Ctor<ISiteMapNodeProvider>().Is(siteMapNodeProvider);
// Configure the builder sets
this.For<ISiteMapBuilderSetStrategy>().Use<SiteMapBuilderSetStrategy>()
.EnumerableOf<ISiteMapBuilderSet>().Contains(x =>
{
x.Type<SiteMapBuilderSet>()
.Ctor<string>("instanceName").Is("default")
.Ctor<bool>("securityTrimmingEnabled").Is(securityTrimmingEnabled)
.Ctor<bool>("enableLocalization").Is(enableLocalization)
.Ctor<bool>("visibilityAffectsDescendants").Is(visibilityAffectsDescendants)
.Ctor<bool>("useTitleIfDescriptionNotProvided").Is(useTitleIfDescriptionNotProvided)
.Ctor<ISiteMapBuilder>().Is(builder)
.Ctor<ICacheDetails>().Is(cacheDetails);
});
Ok, now none of this is new if you have read this entire subject. However, what is interesting here is what we are putting into motion. The default setup uses a single ISiteMapBuilderSet instance to execute a ISiteMapBuilder, which in turn executes a sequence of ISiteMapNodeProvider instances. However, if you wanted to you could have as many builder sets as you want, each with their own caching policy.
In addition, note that we abstracted all of the pieces of the process into interfaces that you can implement and replace, if needed. This means that the ISiteMapNodeProvider can get its data from other sources than XML, and the IXmlSource means you can get the XML from other locations than a file.
Once you get past the configuration syntax of DI, you can see that there are literally an infinite number of ways that MvcSiteMapProvider can be configured when using DI.
To get up to speed on DI, I highly recommend the book [Dependency Injection in .NET] (http://www.amazon.com/Dependency-Injection-NET-Mark-Seemann/dp/1935182501) by Mark Seemann, as the approach we followed to DI is precisely what he is preaching. Not only is it a good DI book, but it also has some really good advice about how to refactor code, which makes it invaluable.
Want to contribute? See our Contributing to MvcSiteMapProvider guide.
- Upgrading from v3 to v4
- 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
- Security Trimming
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
- MvcSiteMapProvider 4.0 - SEO Features Tutorial
- How to Make MvcSiteMapProvider Remember a User’s Position
- MvcSiteMapProvider 4.0 - Cache Configuration
- MvcSiteMapProvider 4.0 - Extending the Cache
- MvcSiteMapProvider 4.0 - Unit Testing with the SiteMaps Static Methods
- Debugging an MvcSiteMapProvider Configuration
- Converting from C# to Vb MvcSiteMapProvider
- ASP.NET MVC Menu using Site Map Provider & Bootstrap 3 Navbar
- ASP.NET MVC SiteMapPath using Site Map Provider & Bootstrap Breadcrumbs
- NightOwl888's MvcSiteMapProvider Demos - Filter for "MvcSiteMapProvider" to see the most relevant.
- MvcSiteMapProvider Tutorial and Examples
- MvcSiteMapProvider Tutorial 2 - Breadcrumbs
- Getting Started with MvcSiteMapProvider
- Inside the MvcSiteMapProvider - Part 1
- Inside the MvcSiteMapProvider - Part 2: Dynamic node providers
- Inside the MvcSiteMapProvider - Part 3: The ISiteMapVisibilityProvider
- Inside the MvcSiteMapProvider - Part 4: The IAclModule
- Inside the MvcSiteMapProvider - Part 5: The ISiteMapNodeUrlResolver
- Styling MvcSiteMapProvider with CSS
- Using MvcSiteMapProvider with Twitter Bootstrap
- ASP.NET MVC Menu using Site Map Provider & Bootstrap Navbar