Skip to content
Shad Storhaug edited this page Apr 24, 2018 · 2 revisions

Note: This page assumes you have prior knowledge of how to configure routing for use with MVC. If not, you can read up on routing in Understanding Routing in ASP.NET MVC or on MSDN.

MvcSiteMapProvider typically uses the ASP.NET routing framework as the way to determine when there is a matching sitemap node (it can also use URL or key to find a match). This node matching is what determines which node is the "current" node, so understanding how it works is very useful for troubleshooting problems with the configuration.

By default, MvcSiteMapProvider uses controller, action, and (optionally) area route values and recognizes them natively. However, when using other route values you will need to do some additional setup in order for the node matching to work properly.

For example, imagine you want to use the default route from the MVC template:

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }

Also, you want to use the optional id parameter in a Product controller:

    public ActionResult Details(int id)
    {
        var product = GetProduct(id);

        return View(product);
    }

Here is what the XML configuration would typically look like:

<mvcSiteMapNode title="Products" controller="Product" action="Index">
    <mvcSiteMapNode title="Details" action="Details" />
</mvcSiteMapNode>

The default behavior automatically inherits the controller (Product in this case) from its parent. Also, the id parameter is ignored by default so you will have to programmatically add the parameter or use a preservedRouteParameters attribute for it to be considered as part of the node match.

Note: PreservedRouteParameters are generally only useful for making the SiteMapPath (breadcrumb trail) HTML helper work alongside navigation that is based on a list or table of database records. However, if you want the items to appear in the Menu or SiteMap HTML helpers or appear in the /sitemaps.xml endpoint (the sitemaps XML for search engines), you should use a dynamic node provider to add a node for every record as shown in Programmatically Adding the Route Parameter.

preservedRouteParameters Attribute

You can use the preservedRouteParameters attribute to preserve the parameter right in the XML configuration:

<mvcSiteMapNode title="Products" controller="Product" action="Index">
    <mvcSiteMapNode title="The Product" action="Details" preservedRouteParameters="id" />
</mvcSiteMapNode>

And your action method would look something like this:

    public ActionResult Details(int id)
    {
        var product = GetProduct(id);

        return View(product);
    }

The preservedRouteParameters will force every "id" that is passed in from the URL to match the node titled "The Product".

The SiteMapPath would look like this:

URL Sitemap Path
Product/Details/1 Home > Products > The Product
Product/Details/2 Home > Products > The Product

SiteMapNodeTitleAttribute

Typically, you will also want to set the title to the same as the product. This can be done using the SiteMapNodeTitleAttribute.

    [SiteMapTitle("Title")]
    public ActionResult Details(int id)
    {
        var product = GetProduct(id);
        
        // The SiteMapTitleAttribute will automatically read the product.Title
        // property and use it to set the title property of the node.

        return View(product);
    }

As a result, you would get sitemap paths similar to this:

URL Sitemap Path
Product/Details/1 Home > Products > Product 1
Product/Details/2 Home > Products > Product 2

The SiteMapNodeTitleAttribute also supports targeting the parent node. Consider the following action method:

    [SiteMapTitle("Title")]
    [SiteMapTitle("Genre.Name", Target = AttributeTarget.ParentNode)]
    public ActionResult Details(int id)
    {
        var album = storeDB.Albums
            .Single(a => a.AlbumId == id);

        return View(album);
    }

By specifying two SiteMapNodeTitleAttribute with a different Target assigned, you would get sitemap paths similar to this:

URL Sitemap Path
Product/Details/1 Home > Genre A > Product 1
Product/Details/2 Home > Genre B > Product 2

Programmatically Adding the Route Parameter

As you can see this configuration above isn't always practical. Typically products would be put into a database, in which case you would want to configure your nodes based on database records, not XML. This is where the IDynamicNodeProvider interface comes in handy. Create a small class inheriting DynamicNodeProviderBase:

    public class MyDynamicNodeProvider 
        : DynamicNodeProviderBase 
    { 
        public override IEnumerable<DynamicNode> GetDynamicNodeCollection(ISiteMapNode node) 
        { 
            // Create a node for each product
            foreach (var product in GetProducts())
            {
                DynamicNode dynamicNode = new DynamicNode("ProductID_" + product.Id, product.Title);
                // Preserve our route parameter explicitly
                dynamicNode.RouteValues.Add("id", product.id);

                yield return dynamicNode; 
            }
        }
    }

Then add your new provider to the XML configuration:

<mvcSiteMapNode title="Products" controller="Product" action="Index">
    <mvcSiteMapNode title="Details" action="Details" dynamicNodeProvider="MyNamespace.MyDynamicNodeProvider, MyAssembly"/>
</mvcSiteMapNode>

The result looks the same, but we will have nodes added automatically as they are added to the database (after the MvcSiteMapProvider's cache expires). This means that in this example, there will actually be 2 separate nodes instead of one.

URL Sitemap Path
Product/Details/1 Home > Products > Product 1
Product/Details/2 Home > Products > Product 2

For a more in-depth look at how the node matching process works, read How to Make MvcSiteMapProvider Remember a User's Position.

Specifying a Route

Sometimes you will want to specify a route explicitly to override the default route order. This can be done using the route attribute. Let's add a route to the default routing setup.

    routes.MapRoute(
        name: "Default",
        url: "{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }

    routes.MapRoute(
        name: "MyRoute",
        url: "Manage/{controller}/{action}/{id}",
        defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }

Note that this is generally not the proper way to setup routing because the first route will catch anything and the second route will never be hit. We can override that behavior by specifying the route explicitly.

<mvcSiteMapNode title="Products" controller="Product" action="Index" route="MyRoute"/>

This will force the match to use the route named MyRoute and ignore the route named Default.


Want to contribute? See our Contributing to MvcSiteMapProvider guide.



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