MvcSiteMapProvider 2.0: node for action with parameters

Jun 21, 2010 at 9:33 PM

hello, maartenba

1st of all I would like to thanks for your work! I tried to use MvcSiteMapProvider v. 2.0.0.0  for actions which have parameter, e.g.

 

public class CategoryController : Controller
{
        [HttpGet]
        public ActionResult Details(int id)
        {
             ...
        }
}

and defined site map node for it:

<mvcSiteMapNode title="$Resources:Breadcrumb,Category_Details" controller="Category" action="Details" />

But by default Html.MvcSiteMap().SiteMapPath() doesn't show path for ~/Category/Details/{Id} URL - it shows path only for static URLs which correspond to action without params. I found that in previous release there was isDynamic and dynamicParameters attributes of mvcSiteMapNode (from here) and paramid (from here). But seems like they were outdated in 2.0 release. So how can I add breadcrumb path for actions with parameters?

Jun 22, 2010 at 7:56 AM

I found the issue. It is caused by the fix of the following bug: http://mvcsitemap.codeplex.com/workitem/4635 where ReflectedControllerDescriptor was replaced by ReflectedAsyncControllerDescriptor. See DefaultActionMethodParameterResolver.cs, line 64:

ControllerDescriptor controllerDescriptor = new ReflectedAsyncControllerDescriptor(controllerType);
ActionDescriptor[] actionDescriptors = controllerDescriptor.GetCanonicalActions()

The problem was that actionDescriptors array was empty. After I changed ReflectedAsyncControllerDescriptor -> ReflectedControllerDescriptor, nodes which correspond to actions with parameters (Category/Details/{id}, Category/Edit/{id}) started to work properly. So without including of source code of MvcSiteMapProvider into solution it can be fixed by custom ActionMethodParameterResolver:

public class CustomActionMethodParameterResolver : IActionMethodParameterResolver
{
    public CustomActionMethodParameterResolver()
    {
        Cache = new Dictionary<string, IEnumerable<string>>();
    }

    protected Dictionary<string, IEnumerable<string>> Cache { get; private set; }

    public IEnumerable<string> ResolveActionMethodParameters(IControllerTypeResolver controllerTypeResolver,
                                                             string areaName, string controllerName,
                                                             string actionMethodName)
    {
        // Is the request cached?
        string cacheKey = areaName + "_" + controllerName + "_" + actionMethodName;
        if (Cache.ContainsKey(cacheKey))
        {
            return Cache[cacheKey];
        }

        // Get controller type
        Type controllerType = controllerTypeResolver.ResolveControllerType(areaName, controllerName);

        // Get action method information
        var actionParameters = new List<string>();
        if (controllerType != null)
        {
            ControllerDescriptor controllerDescriptor = new ReflectedControllerDescriptor(controllerType);
            ActionDescriptor[] actionDescriptors = controllerDescriptor.GetCanonicalActions()
                .Where(a => a.ActionName == actionMethodName).ToArray();

            if (actionDescriptors != null && actionDescriptors.Length > 0)
            {
                foreach (ActionDescriptor actionDescriptor in actionDescriptors)
                {
                    actionParameters.AddRange(actionDescriptor.GetParameters().Select(p => p.ParameterName));
                }
            }
        }

        // Cache the result
        if (!Cache.ContainsKey(cacheKey))
        {
            Cache.Add(cacheKey, actionParameters);
        }

        // Return
        return actionParameters;
    }
}

The only difference with  DefaultActionMethodParameterResolver is that ReflectedControllerDescriptor is used instead of ReflectedAsyncControllerDescriptor. And it should be registered in web.config file:

<siteMap defaultProvider="MvcSiteMapProvider" enabled="true">
  <providers>
    <clear />
    <add name="MvcSiteMapProvider"
         type="MvcSiteMapProvider.DefaultSiteMapProvider, MvcSiteMapProvider"
         ...
         actionMethodParameterResolver="MyNamespace.CustomActionMethodParameterResolver, MyAssembly"
     />
  </providers>
</siteMap>

Hope it will be fixed in default implementation as well.

Coordinator
Jun 22, 2010 at 9:19 AM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.
Coordinator
Jun 22, 2010 at 9:21 AM
This discussion has been copied to a work item. Click here to go to the work item and continue the discussion.