14

How can I get the controller action (method) and controller type that will be called, given the System.Web.Routing.RouteData?

My scenario is this - I want to be able to do perform certain actions (or not) in the OnActionExecuting method for an action.

However, I will often want to know not the current action, but the "root" action being called; by this I mean I may have a view called "Login", which is my login page. This view may include another partial view "LeftNav". When OnActionExecuting is called for LeftNav, I want to be able to determine that it is really being called for the "root" aciton of Login.

I realise that by calling RouteTable.Routes.GetRouteData(actionExecutingContext.HttpContext), I can get the route for the "root" request, but how to turn this into method and type info?

The only solution I have so far, is something like:

 var routeData = RouteTable.Routes.GetRouteData(actionExecutingContext.HttpContext)
 var routeController = (string)routeData.Values["controller"]; 
 var routeAction = (string)routeData.Values["action"];

The problem with this is that "routeController" is the controller name with the "Controller" suffix removed, and is not fully qualified; ie it is "Login", rather than "MyCode.Website.LoginController".

I would far rather get an actual Type and MethodInfo if possible, or at least a fully qualified type name.

Any thoughts, or alternative approaches?

[EDIT - this is ASP.Net MVC 1.0]

Rob Levine
  • 40,328
  • 13
  • 85
  • 111

4 Answers4

7
  protected override void OnActionExecuting(ActionExecutingContext filterContext)
  {
     var type1 = filterContext.Controller.GetType();
     var type2 = filterContext.ActionDescriptor
                    .ControllerDescriptor.ControllerType;
  }

OK, sorry, I missed the "root" part.

Then, another way, you can save controller type to thread storage. Pseudocode:

  protected override void OnActionExecuting(ActionExecutingContext filterContext)
  {
     if (!Thread.LocalStorage.Contains("root_controller"))
        Thread.LocalStorage["root_controller"] = 
            filterContext.ActionDescriptor
                    .ControllerDescriptor.ControllerType;
  }

Just an idea. I'm sure thread local storage is available in C#. The key idea here is that you save it only for first request, thus it's always root controller.

queen3
  • 15,333
  • 8
  • 64
  • 119
  • Unfortunately, this won't work as it returns the type of the *current* controller action, not the "root" controller action in the example I gave. The reason I call GetRouteData(actionExecutingContext.HttpContext) is because this does succesfully give me the route for the "root", but I can't then translate this into the controller type and method. – Rob Levine Apr 22 '10 at 14:52
  • 1
    Don't use ThreadLocal storage from within an ASP.NET application. ASP.NET requests can jump around between threads, so ThreadLocal storage can disappear when you don't expect it. Use HttpContext.Items instead if you need to store information that sticks around only for the current request. – Levi Apr 22 '10 at 16:46
  • Actually I just was not aware of any ASP.NET collection that stores data only for current request. Sure it's better than thread storage. Thanks for the tip. – queen3 Apr 22 '10 at 19:14
4

Here is the solution I compiled from various sources. The url variable should contain the URL of the action:

        url = "YOUR URL";
        // Original path is stored and will be rewritten in the end
        var httpContext = new HttpContextWrapper(HttpContext.Current);
        string originalPath = httpContext.Request.Path;

        try
        {
            // Fake a request to the supplied URL into the routing system
            httpContext.RewritePath(url);
            RouteData urlRouteData = RouteTable.Routes.GetRouteData(httpContext);

            // If the route data was not found (e.g url leads to another site) then authorization is denied.
            // If you want to have a navigation to a different site, don't use AuthorizationMenu
            if(urlRouteData != null)
            {
                string controllerName = urlRouteData.Values["controller"].ToString();
                string actionName = urlRouteData.Values["action"].ToString();

                // Get an instance of the controller that would handle this route
                var requestContext = new RequestContext(httpContext, urlRouteData);
                var controllerFactory = ControllerBuilder.Current.GetControllerFactory();
                var controller = (ControllerBase) controllerFactory.CreateController(requestContext, controllerName);

                // Find the action descriptor
                var controllerContext = new ControllerContext(httpContext, new RouteData(), controller);
                var controllerDescriptor = new ReflectedControllerDescriptor(controller.GetType());
                var actionDescriptor = controllerDescriptor.FindAction(controllerContext, actionName);
            }
        }
        finally
        {
            // Reset our request path.
            httpContext.RewritePath(originalPath);
        }
VitalyB
  • 12,397
  • 9
  • 72
  • 94
2
public Type ControllerType(string controllerName)
{
   var fullName = controllerName + "Controller";
   var assemblyName = Assembly.GetExecutingAssembly().FullName;
   return Activator.CreateInstance(assemblyName, fullTypeName).GetType();
}

public MethodInfo ActionMethodInfo(string actionName, Type controllerType)
{
   return controllerType.GetMethod(actionName);
}

Are you thinking of an implementation similar to this? Some Try/Catches required!

halfer
  • 19,824
  • 17
  • 99
  • 186
Daniel Elliott
  • 22,647
  • 10
  • 64
  • 82
  • Thanks Dan - that is kinda where I'm headed, but really hoping to avoid. Underneath somewhere, MVC knows exactly what method, on what type, it is going to call. The problem with this approach (and mine) is that I'm having to append the suffix "Controller", and will also have to prepend the namespace to the type, etc, etc. It would probably work, but if I can hook into what MVC is doing already, I'd rather do it that way. – Rob Levine Apr 22 '10 at 12:59
  • GetMethod() will throw AmbiguousMatchException if there are multiple methods with the same name, a very common pattern. Typically you must consider matches related to the other values in RouteData, as well, in order to select the right action method. – Suncat2000 Nov 20 '19 at 19:27
0

MvcSiteMapProvider does this. Here is the code for this particular thing.

Here is the code

Orlando Osorio
  • 3,116
  • 7
  • 29
  • 52
rodmjay
  • 549
  • 4
  • 15