1

The current node is coming up null. I can't figure out how to make MvcSiteMapProvider resolve the node under this circumstance.

Here's the node it needs to match

<mvcSiteMapNode title="Policy" route="Details" typeName="Biz.ABC.ShampooMax.Policy" preservedRouteParameters="id" />

Here's the route:

routes.MapRoute(
   "Details",
   "Details/{id}",
   new { Controller = "Object", Action = "Details" }
   ).RouteHandler = new ObjectRouteHandler();

The link that gets clicked:

http://localhost:36695/MyGreatWebSite/Details/121534455762071

It's hitting the route ok. Just the MvcSiteMapProvider.SiteMaps.Current.CurrentNode is null.

toddmo
  • 20,682
  • 14
  • 97
  • 107
  • Behavior any different if you drop the `preservedRouteParameters` attribute? – Cᴏʀʏ Oct 24 '16 at 21:19
  • @Cᴏʀʏ, no, same behavior – toddmo Oct 24 '16 at 21:23
  • Interesting. Usually when I've run into a null `CurrentNode`, the XML is misconfigured. Does it browse to your controller action as expected? – Cᴏʀʏ Oct 24 '16 at 21:35
  • @Cᴏʀʏ, yes. The mvcSiteMapNode is just getting left out of the party. He has an attribute that my action method needs to run successfully :) – toddmo Oct 24 '16 at 22:22
  • Instead of specifying the route in your XML, what happens if you remove that, and the typeName, and add in `controller="Object" action="Details"`? – Cᴏʀʏ Oct 25 '16 at 13:15
  • using `` as the node, the link still has the node being `null`. Even if I then add `preservedRouteParameters="id" ` to it, it's still `null`. – toddmo Oct 25 '16 at 15:05

2 Answers2

2

A null result for CurrentNode indicates the incoming request doesn't match any node in the SiteMap In your case there are 4 different problems that may be contributing to this:

  1. The URL you are inputting http://localhost:36695/MyGreatWebSite/Details/121534455762071 does not (necessarily) match the URL pattern you specified in the route, "Details/{id}". It may if your site is hosted as an IIS application under IISROOT/MyGreatWebSite/.
  2. Your mvcSiteMapNode doesn't specify a controller or action to match. route only acts like an additional piece of criteria (meaning only the named route will be considered in the match), but all of the parameters also need to be provided in order for there to be a match with the route.
  3. You are passing in a custom RouteHandler, which could alter how the route matches the URL. Without seeing the code from your ObjectRouteHandler, it is impossible to tell if or how that will affect how the route matches the URL.
  4. You have a custom attribute, typeName in your mvcSiteMapNode configuration. Unless you have specified to ignore this attribute, it will also be required in the URL to match, i.e. http://localhost:36695/MyGreatWebSite/Details/121534455762071?typeName=Biz.ABC.ShampooMax.Policy.

I recommend against using a custom RouteHandler for the purpose of matching URLs. The effect of doing so makes your incoming routes (URLs into MVC) act differently than your outgoing routes (URLs generated to link to other pages). Since MvcSiteMapProvider uses both parts of the route, it will cause URL generation problems if you only change the incoming routes without also changing the outgoing routes to match. Instead, I recommend you subclass RouteBase, where you can control both sides of the route. See this answer for an example of a custom RouteBase subclass.

However, do note that conventional routing is pretty powerful out of the box and you probably don't need to subclass RouteBase for this simple scenario.

Community
  • 1
  • 1
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • Please see my answer (solution). I think it will tell you how I can further modify my node xml or route config to make it work without changing the mvc sitemap provider code. Thanks Night Owl! – toddmo Nov 29 '16 at 21:27
0

Solution

I arrived at the answer by adding the mvc sitemap provider project into my own solution and stepping through the mvc sitemap provider code to see why my node wasn't being matched. A few things had to be changed. I fixed it by doing the following:

Mvc.sitemap

<mvcSiteMapNode title="Policy" controller="Object" action="Details" typeName="Biz.ABC.ShampootMax.Policy" preservedRouteParameters="id" roles="*"/>

RouteConfig.cs

  routes.MapRoute(
      name: "Details",
      url: "details/{id}",
      defaults: new { controller = "Object", action = "Details", typeName = "*" }
  ).RouteHandler = new ObjectRouteHandler();

Now at first it didn't want to work like this, but I modified the provider like so:

RouteValueDictionary.cs (added wildcard to match value)

    protected virtual bool MatchesValue(string key, object value)
    {
        return this[key].ToString().Equals(value.ToString(), StringComparison.OrdinalIgnoreCase) || value.ToString() == "*";
    }

SiteMapNode.cs (changed requestContext.RouteData.Values)

/// <summary>
/// Sets the preserved route parameters of the current request to the routeValues collection.
/// </summary>
/// <remarks>
/// This method relies on the fact that the route value collection is request cached. The
/// values written are for the current request only, after which they will be discarded.
/// </remarks>
protected virtual void PreserveRouteParameters()
{
  if (this.PreservedRouteParameters.Count > 0)
  {
    var requestContext = this.mvcContextFactory.CreateRequestContext();
    var routeDataValues = requestContext.HttpContext.Request.RequestContext.RouteData.Values;// requestContext.RouteData.Values;

I think the second modification wasn't strictly necessary because my request context wasn't cached; it would have worked if it was. I didn't know how to get it cached.

It's the first modification to make route values honor a wildcard (*) that made it work. It seems like a hack and maybe there's a built in way.

Note

Ignoring the typeName attribute with:

web.config

<add key="MvcSiteMapProvider_AttributesToIgnore" value="typeName" />

makes another node break:

Mvc.sitemap

<mvcSiteMapNode title="Policies" url="~/Home/Products/HarvestMAX/Policy/List" productType="HarvestMax" type="P" typeName="AACOBusinessModel.AACO.HarvestMax.Policy" roles="*">

so that's why I didn't do that.

toddmo
  • 20,682
  • 14
  • 97
  • 107