Archive for July 3rd, 2008

03
Jul

ASP.NET MVC - ControllerActionInvoker: Part 1

Whenever I give a session on ASP.NET MVC I always make a point to highlight the extensibility pipeline and how easily you can replace the default behavior/conventions with just about anything you can imagine (you should be happy with your code right?). I tend to focus on controller factories, action results, action filters, and view engines as the primary candidates for extension, because those enable a whole slew of fairly common scenarios for developers. But I’d hate to feel like I’m playing favorites…

While I was in Sydney last month, one of the attendees at an ASP.NET MVC session I was giving asked me when and why you would ever create your own custom ControllerActionInvoker, which I thought was a great question, and one that I foolishly haven’t been discussing. I’d hate to feel like I’m skirting over any interesting details, so I’m going to put together a couple of blog posts to showcase creating a custom ControllerActionInvoker.

The first thing to tackle when thinking about reasons to extend the ControllerActionInvoker class is to understand what exactly its role is in the overall request execution process. This diagram shows a decent (emphasis on decent) illustration of where the invoker sits in the lifecycle:

img-07-03-2008.png

Notice that the invoker comes into the picture based on its association with the Controller. By this, I specifically mean the Controller class, so if you create your own IController implementation, the ControllerActionInvoker isn’t in the picture anymore (unless you leverage it from your implementation as well). When a controller’s Execute method is called (by the routing engine), the controller grabs its invoker and passes the request on to it. So it isn’t actually the Controller class that executes its own actions, it is its invoker that does. This may sound a little strange, but it makes sense as you dig deeper…

Once the invoker has received “focus” from the controller, it essentially has five tasks that it attempts to perform, all of which represent a possible point of extension, and can be used to enable some pretty interesting stuff:

  1. Retrieve a MethodInfo that represents the requested action method
  2. Determine where to retrieve values for the action method’s parameters (if any exist)
  3. Get a list of all action filters associated with the action method
  4. Execute the action method (along with any filters)
  5. Execute the action result (along with any filters)

So how does this process play out in the default invoker implementation? If the controller’s ActionInvoker property isn’t specifically set, it news up an instance of the ControllerActionInvoker class, and hands it the reigns. It’s behavior aligns to the above five steps as follows:

  1. The invoker reflects against its parent controller, looking for a public method with the exact name of the requested action. The method can’t be static, have generic parameters, or be salted with the NonAction attribute.
  2. The invoker looks to see if the method has any parameters (can’t be out/ref) and if so, looks for values with the same name as the parameter in the following locations (in this order):
    1. Route value
    2. Query String
    3. Form
    4. Cookie
    5. Server variable
  3. The invoker looks for filters that are represented as attributes that were appended to the action method (and inherit from ActionFilterAttribute), as well as its parent controller
  4. This is a three step process:
    1. Call the OnActionExecuting method of all action filters associated with the action
    2. Execute the actual action method (capturing the returned ActionResult for later use)
    3. Call the OnActionExecuted method of all action filters associated with the action
  5. This is a three step process:
    1. Call the OnResultExecuting method of all action filters associated with the action
    2. Call the ActionResult’s ExecuteResult method
    3. Call the OnResultExecuted method of all action filters associated with the action

If we determine that we want to replace one of those steps with our own behavior, how can we go about doing that? All we have to do is subclass ControllerActionInvoker, and assign an instance of our invoker to the controller’s ActionInvoker property, so that it uses our implementation instead of the default. When deciding how we’d like to inject our custom invoker, we have two choices:

  1. Create a custom controller factory
  2. Create a custom controller

If you decide to create a custom controller factory, the easiest path to take would be to create a class that inherits from DefaultControllerFactory (provides a lot of useful functionality on top of IControllerFactory), and override its GetControllerInstance method, where you would create an instance of your invoker, and inject it into the created controller, like so:

public class CustomFactory : DefaultControllerFactory 
{ 
    protected override IController GetControllerInstance(Type controllerType) 
    { 
        var controller = (Controller)base.GetControllerInstance(controllerType); 
        controller.ActionInvoker =
            new CustomInvoker(new ControllerContext(base.RequestContext, controller)); 
        return controller; 
    } 
}

Then you would inject your custom controller factory into the pipeline by letting the ControllerBuilder know you want to use it, by putting the following code in application start in your Global.asax file:

ControllerBuilder.Current.SetControllerFactory(typeof(CustomFactory));

This approach works great if you want all controllers in your web application to automatically receive the custom invoker. But, maybe you want your controllers to have to “opt-in” to leveraging the custom invoker, which is when creating a custom controller might work better, because then your controllers would have to specifically inherit from the custom controller class in order to receive the custom invoker.

Because the invoker relies on ControllerContext, which isn’t provided to the Controller class until its Execute method is called, if you choose to subclass the Controller class, you can simply override its Execute method. This allows you to new up an instance of our custom invoker, leveraging the provided ControllerContext, like so:

public class CustomController : Controller 
{ 
    protected override void Execute(ControllerContext controllerContext) 
    { 
        ActionInvoker = new CustomInvoker(controllerContext); 
        base.Execute(controllerContext); 
    } 
}

Now when I create a new controller, instead of inheriting from the Controller class, I can just inherit from CustomController, and benefit from my custom invoker:

public class HomeController : CustomController 
{ 
	// Action methods... 
}

Now that we’ve discussed what responsibilities the ControllerActionInvoker actually has, and where it rests in the pipeline, in the next few blog posts we’ll see what kind of scenarios we can enable by replacing some of the default behavior with our own.