Rick Strahl's Web Log

Wind, waves, code and everything in between...
ASP.NET • C# • HTML5 • JavaScript • AngularJs
Contact   •   Articles   •   Products   •   Support   •   Search
Ad-free experience sponsored by:
ASPOSE - the market leader of .NET and Java APIs for file formats – natively work with DOCX, XLSX, PPT, PDF, images and more

ASP.NET GZip Encoding Caveats


GZip encoding in ASP.NET is pretty easy to accomplish using the built-in GZipStream and DeflateStream classes and applying them to the Response.Filter property.  While applying GZip and Deflate behavior is pretty easy there are a few caveats that you have watch out for as I found out today for myself with an application that was throwing up some garbage data. But before looking at caveats let’s review GZip implementation for ASP.NET.

ASP.NET GZip/Deflate Basics

Response filters basically are applied to the Response.OutputStream and transform it as data is written to it through the ASP.NET Response object. So a Response.Write eventually gets written into the output stream which if a filter is also written through the filter stream’s interface. To perform the actual GZip (and Deflate) encoding typically used by Web pages .NET includes the GZipStream and DeflateStream stream classes which can be readily assigned to the Repsonse.OutputStream.

With these two stream classes in place it’s almost trivially easy to create a couple of reusable methods that allow you to compress your HTTP output. In my standard WebUtils utility class (from the West Wind West Wind Web Toolkit) created two static utility methods – IsGZipSupported and GZipEncodePage – that check whether the client supports GZip encoding and then actually encodes the current output (note that although the method includes ‘Page’ in its name this code will work with any ASP.NET output).

/// <summary>
/// Determines if GZip is supported
/// </summary>
/// <returns></returns>
public static bool IsGZipSupported()
{
    string AcceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"];
    if (!string.IsNullOrEmpty(AcceptEncoding) &&
            (AcceptEncoding.Contains("gzip") || AcceptEncoding.Contains("deflate")))
        return true;
    return false;
}

/// <summary>
/// Sets up the current page or handler to use GZip through a Response.Filter
/// IMPORTANT:  
/// You have to call this method before any output is generated!
/// </summary>
public static void GZipEncodePage()
{
    HttpResponse Response = HttpContext.Current.Response;

    if (IsGZipSupported())
    {
        string AcceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"];

        if (AcceptEncoding.Contains("deflate"))
        {
            Response.Filter = new System.IO.Compression.DeflateStream(Response.Filter,
                                        System.IO.Compression.CompressionMode.Compress);
            Response.Headers.Remove("Content-Encoding");
            Response.AppendHeader("Content-Encoding", "deflate");
        }
        else
        {
            Response.Filter = new System.IO.Compression.GZipStream(Response.Filter,
                                        System.IO.Compression.CompressionMode.Compress);
            Response.Headers.Remove("Content-Encoding");
            Response.AppendHeader("Content-Encoding", "gzip");
        }

    }
}

As you can see the actual assignment of the Filter is as simple as:

Response.Filter = new DeflateStream(Response.Filter, System.IO.Compression.CompressionMode.Compress);

which applies the filter to the OutputStream. You also need to ensure that your response reflects the new GZip or Deflate encoding and ensure that any pages that are cached in Proxy servers can differentiate between pages that were encoded with the various different encodings (or no encoding).

To use this utility function now is trivially easy: In any ASP.NET code that wants to compress its Response output you simply use:

protected void Page_Load(object sender, EventArgs e)
{            
    WebUtils.GZipEncodePage();

    Entry = WebLogFactory.GetEntry();

    var entries = Entry.GetLastEntries(App.Configuration.ShowEntryCount, "pk,Title,SafeTitle,Body,Entered,Feedback,Location,ShowTopAd", "TEntries");
    if (entries == null)
        throw new ApplicationException("Couldn't load WebLog Entries: " + Entry.ErrorMessage);

    this.repEntries.DataSource = entries;
    this.repEntries.DataBind();

}

