Ronnie Overby
Ronnie Overby

Reputation: 46470

ASP.NET MVC 3 : How to turn determine Controller Action from Url

Surprised I'm not finding this answer anywhere, how can I determine what Controller/Action will be invoked for a given URL in MVC 3?

Update

What I really want to know: "how can I determine what ControllerAction will be invoked for a given URL in MVC 3?" ....yeah

So, either I'm not aware of the magic method that does this:

ControllerActionInfo GetControllerActionInfo(string url)

Or, I will have to create it myself doing whatever MVC does when it gets an http request.

My purpose of asking about this on StackOverflow is that I can save some time reverse engineering this behavior. The correct answer should resemble:

Here's how you can do it: and some code would follow.

Upvotes: 2

Views: 1743

Answers (2)

m3kh
m3kh

Reputation: 7941

You have to use a dummy HttpContext and HttpRequest classes as follows:

public class DummyHttpRequest : HttpRequestBase {

    private string mUrl;

    public DummyHttpRequest(string url) {
        mUrl = url;
    }

    public override string AppRelativeCurrentExecutionFilePath {
        get {
            return mUrl;
        }
    }

    public override string PathInfo {
        get {
            return string.Empty;
        }
    }

}

public class DummyHttpContext : HttpContextBase {

    private string mUrl;

    public DummyHttpContext(string url) {
        mUrl = url;
    }

    public override HttpRequestBase Request {
        get {
            return new DummyHttpRequest(mUrl);
        }
    }

}

Edit: Also, you can extend the DefaultControllerFactory and add a simple method to get the desired information instead of an instance of Controller. (Note: It's merely a sample, you have to support other aspects like ActionNameAttribute and so on)

public class ControllerActionInfo {

    public ControllerActionInfo(Type controllerType, MethodInfo action) {
        ControllerType = controllerType;
        Action = action;
    }

    public Type ControllerType { get; private set; }
    public MethodInfo Action { get; private set; }

}

public class DefaultControllerFactoryEx : DefaultControllerFactory {

    public ControllerActionInfo GetInfo(RequestContext requestContext, string controllerName) {
        Type controllerType = GetControllerType(requestContext, controllerName);

        if (controllerType == null) {
            return null;
        }

        MethodInfo actionMethod = controllerType.GetMethod(requestContext.RouteData.GetRequiredString("action"), BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Public);

        return new ControllerActionInfo(controllerType, actionMethod);
    }

}

Then, use following code snippet to get access to the controller:

DummyHttpContext httpContext = new DummyHttpContext("~/home/index");
RouteData routeData = RouteTable.Routes.GetRouteData(httpContext);
// IController controller = new DefaultControllerFactory().CreateController(new RequestContext(httpContext, routeData), routeData.GetRequiredString("controller"));
DefaultControllerFactoryEx controllerFactory = new DefaultControllerFactoryEx();

var result = controllerFactory.GetInfo(new RequestContext(httpContext, routeData), routeData.GetRequiredString("controller"));

Upvotes: 1

smartcaveman
smartcaveman

Reputation: 42246

The logic for this is in the System.Web.Mvc.MvcHandler class, the System.Web.Mvc.DefaultControllerFactory class, and the System.Web.Mvc.ControllerActionInvoker class. .NET Reflector is your friend.

Basically, the MVC framework:

  1. Uses reflection to get all the controllers in the application project.

  2. Then it does something like IEnumerable<string> controllerNames = controllerTypes.Select(controllerType => controllerType.Name.Replace("Controller",string.Empty));. It then tries to match the first path segment, {controller}, to one of these sanitized controller type names (case-insensitive).

  3. Then, it looks at this controller's public methods that have a return type that is of type ActionResult or some derivative. It matches the method name to the second path segment, {action}, as the action method to be called.

  4. If the selected method has a parameter that is named id, then it matches the third path segment {id} to that value, and passes it to the method. Otherwise, the optional id parameter is ignored.

  5. If the ActionResult type that is returned is a derivative of ViewResultBase then the IViewEngine tries to locate a corresponding view in the project using whatever conventions have been specified for that view engine. The WebFormViewEngine, for example, looks in the project for ~/Views/{controller}/{action}.ascx, ~/Views/{controller}/{action}.aspx, ~/Views/Shared/{action}.ascx, ~/Views/Shared/{action}.aspx by default.

Upvotes: 0

Related Questions