This post discusses an efficient way to compress content served by the ASP.NET output cache.  In .NET Framework v2.0 SP1, included with Windows Server 2008, the ASP.NET Output Cache includes a new feature that allows cached responses to vary by the response's Content-Encoding header.  And IIS 7.0 has efficient compression algorithms for gzip and deflate.  Put the two together, and you can efficiently compress responses once, and serve them from the cache many times.

Here's how you can do this on Windows Server 2008:

1.  If you haven't already, install the Dynamic Content Compression role for IIS 7.0 using the Server Manager.

2.  Step 1 will add the <httpCompression> configuration section to applicationHost.config.  Note that only gzip compression is supported by default.  However, deflate is also implemented in gzip.dll, so if you add the following to the <httpCompression> configuration section, you'll also have deflate enabled:

            <scheme name="deflate" dll="%Windir%\system32\inetsrv\gzip.dll" />

3. Create an application with an ASPX page like the one below.  This example page will cache at most 3 responses, one that is gzip compressed, one deflate compressed, and one that is a normal (uncompressed) response.

<%@ Page language="C#" %>
<%@ OutputCache duration="600" varyByParam="None" varyByContentEncoding="gzip;deflate" %>

<script runat="server">
    void Page_Load() {
        Request.ServerVariables["IIS_EnableDynamicCompression"] = "1";
        Response.Write(DateTime.Now);
    }
</script> 

4.  You must enable the IIS 7.0 "dynamic compression before cache" feature.  You can do this by adding the following to your application's web.config file. 

<system.webServer>
    <urlCompression doDynamicCompression="false" dynamicCompressionBeforeCache="true" />
</system.webServer>

Note that in Step 4, doDynamicCompression="false".  If you set this to "true", all responses will be compressed.  That is, pages that are not using the output cache, as well as pages that are using the output cache (but may not be using varyByContentEncoding) will be compressed.  Our example sets doDynamicCompression to "false" in configuration, but then uses the server variable "IIS_EnableDynamicCompression" to enable it for a specific page.  Alternatively, you could use configuration and <location path=""> to limit the scope of compression.

So how does this work?  When a request is sent to the server, it may or may not include an Accept-Encoding header, indicating one or more acceptable encodings.  For example, Internet Explorer sends "Accept-Encoding: gzip, deflate".  When the ASP.NET Output Cache receives the request, if the page is using the VaryByContentEncoding feature, ASP.NET will check if any of the encodings specified by VaryByContentEncoding match the request's Accept-Encoding header.  If there is a match, ASP.NET will see if that response is in the cache and serve it.  If it's not cached, ASP.NET will render the response and the IIS 7.0 DynamicCompressionModule, which runs during RQ_RELEASE_REQUEST_STATE, will encode the response according to the request's Accept-Encoding header, and it will set the response's Content-Encoding header.  For the example request, the response header will be set to "Content-Encoding: gzip".  When the ASP.NET OutputCacheModule runs during RQ_UPDATE_REQUEST_CACHE, it will check to see if the Content-Encoding header has been set, and if specified in VaryByContentEncoding, it will cache the response according to that encoding.  If the Content-Encoding is not set, the response can be cached as the identity.

This ASP.NET Output Cache feature can be used via the <%@ OutputCache varyByContentEncoding= %> page directive, programmatically via HttpCachePolicy.VaryByContentEncodings, or via configuration outputCacheProfiles><add varyByContentEncoding=””></outputCacheProfiles>. 

Here are a few of the implementation details:

  • The value of the VaryByContentEncoding property is the name of an encoding, or a semi-colon separated list of encodings (e.g. "gzip", "gzip; deflate; compress"). 
  • The identity, or plain text, is a special case.  You can consider it to be implicitly included in the value of VaryByContentEncoding.  E.g., if you specify varyByContentEncoding="gzip", ASP.NET will cache at most two responses, gzip and the identity.
  • A response can be cached only if the value of its Content-Encoding header is included in VaryByContentEncoding; except for the identity, which is a special case.  If the response does not have a Content-Encoding header, it can be cached as the identity.
  • A response will be served from the cache if it has an acceptable encoding according to RFC 2616, Section 14.3 (qvalues are supported). However, even if the identity is available in the cache, if an acceptable encoding is found in VaryByContentEncoding, but that encoding has not yet been cached, ASP.NET will allow the page to render so that a response for that encoding may be cached.
  • If the value of the Content-Encoding response header is not in the list, the response is not cacheable. If the Content-Encoding response header is in the list, the response is cacheable (assuming nothing else in its cache policy prevents it from being cached).
  • If the Content-Encoding response header is not set, the response may be cached as the identity (again, assuming nothing else in its cache policy prevents it from being cached).
  • If the request's Accept-Encoding doesn't match any cached responses, and is not listed in VaryByContentEncoding, and the identity is available in the cache, then the response served will be the identity. 

Regards,
Thomas