Here I use an ASP.NET page, but the above WebUtils.GZipEncode() method call will work in any ASP.NET application type including HTTP Handlers. The only requirement is that the filter needs to be applied before any other output is sent to the OutputStream. For example, in my CallbackHandler service implementation by default output over a certain size is GZip encoded. The output that is generated is JSON or XML and if the output is over 5k in size I apply WebUtils.GZipEncode():

if (sbOutput.Length > GZIP_ENCODE_TRESHOLD)
    WebUtils.GZipEncodePage();

Response.ContentType = ControlResources.STR_JsonContentType;
HttpContext.Current.Response.Write(sbOutput.ToString());

Ok, so you probably get the idea: Encoding GZip/Deflate content is pretty easy.

Posted in ASP.NET   IIS7  

The Voices of Reason


 

Adam Schroder
May 02, 2011

# re: ASP.NET GZip Encoding Caveats

I was working on this same issue today and solved it like this (opposite of your solution). I think that your solution is probably better. It really is a shame it doesn't do this automatically.


   protected void Application_PreSendRequestHeaders()
    {
        HttpResponse response = HttpContext.Current.Response;
        if (response.Filter is GZipStream && response.Headers["Content-encoding"] != "gzip")
            response.AppendHeader("Content-encoding", "gzip");
        else if (response.Filter is DeflateStream && response.Headers["Content-encoding"] != "deflate")
            response.AppendHeader("Content-encoding", "deflate");
    }
 

Rick Strahl
May 02, 2011

# re: ASP.NET GZip Encoding Caveats

@Adam - actually that's a pretty sweet way to add the headers and that's actually more reliable since it captures all scenarios where the response may be cleared out inadvertantly.

For example I have various service implementation where handlers manage error trapping and error return and there too I know I've forgotten in the past to clear out filters and the above would capture that.

lonely soul
May 06, 2011

# re: ASP.NET GZip Encoding Caveats

great article as usual Rick.

Matt
May 09, 2011

# re: ASP.NET GZip Encoding Caveats

I think you need to detect IE6 clients also and not serve gzipped content to them. My understanding is that IE6 (prior to SP2?) will report that it can accept gzip content but will screw up the decoding of the compressed content under various, seemingly random, scenarios.

Andy
May 09, 2011

# re: ASP.NET GZip Encoding Caveats

Adam's solution is very reliable but with one caveat... The GZipStream must be the last filter in the Response.Filter stream chain. (Though I can't think if any situation where it wouldn't be...) But it's possible that streams are chained after the GZipStream to the Response.Filter, therefore getting the Response.Filter before sending headers will return whichever stream was last applied.

For example consider:
Response.Filter = new System.IO.Compression.GZipStream(Response.Filter,
     System.IO.Compression.CompressionMode.Compress);

... later in the same request execution ...
Response.Filter = new MyFilter2(Response.Filter);

then...
(Response.Filter is GZipStream) == false;

vimal
May 10, 2011

# re: ASP.NET GZip Encoding Caveats

Very nice article

Cbrcoder
May 16, 2011

# re: ASP.NET GZip Encoding Caveats

Don't you think this is a webserver concern and best not to use it in code ? Just make a comparison of performance difference between IIS 7.5 and .NET version of compression libraries, there is atleast 10x difference in speed. It's best for programmers to not mess with what is a SERVER concern.

Rick Strahl
May 17, 2011

# re: ASP.NET GZip Encoding Caveats

@CBrCoder - I don't agree with you. IIS's dynamic compression gives you no control over what gets compressed - it's all or nothing. In my apps where I use compression of dynamic content I really just want a few things compressed not everything. Additionally a lot of the overhead of compression can be negated by properly using Caching in your applications.

FWIW, I posted a second blog entry that talks about built-in compression - choices are good :-)

novi4ok
July 06, 2011

# re: ASP.NET GZip Encoding Caveats

