7

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.

Community
  • 1
  • 1
Miguel Angelo
  • 23,796
  • 16
  • 59
  • 82

4 Answers4

11

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!

texclayton
  • 189
  • 1
  • 4
Miguel Angelo
  • 23,796
  • 16
  • 59
  • 82
3

You can achieve this functionality by using the AuthorizeAttribute. You can get the Controller and Action name in OnAuthorization method. PLease find sample code below.

 public sealed class AuthorizationFilterAttribute : AuthorizeAttribute
    {
        /// <summary>
        /// Use for validate user permission and  when it also validate user session is active.
        /// </summary>
        /// <param name="filterContext">Filter Context.</param>
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            string actionName = filterContext.ActionDescriptor.ActionName;
            string controller = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
            if (!IsUserHasPermission(controller, actionName))
            {
               // Do your required opeation
            }
        }
    }
alok_dida
  • 1,723
  • 2
  • 17
  • 36
  • More information is needed. There may be multiple actions with the same name, but responding to different parameters. – Suncat2000 Nov 20 '19 at 19:41
  • Are you talking about the route parameters? – alok_dida Nov 20 '19 at 22:49
  • I'm talking about the method parameters. Multiple action methods may have the same name but have different parameters. If the model binder(s) can match the parameters, the correct method will be called. The different methods will still have the same controller name and action name; with multiple methods, your code would be incomplete. – Suncat2000 Dec 16 '19 at 22:08
0

if you have a default route configured like

routes.MapRoute(
        "Area",
        "",
        new { area = "MyArea", controller = "Home", action = "MyAction" }
    );

you can get the route information inside the controller action like

ht tp://localhost/Admin

will give you

public ActionResult MyAction(string area, string controller, string action)
{
 //area=Admin
 //controller=Home
 //action=MyAction
 //also you can use RouteValues to get the route information
}

here is a great blog post and a utility by Phil Haack RouteDebugger 2.0

Rafay
  • 30,950
  • 5
  • 68
  • 101
0

This is a short notice! Be sure to use filterContext.RouteData.DataTokens["area"]; instead of filterContext.RouteData.Values["area"];

Good Luck.

QMaster
  • 3,743
  • 3
  • 43
  • 56