MvcSiteMapProvider icon indicating copy to clipboard operation
MvcSiteMapProvider copied to clipboard

Returning current node for a detail view using permalink routing

Open laredoza opened this issue 8 years ago • 1 comments

Hi,

I'm not sure if this is an issue or my approach is incorrect. Any help would be appreciated.

I am able to retrieve the current sitemap node(SiteMaps.Current.CurrentNode) for /Product/Details/1 with the default routing:

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

However if I use the following routing:

routes.MapRoute( name: "CmsRoute", url: "{*permalink}", defaults: new { controller = "Page", action = "Index", id = UrlParameter.Optional }, constraints: new { permalink = new BaseRoutingConstraint() } );

The BaseRoutingContraint uses the following Matching Method:

public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { bool result = false;

        if (values[parameterName] != null)
        {
            var permalink = string.Format("~/{0}", values[parameterName].ToString());
            result = BaseSiteMapNode.ReturnAll().Any(a => a.Url == permalink || (a.Controller == values["Controller"] && a.Action == values["Action"]));
        }

        return result;
    }

My nodes are setup as follows:

new DynamicNode { Title = "Products", ParentKey = "00000000-0000-0000-0000-000000000001", Key = "id5", Controller = "Product", Action = "Index", Url = "~/Product/Index", PreservedRouteParameters = new []{"id"}, Attributes = new Dictionary<string, object> { {"Template", "~/Views/Shared/_Layout.cshtml"} }, } , new DynamicNode { Title = "Details", ParentKey = "id5", Key = "id7", Controller = "Product", Action = "Details", Route = "CmsRoute", PreservedRouteParameters = new []{"id"}, Attributes = new Dictionary<string, object> { {"Template", "~/Views/Shared/_Layout.cshtml"}, {"visibility", "MainMenu,!*"} },

        },
        new DynamicNode
        {
           Title = "Edit",
           ParentKey = "id5",
           Key = "id8",
           Controller = "Page",
           Action = "Index",
           PreservedRouteParameters = new []{"id"},
           Attributes = new Dictionary<string, object>
                    {
                        {"Template", "~/Views/Shared/_Layout.cshtml"},
                        {"visibility", "MainMenu,!*"}
                    },

        }

Any node which contains a url such as /Product/Index works fine but as soon as an Id is passed, the current node is not retrieved.

laredoza avatar Sep 26 '15 14:09 laredoza

I am not sure what your goal is, but what you are doing is creating a catch-all placeholder (this is not called permalink routing).

When you do that, you are essentially forwarding the routing logic into the Controller instead of leaving it in the RouteBase-derived class where it belongs. Since this technically is no longer routing, it is not something that MvcSiteMapProvider supports.

Do note that the .NET routing framework is very flexible. You can create any URL scheme you like (including database-driven CMS routes) by inheriting RouteBase as in this example. You can also support areas with your custom route classes by implementing IRouteWithArea.

Also, MvcSiteMapProvider is driven by routes, it does not drive routes. It cannot be used inside of a route constraint like what you are doing (at least that is what it looks like, correct me if I am wrong) because at that point the preservedRouteParameters are not yet available in the request. The route values (where they are derived from) are added after the route is matched by the .NET routing framework, and a constraint is part of that matching logic.

In your case, the route values would always look like this because that is what you have defined as the placeholders in the route.

Key Value
controller Home
action Index
id
permalink Product/Index (or whatever the URL was that was passed)

Since the only thing you have that varies between routes is the permalink parameter, you would have to setup your nodes like this:

yield return new DynamicNode
{
    Title = "Products",
    ParentKey = "00000000-0000-0000-0000-000000000001",
    Key = "id5",
    Controller = "Home",
    Action = "Index",
    RouteValues = new Dictionary<string, object> 
    { 
        { "permalink", "Product/Index" } 
    },
    PreservedRouteParameters = new string[]{"id"},
    Attributes = new Dictionary<string, object>
    {
        {"Template", "~/Views/Shared/_Layout.cshtml"}
    }
};

yield return new DynamicNode
{
    Title = "Edit",
    ParentKey = "id5",
    Key = "id8",
    Controller = "Home",
    Action = "Index",
    RouteValues = new Dictionary<string, object> 
    { 
        { "permalink", "Page/Index" } 
    },
    // Note that this id must refer to the same entity as the id5 route
    PreservedRouteParameters = new []{"id"}, 
    Attributes = new Dictionary<string, object>
    {
        {"Template", "~/Views/Shared/_Layout.cshtml"},
        {"visibility", "MainMenu,!*"}
    }
};

I haven't tested that, and I wouldn't recommend that you do it that way. Instead, use routing to map URLs to route value dictionaries and route value dictionaries back to URLs. Then, configure those same route values in MvcSiteMapProvider.

NOTE: The Url property will override routing and make the node URL-based, which comes in handy for external URLs and those to ASP.NET pages, but it is generally not the best way to use with MVC.

NightOwl888 avatar Oct 10 '15 17:10 NightOwl888