Hello!
I have one question about this code...
I have some errors, because i cant find what i need to write in
using
line...
for example my Visual Studio underline this code
Entry = WebLogFactory.GetEntry();
. I dont know where is WebLogFactory, in which using it is?

Andr
July 12, 2011

# re: ASP.NET GZip Encoding Caveats

Rick, you are very cool guy. You as usual save my hours and even days of dumb work by your great articles.

kralcafe
July 28, 2011

# re: ASP.NET GZip Encoding Caveats

I think you need to detect IE6 clients also and not serve gzipped content to them.

Wynand Murray
September 01, 2011

# re: ASP.NET GZip Encoding Caveats

Hi Rick, thanks for a great post, this has REALLY brought down my site's traffic in size!
I did find one error in your "IsGZipSupported" function though:

I am referring to the following line:

if (!string.IsNullOrEmpty(AcceptEncoding) && (AcceptEncoding.Contains("gzip") || AcceptEncoding.Contains("deflate")))

The exception occurs when the client browser does NOT return an Encoding Header.
Upon checking if the "AcceptEncoding" value (Which will be null) "Contains" some text, an Object Reference exception is thrown.

I fixed this by checking if "AcceptEncoding" is null WITHOUT using the "&&" operator to search for the text value inside it. I only use the "Contains" method to search for values after "AcceptEncoding" has been identified as having a value.

This was strange to me since I didn't think that testing for a "Null" AND a value with the "&&" operator would result in an exception in this case.

Thanks for all your great posts, please keep them coming :)

Regards,

Wynand Murray.

Rick Strahl
September 01, 2011

# re: ASP.NET GZip Encoding Caveats

@Wyand - I'm not sure why that should fail. If the value is null the condition !string.IsNullOrEmpty() will fail and should terminate the if expression via short circuiting - the && should never evaluate. If it indeed fails then there'd be a serious bug in the .NET compiler not properly short circuiting which would break lots of code. You sure there's not something else going on when this fails?

blueray
September 28, 2011

# re: ASP.NET GZip Encoding Caveats

Hi,

excellent article, thanks.

I got problem though. I used the workaround for appending lost content-encoding header by Adam Schroder , but HttpContext.Current is always null inside Application_PreSendRequestHeaders, thus on exception I receive that scrambled text. Any ideas why HttpContext.Current is null ?

DotNetWise
March 24, 2012

# re: ASP.NET GZip Encoding Caveats

Weird enough, ASP.NET MVC 4 Web API ignores IIS gzip compression.
Any ideas how to turn it on in that case too?

Rick Strahl
March 24, 2012

# re: ASP.NET GZip Encoding Caveats

@DotNetWise - what gets ignored exactly? Are you talking about IIS dynamic compression, static compression, ASP.NET based filter GZip compression?

DotNetWise
March 26, 2012

# re: ASP.NET GZip Encoding Caveats

IIS' dynamic compression is ignored on ASP.NET MVC 4 for Actions who return JsonResult or application/json; charset=UTF-8; although the web.config explicitely enables dynamic compression to *.*

<httpCompression sendCacheHeaders="true" directory="c:\temp" minFileSizeForComp="10" noCompressionForProxies="false">
      <clear/>
      <scheme name="gzip" dll="%Windir%\system32\inetsrv\gzip.dll" staticCompressionLevel="9" />
      <dynamicTypes>
        <clear/>
        <add mimeType="text/*" enabled="true" />
        <add mimeType="message/*" enabled="true" />
        <add mimeType="application/x-javascript" enabled="true" />
        <add mimeType="application/json" enabled="true" />
        <add mimeType="application/json; charset=UTF-8" enabled="true"   />
        <add mimeType="*/*" enabled="true" />
      </dynamicTypes>
      <staticTypes>
        <add mimeType="text/*" enabled="true" />
        <add mimeType="message/*" enabled="true" />
        <add mimeType="application/x-javascript" enabled="true" />
        <add mimeType="application/atom+xml" enabled="true" />
        <add mimeType="application/xaml+xml" enabled="true" />
        <add mimeType="application/javascript" enabled="true" />
        <add mimeType="application/javascript; charset=UTF-8" enabled="true" />
        <add mimeType="application/json" enabled="true" />
        <add mimeType="application/json; charset=UTF-8" enabled="true" />
        <add mimeType="*/*" enabled="true" />
      </staticTypes>
    </httpCompression>

