Search code examples
c#asp.net-mvc-3reflectionhtml-helper

How to retrieve attributes of requested Action Method


I'm trying to peek at any authentication attributes which may be decorating action methods in my controllers in an MVC 3 application. I'm doing this in my own HtmlHelper extension methods which are basically wrappers to ActionLink (to give you the context of what information I have available at runtime). I have a basic solution in place, but overloaded methods have just made it explode. I know that the framework is internally resolving urls to action methods, but after looking through the code for System.Web.Mvc.LinkExtensions, I still haven't found precisely how that is happening, so I'm a bit stuck as to how to tackle this issue.

Here's the code I have so far for resolving the relevant method:

private static bool _IsUserAuthorized(HtmlHelper html,
  string controllerName, string actionName)
{
  controllerName = controllerName ??
    html.ViewContext.RouteData.GetRequiredString("controller");

  var factory = ControllerBuilder.Current.GetControllerFactory();
  var controller = factory.CreateController(
    html.ViewContext.RequestContext, controllerName);

  Type controllerType = controller.GetType();
  var methodInfo = controllerType.GetMethod(actionName,
    BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);

  ... check authentication
}

So my current problem is that when a method is overridden, I get "ambiguous match found" exceptions. I'm guessing I need to process the RouteValues to resolve any parameters to the method so I can unambiguously identify the right one. Does anyone have some pointers on how to do this? Alternately, does the framework already provide a means of resolving the exact method needed?

Thanks so much!


Solution

  • EDIT: Updated the method to include insights from this page. This final version peeks the AuthorizationFilters for the requested action method and checks whether the user is authorized to perform the action.

    So I dug around in System.Web.Mvc.ControllerActionInvoker and found the methods and constructors I needed.
    ControllerDescriptor.FindAction() ended up being the key. Below, I've copied the method I wrote that will retrieve all the attributes:

    private static bool _IsUserAuthorized(HtmlHelper htmlHelper,
                                          string controllerName,
                                          string actionName)
    {
        ControllerContext controllerContext = null;
        
        // if controllerName is null or empty,
        // we'll use the current controller in HtmlHelper.ViewContext.
        if (string.IsNullOrEmpty(controllerName))
        {
            controllerContext = htmlHelper.ViewContext.Controller.ControllerContext;
        } else {  // use the controller factory to get the requested controller
            var factory = ControllerBuilder.Current.GetControllerFactory();
            
            var controller = (ControllerBase)factory.CreateController(
                htmlHelper.ViewContext.RequestContext, controllerName);
                
            controllerContext = new ControllerContext(
                htmlHelper.ViewContext.RequestContext, controller);
        }
        
        var controllerType = controllerContext.Controller.GetType();
        var controllerDescriptor = new ReflectedControllerDescriptor(controllerType);
        var actionDescriptor = controllerDescriptor.FindAction(
            controllerContext, actionName);
        
        if (actionDescriptor == null)
        {
            return false;
        }
        
        var filters = new FilterInfo(
            FilterProviders.Providers.GetFilters(controllerContext, actionDescriptor)
        );
        
        var authContext = new AuthorizationContext(controllerContext, actionDescriptor);
        foreach (IAuthorizationFilter authFilter in filters.AuthorizationFilters)
        {
            authFilter.OnAuthorization(authContext);
            if (authContext.Result != null)
            {
                return false;
            }
        }
        
        return true;
    }