Question

I am trying to get get an ActionDescriptor for an action on a controller that uses Attribute Routing, however it is always null.

var controllerDescriptor = new ReflectedControllerDescriptor(controllerType);
var actionDescriptor = 
    controllerDescriptor.FindAction(controllerContext, actionName) ??
    controllerDescriptor.GetCanonicalActions().FirstOrDefault(a => a.ActionName == actionName);

From my research, I have found that in the class ActionMethodSelectorBase there is a method called PopulateLookupTables which splits up all of the methods in the controller that you give it. Inside thie method there it filters the list of MethodInfo's into 2 sets of lists.

  • AliasedMethods - All action methods with no direct routes that are decorated with the ActionNameSelectorAttribute.
  • NonAliasedMethods - All action methods with no direct routes that are not decorated with the ActionNameSelectorAttribute.

NOTE: The AliasedMethods and NonAliasedMethods will be empty if a direct route (RouteAttribute) is set at the controller level.

NOTE: Direct Routes are defined as methods that are in a controller (excluding constructors and events) and are decorated with an attribute that inherits from either IRouteInfoProvider or IDirectRouteFactory (RouteAttribute inherits from both of these).

and

  • DirectRouteMethods - Methods with some form of IRouteInfoProvider decorating them directly.
  • StandardRouteMethods - Methods with no IRouteInfoProvider decorating them directly. (includes action methods in a controller with RouteAttribute but where the method does not have a RouteAttribute).

When ReflectedControllerDescriptor.FindAction is called, it internally calls ActionMethodSelectorBase.FindActionMethods which only looks at the AliasedMethods and NonAliasedMethods (which exclude all actions with direct routes).

When ReflectedControllerDescriptor.GetCanonicalActions is called, it internally calls ReflectedControllerDescriptor.GetAllActionMethodsFromSelector which only looks at the AliasedMethods and NonAliasedMethods (which exlude all actions with direct routes).

From what I can see, the DirectRouteMethods are only used in one place, the RouteCollection.MapMvcAttributeRoutes extention methods. This means that the RouteTable.Routes collection has a RouteCollectionRoute to the action but I am not sure how to get to it.

Does anyone know how to get an ActionDescriptor for an action that has a RouteAttribute

Was it helpful?

Solution

You can use the AsyncControllerActionInvoker class to do this, but since the method you need is protected, you need to first inherit the class and redefine the method as public.

private class ActionSelector 
    : AsyncControllerActionInvoker
{
    // Needed because FindAction is protected, and we are changing it to be public
    public new ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
    {
        return base.FindAction(controllerContext, controllerDescriptor, actionName);
    }
}

Then it is just a matter of using the same 2 objects you had before to call the new method.

var controllerDescriptor = new ReflectedControllerDescriptor(controllerType);
var actionSelector = new ActionSelector();
var actionDescriptor = 
    actionSelector.FindAction(controllerContext, controllerDescriptor, actionName) ??
    controllerDescriptor.GetCanonicalActions().FirstOrDefault(a => a.ActionName == actionName);

Note that this will work for both StandardRouteMethods and DirectRouteMethods.

However, one thing to be aware of is that calling this method will reset the RouteData for the controllerContext.RouteData and controllerContext.RequestContext.RouteData properties to that of the action that is found. So, you will need to wrap the ControllerContext and RequestContext classes and re-implement the RouteData properties so the setter has no effect if it is important that you don't destroy your context.

Reference: https://aspnetwebstack.codeplex.com/workitem/1989

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top