James Stoertz
April 12, 2012

# re: ASP.NET GZip Encoding Caveats

Rick, you are brilliant. BRILLIANT!
We had the "garbled" problem (on errors) for months and couldn't resolve it. Couldn't even search for it because of the binary screen.

Lachlan
July 05, 2012

# re: ASP.NET GZip Encoding Caveats

Hi, good article thanks.

I tried throwing an error after setting the filter without doing anything clever, and it seemed to strip the filter itself without any intervention from me?

Also, I found a bug in the edge case of doing Response.Redirect() from AJAX (UpdatePanel) - this strips the encoding header but not the filter - hence you get an error at client.

In my case I "solved" this by setting the filter etc in page PreRender rather than Load (Redirect() will hopefully occur before PreRender). I haven't tried the PreSendRequestHeaders method but that may also fix it.

Another handy tip is you can use the DotNetZip library GZipStream which does better compression than the built-in .Net class. http://dotnetzip.codeplex.com/

Cheers

Mike McGranahan
September 02, 2012

# re: ASP.NET GZip Encoding Caveats

Thanks for the write up. Just want to note that the response Vary header should be "Accept-Encoding", not "Content-Encoding". Names listed in the Vary value refer to headers in the Request, not the Response.

Ian
November 28, 2012

# re: ASP.NET GZip Encoding Caveats

Hi Rick,

I have implemented this code within Application_BeginRequest on two sites.

One site is MVC 3 asp.net 4 framework.

And the other is a legacy site running on Framework 2.

I have installed both sites onto my local windows 7 premium home addition IIS 7.

The compression works for the MVC 3 site but is completely ignored on the asp.net 2 site.
Looking at Fiddler it seems to swallow the response headers that were added and not compressing anything.

I noticed the dynamic compression module is not installed on my local IIS, does that need to be installed for the compression to work. Even though it works fine for a Framework 4 site.?

Thanks
Ian

Rick Strahl
November 28, 2012

# re: ASP.NET GZip Encoding Caveats

@Ian - dynamic compression does not need to be installed on the server. Dynamic compression is IIS automatically compressing even dynamic content - this code does it 'manually' as part of the individual request processing.

This code should work fine in ASP.NET 2.0, so unless the application is possibly manipulating headers this code should continue to work.

Carl-Erik Kopseng
October 10, 2016

# re: ASP.NET GZip Encoding Caveats

Yeah, indeed it is easy, but unfortunately your example code is incorrect, as it does not try to parse the Accept-Encoding per the spec :-/ A http client might say "while this encoding is supported, do not try to use it" by saying "my-format;q=0". Your code fails (gives incorrect results), for instance, on strings like these: "deflate;q=0.0,gzip;q=0.0,identity". Here neither gzip nor deflate should be used.

Dave Transom has some good code for parsing and handling this on his blog where he goes into detail on how to handle this on a more general basis:
http://www.singular.co.nz/2008/07/finding-preferred-accept-encoding-header-in-csharp/

Sivan Leoni
September 06, 2017

# re: ASP.NET GZip Encoding Caveats

Thank you for the great post. Any idea why would a certain website look fine on all computers and browsers, but a specific computer it is garbled text page in all browsers. Is there a settings somewhere in windows that prevents pages from interpreting gzip content correctly? Why would only one of my clients receive the garbled text?

I'm a web programmer, and my application works fine for all people, but I have one client that says that in all their browsers, the page looks garbled.

Any help would be great.

Thanks! Sivan

 

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2017