Search code examples
mvcsitemapprovider

MvcSiteMapProvider how to make a node match the current url


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.


Solution

  • 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.