OdeToCode IC Logo

Action Filter versus Controller Base Class

Monday, June 28, 2010

One of the features (or challenges) with ASP.NET MVC is the number of approaches you can use to successfully implement a feature. Take the "log each action method" requirement. One possible approach is to use a custom action invoker, however, an action invoker already has enough responsibilities (see Jonathon's post), and also has a fairly low level API. A custom action filter is the better choice.

public class LogAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // ... log stuff before execution            
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        // ... log stuff after execution
    }
}

You can use the LogAttribute on every controller.

[Log]
public class VuvuzelaController
{
    public ActionResult Detonate()
    {
        // ...
    }
}

After you've added the attribute to a few controllers, you might start wondering why you don't use a base class instead. After all, the Controller class defines virtual methods to hook into the pre and post action execution, and with this approach you won't see the [Log] attribute on every controller.

public class LoggingController : Controller
{
    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // ... log stuff before execution               
    }

    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        // ... log stuff after execution
    }
}

Now everyone just needs to use LoggingController as a base class.

public class VuvuzelaController : LoggingController
{
    public ActionResult Detonate()
    {
        // ...
    }
}

Wait! What if someone forgets to use the base class? This problem is easy to prevent - we'll look at the details in a future post (think unit test with LINQ queries and reflection).

The bigger problem is all the design issues a base class brings into play. What if someone else overrides the methods and forgets to call the base implementation? What if someone wants to manage transactions and ORM sessions in a base controller? If you follow the pattern you either end up with a fragile class hierarchy or a SessionInitializingTransactionMonitoringLoggingCachingSuperDuperController base class.

Fortunately, ASP.NET MVC finds and respects the action filters on a base controller class. You can add additional functionality without adding implementation.

[Log]
public class FootballController : Controller
{
    
}

Now every controller derived from FootballController will log.

public class VuvuzelaController : FootballController
{
    public ActionResult Detonate()
    {
        // ...
    }
}