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