ASP.NET Web API GZip compression ActionFilter with 8 lines of code



If you are building high performance applications that consumes WebAPI, you often need to enable GZip / Deflate compression on Web API.

Compression is an easy way to reduce the size of packages and in the same time increase the speed of communication between client and server.

Two common algorithms used to perform this on the Web are GZip and Deflate. These two algorithms are recognized by all web browsers and all GZip and Deflate HTTP responses are automatically decompressed.

On this picture you can see the benefits of GZip compression.

 

compression

Source : Effects of GZip compression

How to implement compression on ASP.NET Web API

There are several ways to do this. One is to do it on IIS level. This way all responses from ASP.NET Web API will be compressed.

Another way is to implement custom delegating handler to do this. I have found several examples of how to do this on the internet but it often requires to write a lot of code.

The third way is to implement your own actionFilter which can be used on method level, controller level or for entire WebAPI.
 

 

Implement ASP.NET Web API GZip compression ActionFilter

For this example with around 8 lines of code I will use very popular library for Compression / Decompression called DotNetZip library .This library can easily be downloaded from NuGet.

Now we implement  Deflate compression ActionFilter.

 public class DeflateCompressionAttribute : ActionFilterAttribute
 {

    public override void OnActionExecuted(HttpActionExecutedContext actContext)
    {
        var content = actContext.Response.Content;
        var bytes = content == null ? null : content.ReadAsByteArrayAsync().Result;
        var zlibbedContent = bytes == null ? new byte[0] : 
        CompressionHelper.DeflateByte(bytes);
        actContext.Response.Content = new ByteArrayContent(zlibbedContent);
        actContext.Response.Content.Headers.Remove("Content-Type");
        actContext.Response.Content.Headers.Add("Content-encoding", "deflate");
        actContext.Response.Content.Headers.Add("Content-Type","application/json");
        base.OnActionExecuted(actContext);
      }
  }

 

We also need a helper class to perform compression.

public class CompressionHelper
{ 
        public static byte[] DeflateByte(byte[] str)
        {
            if (str == null)
            {
                return null;
            }

            using (var output = new MemoryStream())
            {
                using (
                    var compressor = new Ionic.Zlib.DeflateStream(
                    output, Ionic.Zlib.CompressionMode.Compress, 
                    Ionic.Zlib.CompressionLevel.BestSpeed))
                {
                    compressor.Write(str, 0, str.Length);
                }

                return output.ToArray();
            }
        }
}

 

For GZipCompressionAttribute implementation is exactly the same. You only need to call GZipStream instead of DeflateStream in helper method implementation.

If we want to mark some method in controller to be Deflated just put this ActionFilter attribute above method like this :

public class V1Controller : ApiController
{
  
    [DeflateCompression]
    public HttpResponseMessage GetCustomers()
    {

    }

}

 

If you find some better way to perform this please let me know.

 

  • Hiren Thacker

    Hey, This was very helpful.
    One thing I added to your code was that you should be adding all the headers from content to new content since you are resetting the content.

    if (content != null)
    {
    foreach (var httpContentHeader in content.Headers)
    {
    actContext.Response.Content.Headers.Add(httpContentHeader.Key, httpContentHeader.Value);
    }
    }

  • http://blog.developers.ba Radenko Zec

    Hi Hiren. Thanks for comment and code improvement.
    It is logical to maintain all headers from previous content.
    Nice job.

  • JD

    Nice article but I don’t see how adding another dependency to your project is better than a few extra lines of code. To compress my Web API responses I’m using this approach: http://blogs.msdn.com/b/kiranchalla/archive/2012/09/04/handling-compression-accept-encoding-sample.aspx this works quite well and doesn’t add a dependency.

    • http://blog.developers.ba Radenko Zec

      Hi JD. Thanks for comment.
      Actually approach you are using is quite good.
      Approach described in this article you need to add reference to other project DotNetZip.
      What are the main benefits: Code is simple and small,
      I can control speed of compression and quality of compression (have 10 levels) CompressionLevel.BestSpeed,CompressionLevel.BestCompression…

      DotNetZip is one of the fastest compression libraries (some authors claim it is much faster then Microsoft implementation)

      Approach in your article is also very good. If it works for you well, you should continue use it.

  • Taiseer Joudeh

    Thanks Radenko, very useful with minimum number of LOC :)

    • http://blog.developers.ba Radenko Zec

      Thanks Taiseer.

  • Nenad Jesic

    http://laktasi.org/
    Hvala drugar

  • Vedran Mandić

    Saving the day again! :) A 3 minute implementation with minimal LOC! Hvala!

    • http://blog.developers.ba Radenko Zec

      Nema na cemu ;)

  • Siva Saripilli

    Is the output response expected to be zlibbedContent?

    • http://blog.developers.ba Radenko Zec

      Hi Siva.
      Thanks for comment. Expected output is deflate (zlibbed) content.

  • Roman Chyzh

    Hi, Radenko. Why you remove Content-Type and then add application/json? What’s reason? If i use both types (xml and json) how can i processed it? Thanks.

    • http://blog.developers.ba Radenko Zec

      Hi Roman. Thanks for comment.
      Sorry for late response.
      I think this piece of code you mention is left-over.(I have just copied code for this blog post from my production project)

      There was some need to remove and re-add application/json I think.
      You probably can just remove those 2 lines of code.

      • Roman Chyzh

        @radenkozec:disqus, well, thanks for answer :)