[Claudio Figueiredo] ASP.Net MVC ActionFilters

I’m a lame blogger, I know. There’s been more than two years that my friend Heynemann has been teasing me to post more, and it’s starting to happen more naturally these times. The reason that I blog less than I’d wish for is that I’m crazy about coding. That’s where I tend to focus most of my time, but I’ll keep posting more frequently now. The reasoning behind this introduction is to assure all my three readers (you know who you are lol), that my series on .Net MVC will be continuing soon.

Action Filters - Why and How?

Action Filters are .Net MVC native way of adding intercepting capabilities to your application. It’s a non-imperative way of adding some AOP to your Controller’s Actions without resorting to AOP or IoC frameworks.

It’s a sweet move on the part of the development team, and It’s something that makes SoC and extensibility in .Net MVC very easy to implement.

Well, since the first time I’ve read about it, I knew it would be a perfect fit to integrate with ValEver.

ValEver (short for Validation Everywhere) was one of the neatest pieces of code ever produced by Bernardo and I had the pleasure of developing its integration with Windsor Container some months ago.

I’ll try to give some examples here on how to use ActionFilters through implementing integration with ValEver and check on the benefits of this happy union. :D

ActionFilters provide a declarative way to access the executing context prior to, and following, the execution of an action.

It has a simple yet meaningful contract:

public abstract class ActionFilterAttribute : Attribute, IActionFilter
{
 
    // Methods
    protected ActionFilterAttribute();
    public virtual void OnActionExecuted(ActionExecutedContext filterContext);
    public virtual void OnActionExecuting(ActionExecutingContext filterContext);
    public virtual void OnResultExecuted(ResultExecutedContext filterContext);
    public virtual void OnResultExecuting(ResultExecutingContext filterContext);
 
    // Properties
    public int Order { get; set; }
}

Most methods are self explanatory but the important ones in what I’m trying to achieve are OnActionExecuting and the ActionExecutingContext.

That’s the Pre handler of a Controller’s Action call. Through it you can reach the Context and interact with it.

First thing I did, as any good developer should do, was a test. And so it came:

[ValEverActionFilter]
[ValidationShouldThrow]
public ActionResult Index([Required("Supply a valid parameter")]string parameter1) {
    return View();
}

The ValidationShouldThrow and Required(“Supply a valid parameter”)] attributes are native from ValEver. The first being a marker to control behaviour, the second being ‘The’ validation of a parameter.

And from this test came my custom ValEverActionFilter:

public class ValEverActionFilterAttribute : ActionFilterAttribute {
    public override void OnActionExecuting(ActionExecutingContext filterContext) {
        base.OnActionExecuting(filterContext);
    }

This small piece of code is all that’s needed. Besides the business logic, which will be added later, all I need are the parameter values . Interestingly we have them. The neat trick here is the context.

The ActionExecutingContext allows us to direct access to info such as …

//The calling Action
public MethodInfo ActionMethod { get; private set; }
 
//The parameters and it's values
public IDictionary<string, object> ActionParameters { get; private  set; }
 
//The request HttpContext 
public HttpContextBase HttpContext { get; internal set; }
 
//Any data regarding the Routing used in the call
public RouteData RouteData { get; internal set; }

… being able to manipulate almost anything inside the call’s context.

Then again, I settled to test. But this time I need an integrated test between the Controller Action and the Validation through the call of ValEverActionFilter.

It happens that only calling the Action is not enough. And it makes sense. The controller is not so special alone after all. The ActionFilter calling is done on the Handler instantiation on ASP.NET.
“- So, no problem. How hard could it be?” TM

After lot of googling I found how hard it was. There’s no out-of-the-box solution for this kind of test. So I set to make mine and it came as:

[Test]
public void ShouldThrowOnInvalidParameters() {
 
    TestingController controller = new TestingController();

 
    Assert.Throws<ValitationFailedException>(() => 
        controller.InvokeActionWithFilters<TestingController>(p 
            => p.Index(string.Empty)));
}

The InvokeActionWithFilters Extension Method mimics what the ASP.NET does. Supplying defaults and calling the ActionFilter’s hooked events in the right time with the right context.

Then I just had to write the business logic to my filter, and make tests go green.

public class ValEverActionFilterAttribute : ActionFilterAttribute {
    public override void OnActionExecuting(ActionExecutingContext filterContext) {
 
        var methodInfo = filterContext.ActionMethod;
 
        AddValidationParameters(filterContext, methodInfo);
 
        bool isValid = ValidationContext.Validate(filterContext.Controller) &&
                                     ValidationContext.ValidateArguments(methodInfo);
 
        ThrowIfRequiredByDecoration(isValid, methodInfo);
 
        base.OnActionExecuting(filterContext);
    }

Nothing more had to be done to have it interacting with the method call.
The filterContext.ActionMethod contains all the information needed. Parameters and it’s values too.

Conclusion

So, despite the little trouble testing the ActionFilters with the call’s context, it was easy extend and interact with one of the core aspect of a MVC framework and, better yet, keeping the concerns apart. Just some other cool things you can do with ActionFilters:

  • Cancel Action Execution
  • Interact with HttpContext
  • Override/fill parameters values
  • Set manually your own ActionResult

This opens so many possibilities that it makes me glad to see development going in this direction and delivering such good feats.

The full code of the sample application can be found here.

Cheers.