ASP.NET MVC ActionFilter: Enhance your Controllers with ActionFilters

Don't pepper your HttpContext all over the place. ActionFilters are used to contain any kind of HttpContext. In this post, we'll talk about how to keep your HttpContext in check using ActionFilters with examples.

Written by Jonathan "JD" Danylko • Last Updated: • MVC •
An ActionFilter

How many times have you used the HttpContext in your web applications?

Now, how many times have you used it spread over your entire web applications? I've always had issues with using HttpContext all over a web site. 

I guess the thing that kills me is that for every request, it asks for 30K. With the new ASP.NET vNext, HttpContext will be a svelt 2K.

But I digress.

This is why I like the ActionFilters. It's an easy way to keep HttpContext contained in one location.

What are they?

ActionFilters are strictly attributes used to decorate your controllers and encapsulate HttpContext logic into one neat package.

If you need to check cookies, use ActionFilters. If you want to change the output of your HTML, use an ActionFilter.

One example of an ActionFilter is the AuthorizeAttribute.

[Authorize]
public ActionResult Index(PagingModel model)
{
    return View(_factory.GetViewModel<FaqController, FaqViewModel>(this));
}

You can attach this to a Controller or an Action. If someone tried to access a particular action, it would redirect them to the login page.

Now that we have an understanding of what ActionFilters are, let's get into some code to see some of their capabilities.

Squish the Spaces!

Nowadays, it's all about speed, am I right? When you are sending HTML pages down the wire, most HTML pages have all of those darn pesky spaces.

Let's make a Whitespace extractor.

ActionFilters\WhiteSpaceFilter.cs

using System.Web.Mvc;
namespace ThinController.ActionFilters
{
    public class WhitespaceFilterAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            var response = filterContext.HttpContext.Response;

            if (response.ContentType != "text/html" || response.Filter == null) return;
            response.Filter = new WhitespaceStream(response.Filter);         }     } }

ActionFilters\WhitespaceStream.cs

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;

namespace ThinController.ActionFilters {     public class WhitespaceStream : Stream     {         private readonly Stream _base;         private StringBuilder _s = new StringBuilder();
        public WhitespaceStream(Stream responseStream)         {             if (responseStream == null)                 throw new ArgumentNullException("WhitespaceStream.ResponseStream is null.");             _base = responseStream;         }
        public override void Write(byte[] buffer, int offset, int count)         {             var html = Encoding.UTF8.GetString(buffer, offset, count);
            var reg = new Regex(@"(?<=\s)\s+(?![^<>]*</pre>)");             html = reg.Replace(html, string.Empty);
            buffer = Encoding.UTF8.GetBytes(html);             this._base.Write(buffer, 0, buffer.Length);         }
        #region Default Members
        public override int Read(byte[] buffer, int offset, int count)         {             return _base.Read(buffer, offset, count);         }
        public override bool CanRead         {             get { return false; }         }
        public override bool CanSeek         {             get { return false; }         }

        public override bool CanWrite         {             get { return true; }         }
        public override long Length         {             get { return _base.Length; }         }
        public override long Position { get; set; }
        public override void Flush()         {             _base.Flush();         }
        public override long Seek(long offset, SeekOrigin origin)         {             return _base.Seek(offset, origin);         }
        public override void SetLength(long value)         {             _base.SetLength(value);         }
        #endregion     } }

Every ActionFilter has an OnActionExecuted (after the event happened) and an OnActionExecuting (before the action happens) event. In the WhiteSpaceFilter class, we focus on the OnActionExecuted because we want to remove the spaces from the outgoing HTML stream.

In the WhitespaceStream class, everything is pretty standard in the line of stream-y stuff, but the meat of the code is in the Write method. Most of the action usually happens in the Write method.

First, we get the string from the buffer, use a RegEx to remove spaces from the string, and then write the string back out to the stream. Pretty cut-and-dry.

Conclusion

When implementing ActionFilters, they are strictly for analyzing the Mvc pipeline and allow you to "inject" logic code into the HTML stream for rendering to the browser.

I realize this was a short post today, but I hope I've shown you how ActionFilters can go a long way by compartmentalizing your HttpContext (keeping it contained) from being scattered all around your application.

What kind of ActionFilters have you created? Are they similar to the AuthorizeAttribute? Post your comments below.

ASP.NET 8 Best Practices on Amazon

ASP.NET 8 Best Practices by Jonathan Danylko


Reviewed as a "comprehensive guide" and a "roadmap to excellence" with over 120 Best Practices for ASP.NET Core 8, Jonathan's first book by Packt Publishing explores proven techniques for every phase of the SDLC.

Learn industry-standard concepts to improve your coding, debugging, and deployment of ASP.NET Core websites.

Order now on Amazon.com button

Picture of Jonathan "JD" Danylko

Jonathan "JD" Danylko is an author, web architect, and entrepreneur who's been programming for over 30 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus