Search code examples
c#.netmodel-view-controllerurl-routingmvcsitemapprovider

MVCSiteMapProvider some nodes match, others do not


I am using MvcSiteMapProvider to generate breadcrumbs and I am having trouble matching nodes with a new feature. We use MVC5 areas and are using the latest MvcSiteMapProvider.MVC5 libraries. We are using i18n with Resx files, our title attribute being keys. Our page URLs don't change after release, so use the standard XML config.

We use MVC5 attribute based routing.

The List action is the default action for both the Home controller and the area, so is on the Store/ route. It works fine, the match is made.

The Search action Store/Search route does not match a node.

Configuration

<mvcSiteMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0"
    xsi:schemaLocation="http://mvcsitemap.codeplex.com/schemas/MvcSiteMap-File-4.0 MvcSiteMapSchema.xsd">

  <mvcSiteMapNode controller="Dashboard" action="Index" title="Foobar" key="Bar">

    <!-- quite a large file -->

    <mvcSiteMapNode area="Store" controller="Home" action="List" title="SiteMap_DocumentStore_Home_List" preservedRouteParameters="page, itemsPerPage, msg">
      <mvcSiteMapNode area="Store" controller="Home" action="Search" title="SiteMap_DocumentStore_Search" preservedRouteParameters="tags, page"/>

      <!-- snip extra entries -->
    </mvcSiteMapNode>
  </mvcSiteMapNode>
</mvcSiteMap>

I appreciate that I can remove attributes area and controller from the mvcSiteMapNode children of List. I've left them in here for completeness.

Home Controller

[RouteArea("Store")]
[Route("{action=list}")]
public class HomeController : Controller
{
  [Route("{page?}/{itemsPerPage?}")]
  public ActionResult List(int page = 1, int itemsPerPage = -1, string msg = "")
  {}

  [Route("Search/{tags?}/{page?}")]
    public ActionResult Search(string tags = "", int page = 1)
  {}
}

Investigation

I have a feeling that it is something to do with the MVC Route for the List action being empty. If I change the route of List to:

[Route("List/{page?}/{itemsPerPage?}")]
public ActionResult List(int page = 1, int itemsPerPage = -1, string msg = "")
{}

Then the Search node will then match, as will its siblings (that I snipped out)

Edit - Simplify the routing

I have removed the default route for the controller [Route("{action=list}")]. The problem still persists.


Solution

  • Issue #1:

    Per MSDN:

    Default Route

    You can also apply the [Route] attribute on the controller level, capturing the action as a parameter. That route would then be applied on all actions in the controller, unless a specific [Route] has been defined on a specific action, overriding the default set on the controller.

    In your case, the default controller-level route will be completely ignored because in each case you have a route at the action level that is overriding it.

    Issue #2:

    I looked into why it is "not matching" by starting a new MVC 5 project in VS 2015 and adding an area and the rest of your config. For awhile I was perplexed as to why it wasn't working.

    Then I discovered that the scaffolding wires up a different layout page for each Area in /Area/<area name>/Views/_ViewStart.cshtml.

    @{
        Layout = "~/Areas/Store/Views/Shared/_Layout.cshtml";
    }
    

    I changed it to use the shared ViewStart.cshtml file, and then it showed the breadcrumb trail.

    @{
        Layout = "~/Views/Shared/_Layout.cshtml";
    }
    

    Issue #3:

    Also, you have an issue with your preserved route parameters. Since they are always derived from the current request, the inner request must always provide all of the parameters of its ancestors. Furthermore, the parameters must not have a different meaning between parent and child, so for example page must refer to the same page for both List and Search. In other words, each key name must be unique within its ancestry.

    If they are the same, you can fix this by simply adding the additional parameter to the Search URL.

    [Route("Search/{page?}/{itemsPerPage?}/{tags?}")]
    

    Otherwise you should give each page parameter a different name.

    See How to make MvcSiteMapProvider Remember a User's Position and the demos included for guidance.