On All Things Web

Dec 14

ASP.NET MVC gives you a lot of options when you want to customize the way that ASP.NET MVC uses controllers.  By default, ASP.NET uses a controller factory to handle MVC controller requests.  A ControllerActionInvoker class, related to the controller, is solely responsible for invoking action method requests and returning the results.  It is this class where the action method attributes are executed (the pre/post execution actions and the pre/post result actions).  So both of these components are important classes.


In my applications, I like to customize both.  It’s easy to provide additional features for you to add on to.  The options for implementing a custom controller factory are:


  • Implement the IControllerFactory interface
  • Inherit from DefaultControllerFactory, the base controller factory class

The later is usually the best option, unless you are completely rewriting the controller factory framework.  By inheriting from this class, it makes it easy to add additional features.  For instance, if you want to use a custom action invoker, you can do this in the GetControllerInstance method by doing:


public class MyCF : DefaultControllerFactory
{
   private MyActionInvoker _inv = new MyActionInvoker();

   protected override IController GetControllerInstance(Type controllerType)
   {
       var ctlr = base.GetControllerInstance(controllerType);
       if (ctlr is Controller)
          ctlr.ActionInvoker = _inv;

       return ctlr;
   }
}


We’ll get to the action invoker later, but you can see GetControllerInstance opens it up for many things; for instance, you could use Unity or Castle to create a reference and assign any injected references into the controller itself, a popular topic lately because of MVC (although IOC/dependency injection has been a hot topic for a while, especially in the Java world, .NET seems to be a late adopter).  This can happen here, so instead of using teh base class method (which does a reflective search for the specified type in all of the referenced projects, the logic could be changed to do:


var ctlr = this.LoadFromDIContainer(controllerType);
//rest of code


So you see, MVC opens your application up to many extensibility points, which is another place where the action invoker comes into play.  The action invoker is responsible for invoking actions, of course.  But an action can fail, either due do a threading abort exception (redirection), or because the action method couldn’t be found. In any case, I like to wrap the action invoke with catch exceptions, instead of relying on HandleUnknownAction to do the work (which could also be another option by having all of your controller methods inherit from a common controller class, another extensibility point).  Our action invoker could do:


public class ActInv : ControllerActionInvoker
{
   protected override ActionResult InvokeAction(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
   {
      try
      {
         base.InvokeAction(controllerContext, actionDescriptor, parameters);
      }
     catch(ThreadAbortException aex)
     {
         //Log error specific to thread aborting
         throw;
     }
     catch(Exception ex)
     {
        //Log error
       throw
     }
   }
}


Here if an error occurs, logging occurs and the error is rethrown so the page fails.  Here we could also return a redirection to the error page to show a friendly error, or use an exception logger attribute to handle the error details.  Now, the key to this method is realizing this invokes on every action request.  So we could do things like loading user information if they are logged in (the <forms loginUrl=””> still takes care of unauthenticated users).  If the user information isn’t cached, we could load and cache it, then use it to process the request.  Any other code that you would run on every action execution can be put here too.


If you need to get more granular, you can override the InvokeActionMethod (executing the method itself; the result doesn’t get returned directly here, but is stored internally) and InvokeActionResult (this will actually return the result of the action method).  The latter method gives you direct access to the view or view result to do as you see fit.  Since you have access to the view, and if you use a custom view, you can pass along any information to that view that you may need.


To use this controller factory, pass along a reference as shown below.  This will replace the default with our new factory.


ControllerBuilder.Current.SetControllerFactory(new MyCF());


I hope I passed a long a few important tidbits about the MVC controller system, which is pretty powerful in its capabilities.

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>