MVCSiteMapProvider breadcrumbs incorrect parent no

2019-05-11 03:21发布

问题:

I have this sitemap:

<mvcSiteMapNode title="Projects" controller="Projects" action="Index" key="Home" visibility="!*">
<mvcSiteMapNode title="Projects" controller="Projects" action="Index">
  <mvcSiteMapNode title="Project" controller="Projects" action="Details" preservedRouteParameters="id">
    <mvcSiteMapNode title="Session" controller="Sessions" action="Details" preservedRouteParameters="id">
      <mvcSiteMapNode title="Edit Session" controller="Sessions" action="Edit" preservedRouteParameters="id"/>
    </mvcSiteMapNode>
  </mvcSiteMapNode>
</mvcSiteMapNode>
<mvcSiteMapNode title="My Account" controller="Account" action="ChangePassword" />
<mvcSiteMapNode title="Admin" controller="Admin" action="Index" >
  <mvcSiteMapNode title="Create User" controller="Admin" action="AddUser" />
  <mvcSiteMapNode title="Manage Users" controller="Admin" action="Users" />
</mvcSiteMapNode>

When I go to Session Details page, the breadcrumbs display:

Projects > Project > Session

however the project link, which goes to the Project Details page, is using the same id as the session, not the project it came from.

I tried adding inheritedRouteParameters="id" to the Session Details page, but it didn't change anything.

Edit: I added different preservedRouteParameters, but now "Project" links back to /Projects/Details without the id attached.

回答1:

preservedRouteParameters always copies the value from the current request if the key name matches, it makes no assumptions about what the "id" is. Therefore, if you use "id" in preservedRouteParameters on 2 nodes that are displayed at the same time, you need to make sure they have the same meaning.

One way around this is to use a different key name for each case ("projectId" and "sessionId", for example). Then you can preserve both on the child node so it will "remember" what parent it belongs to.

<mvcSiteMapNode title="Project" controller="Projects" action="Details" preservedRouteParameters="projectId">
    <mvcSiteMapNode title="Session" controller="Sessions" action="Details" preservedRouteParameters="projectId,sessionId">

You may need to modify your routes to get this to make URLs that are acceptable to your requirements, but for this to work the parent ID must be a part of the route (and usually the URL) of the child node. Here is an example that matches the above node configuration.

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        routes.MapRoute(
            name: "SessionRoute", 
            url: "Project/{projectId}/{sessionId}", 
            defaults: new { controller = "Sessions", action = "Details" });

        routes.MapRoute(
            name: "ProjectRoute", 
            url: "Project/{projectId}", 
            defaults: new { controller = "Projects", action = "Details" });

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

Have a look at the MvcSiteMapProvider-Forcing-A-Match-2-Levels project in this demo to see exactly how this can be done. Do note that adding the routes is optional - you can always use the default route if you don't mind querystring parameters in the URL.

If that doesn't suit you, then you can combine using preservedRouteParmeters on one node parameter with setting another parameter explicitly.

<mvcSiteMapNode title="Project" controller="Projects" action="Details" preservedRouteParameters="projectId">
    <mvcSiteMapNode title="Session" controller="Sessions" action="Details" id="1" preservedRouteParameters="projectId">
    <mvcSiteMapNode title="Session" controller="Sessions" action="Details" id="2" preservedRouteParameters="projectId">
    <mvcSiteMapNode title="Session" controller="Sessions" action="Details" id="3" preservedRouteParameters="projectId">

Or you can create a node for each combination of "id"s that you have.

<mvcSiteMapNode title="Project" controller="Projects" action="Details" id="1">
    <mvcSiteMapNode title="Session" controller="Sessions" action="Details" id="1">
    <mvcSiteMapNode title="Session" controller="Sessions" action="Details" id="2">
    <mvcSiteMapNode title="Session" controller="Sessions" action="Details" id="3">
</mvcSiteMapNode>
<mvcSiteMapNode title="Project" controller="Projects" action="Details" id="2">
    <mvcSiteMapNode title="Session" controller="Sessions" action="Details" id="4">
    <mvcSiteMapNode title="Session" controller="Sessions" action="Details" id="5">
    <mvcSiteMapNode title="Session" controller="Sessions" action="Details" id="6">
</mvcSiteMapNode>

For these last 2 options, it is usually best to use a DynamicNodeProvider to populate the dynamic data rather than marking them all up in XML.

These methods create cleaner URLs, but also use up more RAM. Generally for administration pages, it is better to go with the slightly sloppy URLs (preservedRouteParmaters) and save using dynamic nodes (and RAM) for pages that require search engine indexing.

inheritedRouteParameters is only for inheriting values from the parent node in the XML configuration and has no effect at the request level.

BTW - You must ensure you have a unique combination of route values on each node. In your example, the first 2 nodes have exactly the same route values, meaning the second one will never be matched because the first match always wins.