Saturday 6 October 2012

On Dynamic Compression in MVC Applications

This week I was working on a Compression Action Filter that can be safely added to the Global Action Filter list for your MVC Application. By Compressing the Action Results from my Application I save considerable amounts of bandwidth and significantly improve page load times for my users at a small computational overhead at the Server.

I thought I might take a moment to pick apart my code to explain some of the problems with applying an Action Filter of this type at the Global level, but also why once you have an Action Filter you can use there its much better than something more naive. Forgive the poor formatting of my code snippets, I am fairly new to Blogger and can't see how to do these more appropriately.

We can start with the reasons for wanting this to be a Global Action Filter - if they were not already obvious -. Originally, I was using this Action Filter on a per Controller or Action basis, this meant quite a considerable amount of overhead was heaped onto my work load in order to maintain this Action Filters use. Unfortunately, the original version of the Action Filter did not take into account a number of scenarios which made it a bad candidate to be applied at the Global level, and so began the refactor.

I structured the code as a series of conditional bailouts, as this code executes on every request I wanted to make it quick to bail and easy to see what was going on should someone need to fix it.

The Action Filter currently supports ViewResult, PartialViewResult, JsonResult and ContentResult. The idea here is that you will want to support a number of different types of Action Result in your Application, but that you will not want to be trying to Compress FileResults for example as that is just going to cause unnecessary overhead.

if(!SUPPORTED_RESULTS.Contains(filterContext.Result.GetType())) return;

The Action Filter will not try to support Child Actions, this allows me to use RenderAction in my Views without having to worry about the content being Compressed twice and coming out as gobbledeguck. The same rule applies for Exceptions happening on the Action Result.

if (filterContext.IsChildAction) return;          
if (filterContext.Exception != null) return;

Finally, I check that the Response does not already have Compression. I have a very large Application which already has this Action Filter applied in many places. For the moment, I do not want to go back and haphazardly remove this everywhere. I will do it on a Controller by Controller basis when I next come to do some work in them.

if (filterContext.HttpContext.Response.Filter is GZipStream) return;

The final code can be found in this commit on GitHub. I hope it can be of some use to you.