February 05, 2007 @ 4:53 pm-  from Maui, Hawaii


After I posted the GZip Script Compression module code a while back, I’ve gotten a number of questions regarding GZip and compression in ASP.NET applications so I thought I show a couple of other ways you can use the new GZipStream class in ASP.NET.


The beauty of this new GZip support is how easy it is to use in your own ASP.NET code. I use the GZip functionality a bit in my WebLog code. For example the the cached RSS feed is GZip encoded which uses GZipStream on the Response.OutputStream fed to an XmlWriter():

 

Response.ContentType = "text/xml";

 

Stream Output = Response.OutputStream;

 

if (Westwind.Tools.wwWebUtils.IsGZipSupported())

{

    GZipStream gzip = new GZipStream(Response.OutputStream, CompressionMode.Compress);

    Response.AppendHeader("Content-Encoding", "gzip");

    Output = gzip;

}

 

Encoding Utf8 = new UTF8Encoding(false); // No BOM!

XmlTextWriter Writer = new XmlTextWriter(Output,Utf8);

 

Writer.Formatting = Formatting.Indented;

 

Writer.WriteStartElement("rss");

 

 

Writer.WriteEndElement(); // rss

 

Writer.Close();

Response.End();

 

 

GZipStream is a stream acts like a stream filter so you can assign it to an existing stream like ResponseStream can intercept all inbound data then write the updated data into the actual Response stream. It’s super easy to do.

 

You should always check however if GZip is enabled which is done by this helper method IsGZipSupported() which looks like this:

 

/// <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;

}

 

This ensures that you don’t encode content when the client doesn’t understand GZip and would be unable to read the encoded GZip content. Note that the code checks for either gzip or deflate so this assumes before encoding you’ll pick the right encoding algorithm.

GZip in Page Content

Speaking of encoding - above was XML and raw ResponseStream encoding, but you can also apply GZip content very, very easily to your page level or any ASP.NET level code (such as in an HTTP handler). In fact, you can use a very generic mechanism to encode any output by using a Response.Filter which the following helper method (also in wwWebUtils) demonstrates:

 

/// <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()

{

    if (IsGZipSupported())

    {

        HttpResponse Response = HttpContext.Current.Response;               

 

        string AcceptEncoding = HttpContext.Current.Request.Headers["Accept-Encoding"];

        if (AcceptEncoding.Contains("gzip"))

        {

            Response.Filter = new System.IO.Compression.GZipStream(Response.Filter,

                                      System.IO.Compression.CompressionMode.Compress);

            Response.AppendHeader("Content-Encoding", "gzip");

        }

        else

        {

            Response.Filter = new System.IO.Compression.DeflateStream(Response.Filter,

                                      System.IO.Compression.CompressionMode.Compress);

            Response.AppendHeader("Content-Encoding", "deflate");

        }

    }

}

 

You can now take this helper function and use this in page level code. For example, the main page in my blog which is HUGE (frequently around 500k) uses it like this:

 

protected void Page_Load(object sender, EventArgs e)

{

    wwWebUtils.GZipEncodePage();

 

    Entry = WebLogFactory.GetEntry();

 

    if (Entry.GetLastEntries(App.Configuration.ShowEntryCount, "pk,Title,Body,Entered,Feedback,Location") < 0)

        throw new ApplicationException("Couldn't load WebLog Entries: " + Entry.ErrorMessage);

 

    this.repEntries.DataSource = this.Entry.EntryList;

    this.repEntries.DataBind();

}

 

That’s it. One line and the page will now conditionally GZip encode if the client supports it.

 

If you really wanted to you can take this even one step further and create a module that automatically sets the Response.Filter early in the pipeline and based on that automatically compresses all content.

 

However, remember GZip encoding applies compression on the fly so there’s some overhead in the GZip encoding – you are basically adding more CPU processing to your content to reduce the output size. If your content is not that large to start with there’s probably not that much sense in compressing in the first place so I don’t think that whole-sale compression of dynamic content is a good idea.

 

 

Caching

But one thing that can mitigate the overhead of GZip compression is caching.

 

Ah yes, both the RSS feed and the home page are usually heavily cached – the content doesn’t change all that frequently so there’s no reason to keep re-generating it right? If you use GZip on your content you have to be careful to cache both the GZipped content and the non-encoded content or else you’ll feed garbage to clients that don’t understand GZip.

 

So for example the default page has:

 

<%@ OutputCache Duration="60" VaryByParam="none" VaryByCustom="GZIP" %>

 

And then a custom Global.asax handler that looks like this:

 

public override string GetVaryByCustomString(HttpContext context, string custom)

{

    if (custom == "GZIP")

    {

        if (Westwind.Tools.wwWebUtils.IsGZipSupported())

            return "GZip";

        return "";

    }

 

    return base.GetVaryByCustomString(context, custom);

}

 

Which results in possibly two different versions of the GZipped page being cached.

 

 

And there you have it - GZipped content is easy to create now in ASP.NET 2.0/.NET 2.0 and if judiciously applied it can save some significant bandwidth. On my homepage which is close to 500k of blog text content (gotta review that <s>) the GZipped size is 55k or so – nearly a 90% reduction in size. I’d say that’s plenty of worth it, especially when caching is added.