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()
    {
        // ...
    }
}

Comments
gravatar Jeremy D. Miller Tuesday, June 29, 2010
This whole issue gets a lot cleaner if they really do make it to a composition based framework in MVC3. In FubuMVC world, this issue is simpler. Write custom behaviors for things like logging, error handling, and transactions. Then you can happily express policies about what gets wrapped in *one* place like in this code sample: http://pastie.org/1023306.
gravatar scott Tuesday, June 29, 2010
@Jeremy - that is a *really* nice feature of FuBu.
gravatar Russell Tuesday, June 29, 2010
Nice! I actually would have loved to see this about 4 weeks ago before I created almost the same thing. I did not end up adding the Log attribute to a base controller simply because I wanted a little more control over which controller methods were logged.
gravatar Wes Wednesday, June 30, 2010
We created a more composable way of applying filters, basically the idea is to selectively match a filter to a given request. It's all injected via Windsor so there is no need to attribute any base controller or action methods. The results are here:

geekswithblogs.net/.../ijoined-filter.aspx

geekswithblogs.net/...

Fubu's approach is best as it wraps the action filters around the action in a decorator/proxy fashion, this allows using and try catch blocks around invocation of inner filters/the action.

I've been contemplating implementing a new filter method with ASPNet MVC that wraps invoking the action and result via interception, to provide a similar decorator/proxy pattern to Fubu's.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!