The Controller’s ActionInvoker property everything a property could desire: strategy and laziness. It did its job so well that I never even paid attention to it. That one has been around since ASP.NET MVC 1. I noticed earlier today a new method, CreateActionInvoker(), on the Controller class in ASP.NET MVC 2. Intrigued by the new method, I began playing around with ActionInvokers and learned an ASP.NET MVC design lesson.
Have you ever done something like this?
public class MyController : Controller
{
public string Message()
{
return "Hello World!";
}
}
Okay, showing “Hello World!” is rather goofy. The important question is this: have you ever added an action to a controller, but didn’t return an ActionResult? In this “Hello World!” example, I’m returning a string. What that method should look like, if you want to be clear about what is happening, is this.
public ActionResult Message()
{
return Content("Hello World!");
}
One could argue that it isn’t important that it returns an ActionResult, or that you call Content() to format it to a proper ContentResult. In fact, it’s much easier to test if we stick to simple methods, and becomes more configurable. I think the design lesson is this: only hard code an ActionResult if that action must always return such. Otherwise, stick to simple methods as it gives you much more flexibility.
The key in all of this lies with the ActionInvoker property of the Controller. It is an IActionInvoker, and by default it is assigned a ControllerActionInvoker. When an action is called that doesn’t return an ActionResult, the ActionInvoker is called and it converts the value to an ActionResult. In the case of the ControllerActionInvoker, that value will be a ContentResult.
This uses the strategy pattern, so you can assign your own invoker if you want your controller to say, return a JsonResult when an ActionResult isn’t specified.
public class JsonControllerActionInvoker : ControllerActionInvoker
{
protected override ActionResult CreateActionResult(ControllerContext controllerContext,
ActionDescriptor actionDescriptor, object actionReturnValue)
{
if (actionReturnValue == null)
{
return new EmptyResult();
}
return (actionReturnValue as ActionResult) ?? new JsonResult { Data = actionReturnValue };
}
}
In the constructor of your controller you can assign an instance of the new invoker.
public HomeController()
{
ActionInvoker = new JsonControllerActionInvoker();
}
Everything is good so far, because as I said, the ActionInvoker is lazy. Since it hasn’t been called, it hasn’t created an instance of the other Invoker. Let’s take a look at the reflected code.
public IActionInvoker ActionInvoker
{
get
{
if (this._actionInvoker == null)
{
this._actionInvoker = new ControllerActionInvoker();
}
return this._actionInvoker;
}
set
{
this._actionInvoker = value;
}
}
That looks perfectly reasonable. So here’s the only scenario I can think of where this fails: you have weird methods that null out the ActionInvoker, but your Controller is still around. So when the action gets called, the default Invoker is created; oh noes!
In ASP.NET MVC 2, they changed this implementation. I have no idea what the real scenario was that caused this, but here it is.
public IActionInvoker ActionInvoker
{
get
{
if (this._actionInvoker == null)
{
this._actionInvoker = this.CreateActionInvoker();
}
return this._actionInvoker;
}
set
{
this._actionInvoker = value;
}
}
There’s the method, CreateActionInvoker, that made me look into this. Presumably, instead of assigning strategy in your constructor (perhaps even from configuration), you will override CreateActionInvoker() and return the one you want.
protected override IActionInvoker CreateActionInvoker()
{
return new JsonControllerActionInvoker();
}
Personally, I wish they would take out that method before the final release. It’s mostly useless, and I can’t think of any reason why the strategy pattern would fail and require the use of a virtual method instead. Any idea?