Search code examples
asp.net-mvcasp.net-mvc-4razormvcsitemapprovider

Multiple path to same Razor view - ASP.NET MVC breadcrum using SiteMap Provider


I have Asp.NET MVC SiteMap Provider version 4.0.

I have written a small test project that has several test Razor views. Here is MvcSite xml file that has paths defined:

 <mvcSiteMapNode title="Home - default" controller="Home" action="Index">
    <mvcSiteMapNode title="Middle - default" controller="Home" action="Middle">
      <mvcSiteMapNode title="Edit - default" controller="Home" action="Edit" key="keyForEdit">
      </mvcSiteMapNode>
    <mvcSiteMapNode title="Over - another path" controller="Home" action="Over">
      <mvcSiteMapNode title="Edit through over" controller="Home" action="Edit" canonicalKey="keyForEdit">
      </mvcSiteMapNode>
    </mvcSiteMapNode>
    </mvcSiteMapNode>
  </mvcSiteMapNode>
</mvcSiteMap>

The idea is to reach "Edit" view from different places in application. I can go through "Middle" or through "Over" Razor view. My goal is to have different bredcrumb displays for these different paths to "Edit" view. Here is a master layout that the site is built with:

    @{
        Layout = null;
    }

    <!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>This is layout</title>
    @Html.MvcSiteMap().CanonicalTag()
</head>
<body>
    <div>Hi from Layout!</div>
    <div>@Html.MvcSiteMap().SiteMapPath()</div>
    <div>
        @RenderBody()
    </div>
</body>
</html>

Still, each time I get to "Edit" view I have "Edit - default" title inside breadcrumb. Why is this? Thank you very much for your time.


Solution

  • The reason for this is because you have 2 different nodes with the exact same route signature. When this occurs the first match will always win when determining the current node (the node for the current request).

    For your example, both URLs have a route that look like this.

    |-----------------------------------|-----------------------------------|
    |         Current Request           |          SiteMap Node             |
    |-----------------------------------|-----------------------------------|
    |      Key       |      Value       |      Key       |      Value       |
    |-----------------------------------|-----------------------------------|
    | controller     | Home             | controller     | Home             |
    | action         | Edit             | action         | Edit             |
    |-----------------------------------|-----------------------------------|
    

    The problem is that there is no unique way to get to the second node, so it will never match. Also, the URL (and the route) will be the same in both cases.

    In order for this to work properly, the route signature must be unique for each node even if they represent the same location. The simplest way to achieve that is to add an additional parameter to make a different route signature and different URL for one of the cases.

    <mvcSiteMapNode title="Home - default" controller="Home" action="Index">
        <mvcSiteMapNode title="Middle - default" controller="Home" action="Middle">
            <mvcSiteMapNode title="Edit - default" controller="Home" action="Edit" key="keyForEdit"/>
        </mvcSiteMapNode>
        <mvcSiteMapNode title="Over - another path" controller="Home" action="Over">
            <mvcSiteMapNode title="Edit through over" controller="Home" action="Edit" category="path2" canonicalKey="keyForEdit"/>
        </mvcSiteMapNode>
    </mvcSiteMapNode>
    

    When using the default route, the first edit page will generate the URL /Home/Edit and the second one will generate the URL /Home/Edit?category=path2. This gives MvcSiteMapProvider enough information to tell the difference between the 2 locations because they are now unique. Here is what the second one now looks like.

    |-----------------------------------|-----------------------------------|
    |         Current Request           |          Default Node             |
    |-----------------------------------|-----------------------------------|
    |      Key       |      Value       |      Key       |      Value       |
    |-----------------------------------|-----------------------------------|
    | controller     | Home             | controller     | Home             |
    | action         | Edit             | action         | Edit             |
    | category       | path2            |                                   |   
    |-----------------------------------|-----------------------------------|
    
    
    |-----------------------------------|-----------------------------------|
    |         Current Request           |        Another Path Node          |
    |-----------------------------------|-----------------------------------|
    |      Key       |      Value       |      Key       |      Value       |
    |-----------------------------------|-----------------------------------|
    | controller     | Home             | controller     | Home             |
    | action         | Edit             | action         | Edit             |
    | category       | path2            | category       | path2            |
    |-----------------------------------|-----------------------------------|
    

    So when the current request route includes category=path2, it will now match the second node when looking up the current node and fail to match the first one because of the extra parameter.

    You could also arrange your routes to create URLs that conform to any scheme that you like. The only limitation is that the route for each node must be unique.

    Have a look at MvcSiteMapProvider - SEO Features Tutorial for an example and downloadable demo project. Also, it might help if you review How to Make MvcSiteMapProvider Remember a User's Position if you intend to use a custom URL scheme.