Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get the MethodInfo of an action, given action, controller and area names?

What I have is the following extension method:

public MyCustomAttribute[] GetActionAttributes(
    this Controller @this,
    string action,
    string controller,
    string area,
    string method)
{
}

How does ASP.NET MVC 3 find the action method, given the area, controller, action names and the method (GET, POST)?

To this moment I have nothing... no clues on how to do this.

I am currently looking for the stack trace inside a controller action, to find out how MVC dicovered it.

Why I need these attributes

My attributes contain information about whether a given user can or not access it... but depending on whether they can or not access it, I wan't to show or hide some html fields, links, and other things that could call that action.

Other uses

I have thought of using this to place an attribute over an action, that tells the css class of the link that will be rendered to call it... and some other UI hints... and then build an HtmlHelper that will render that link, looking at these attributes.

Not a duplicate

Yes, some will say this is possibly a duplicate of this question... that does not have the answer I want:

How can i get the MethodInfo of the controller action that will get called given a request?

That's why I have specified the circumstances of my question.

like image 438
Miguel Angelo Avatar asked Oct 24 '12 03:10

Miguel Angelo


1 Answers

I have looked inside MVC 3 source code, and tested with MVC 4, and discovered how to do it. I have tagged the question wrong... it is not for MVC 3, I am using MVC 4. Though, as I could find a solution looking at MVC 3 code, then it may work with MVC 3 too.

At the end... I hope this is worth 5 hours of exploration, with a lot trials and errors.

Works with

  • MVC 3 (I think)
  • MVC 4 (tested)

Drawbacks of my solution

Unfortunately, this solution is quite complex, and dependent on things that I don't like very much:

  • static object ControllerBuilder.Current (very bad for unit testing)
  • a lot of classes from MVC (high coupling is always bad)
  • not universal (it works with MVC 3 default objects, but may not work with other implementations derived from MVC... e.g. derived MvcHandler, custom IControllerFactory, and so on ...)
  • internals dependency (depends on specific aspects of MVC 3, (MVC 4 behaves like this too) may be MVC 5 is different... e.g. I know that RouteData object is not used to find the controller type, so I simply use stub RouteData objects)
  • mocks of complex objects to pass data (I needed to mock HttpContextWrapper and HttpRequestWrapper in order to set the http method to be POST or GET... these pretty simple values comes from complex objects (oh god! =\ ))

The code

public static Attribute[] GetAttributes(
    this Controller @this,
    string action = null,
    string controller = null,
    string method = "GET")
{
    var actionName = action
        ?? @this.RouteData.GetRequiredString("action");

    var controllerName = controller
        ?? @this.RouteData.GetRequiredString("controller");

    var controllerFactory = ControllerBuilder.Current
        .GetControllerFactory();

    var controllerContext = @this.ControllerContext;

    var otherController = (ControllerBase)controllerFactory
        .CreateController(
            new RequestContext(controllerContext.HttpContext, new RouteData()),
            controllerName);

    var controllerDescriptor = new ReflectedControllerDescriptor(
        otherController.GetType());

    var controllerContext2 = new ControllerContext(
        new MockHttpContextWrapper(
            controllerContext.HttpContext.ApplicationInstance.Context,
            method),
        new RouteData(),
        otherController);

    var actionDescriptor = controllerDescriptor
        .FindAction(controllerContext2, actionName);

    var attributes = actionDescriptor.GetCustomAttributes(true)
        .Cast<Attribute>()
        .ToArray();

    return attributes;
}

EDIT

Forgot the mocked classes

class MockHttpContextWrapper : HttpContextWrapper
{
    public MockHttpContextWrapper(HttpContext httpContext, string method)
        : base(httpContext)
    {
        this.request = new MockHttpRequestWrapper(httpContext.Request, method);
    }

    private readonly HttpRequestBase request;
    public override HttpRequestBase Request
    {
        get { return request; }
    }

    class MockHttpRequestWrapper : HttpRequestWrapper
    {
        public MockHttpRequestWrapper(HttpRequest httpRequest, string httpMethod)
            : base(httpRequest)
        {
            this.httpMethod = httpMethod;
        }

        private readonly string httpMethod;
        public override string HttpMethod
        {
            get { return httpMethod; }
        }
    }
}

Hope all of this helps someone...

Happy coding for everybody!

like image 92
Miguel Angelo Avatar answered Sep 27 '22 21:09

Miguel Angelo