Compression support in ASP.NET 2.0Bart De Smet
Applies to:MVP Visual C#
This article covers the new System.IO.Compression namespace in the .NET Framework v2.0. You'll learn how this namespace can be used to implement gzip and deflate compression directly in ASP.NET. Downloads: Contents: IntroductionTo decrease the number of bytes sent over the wire during a web request, browsers can send an Accept-Encoding header to the web server requesting a certain compression of the response sent back. Let's take a look at it how this looks in reality right now. To do this, open up a simple Notepad instance and create the following simple page: <%@ Page Trace="True" %> I tried to make it easier but didn't succeed. Save the page in the inetpub\wwwroot folder and request it through the browser by calling http://localhost/trace.aspx. Locate the headers section in the trace and you'll find the Accept-Encoding header: ![]() Internet Explorer accepts compression This is the header sent by the browser to the web server during the request to ask for compression. In fact, this compression header is a HTTP 1.1 feature. To show you, go to the settings of the browser and disable HTTP 1.1 support: ![]() Compression is a feature of HTTP 1.1 only When you restart the browser and invoke the request again, the Accept-Encoding header will have disappeared: ![]() Internet Explorer doesn't accept compression when running without HTTP 1.1 support Now, what about the compression? First of all, turn on the HTTP 1.1 support again and to make sure the latest settings are loaded, restart the browser. Next, go to the IIS 6.0 properties of the Web Sites "folder" and go to the Service tab: ![]() IIS has built-in support for compression Over here you can enable the IIS level compression of static files and application files. The temporary folder will contain the compressed files as a kind of cache. For those of you who're interested in the internal kitchen of this, take a look at the metabase file (windows\system32\inetsrv\metabase.xml) and locate the IisCompression tags in it: <IIsCompressionScheme Location ="/LM/W3SVC/Filters/Compression/deflate" HcCompressionDll="%windir%\system32\inetsrv\gzip.dll" HcCreateFlags="0" HcDoDynamicCompression="TRUE" HcDoOnDemandCompression="TRUE" HcDoStaticCompression="FALSE" HcDynamicCompressionLevel="0" HcFileExtensions="htm html txt" HcOnDemandCompLevel="10" HcPriority="1" HcScriptFileExtensions="asp dll exe" > </IIsCompressionScheme> <IIsCompressionScheme Location ="/LM/W3SVC/Filters/Compression/gzip" HcCompressionDll="%windir%\system32\inetsrv\gzip.dll" HcCreateFlags="1" HcDoDynamicCompression="TRUE" HcDoOnDemandCompression="TRUE" HcDoStaticCompression="TRUE" HcDynamicCompressionLevel="0" HcFileExtensions="htm html txt" HcOnDemandCompLevel="10" HcPriority="1" HcScriptFileExtensions="asp dll exe" > </IIsCompressionScheme> <IIsCompressionSchemes Location ="/LM/W3SVC/Filters/Compression/Parameters" HcCacheControlHeader="max-age=86400" HcCompressionBufferSize="8192" HcCompressionDirectory="%windir%\IIS Temporary Compressed Files" HcDoDiskSpaceLimiting="FALSE" HcDoDynamicCompression="FALSE" HcDoOnDemandCompression="TRUE" HcDoStaticCompression="FALSE" HcExpiresHeader="Wed, 01 Jan 1997 12:00:00 GMT" HcFilesDeletedPerDiskFree="256" HcIoBufferSize="8192" HcMaxDiskSpaceUsage="100000000" HcMaxQueueLength="1000" HcMinFileSizeForComp="1" HcNoCompressionForHttp10="TRUE" HcNoCompressionForProxies="TRUE" HcNoCompressionForRange="FALSE" HcSendCacheHeaders="FALSE" > </IIsCompressionSchemes> These contain the settings of the compression and the mappings on the DLL that does the trick (in this case, gzip.dll). If you just want to apply compression on a certain website, you can use the non-global variants called DoDynamicCompression and DoStaticCompression on particular nodes in the metabase (whether this is a virtual directory, a normal directory, a file, or whatever else). Detailed information can be found in the IIS 6.0 Resource Kit. To make sure that the new settings are loaded, restart IIS using iisreset.exe. To see that compression is effectively done during the request processing, let's call a static HTML page by calling http://localhost (this should, on a clean installation, retrieve the iisstart.htm file). When you've made the request, you won't see a difference in the browser. However, when you take a look at the C: \WINDOWS\IIS Temporary Compressed Files folder on your web server, you'll find a file containing the gzipped content: ![]() The compressed files are stored in a temporary folder for reuse during subsequent requests Note that the modification date will always be the same. When you add another static HTML file to the inetpub\wwwroot folder and make a few requests for it through the browser, a new compressed file will pop up in the temporary folder, with the same modification date. Open the file using notepad to take a look at the contents of it. You'll see that it's indeed compressed: ![]() If you can read this, you should be running a HTTP 1.1 compliant browser Subsequent requests for the file will be served from the cache. When dynamically generated pages are zipped, these won't be stored to disk. The expiration date of a zipped file will be set to 1997 always to prevent proxy servers from caching the file and sending it back (in mistake) to browser that do not support compression. If you're a diehard and want to see the compressed response yourself, I invite you to use the network monitor in Windows Server 2003 to take a look at the network traffic for the HTTP request: ![]() The "Network Monitor Tools" can be installed through the Windows Components Wizard, Management and Monitoring Tools. You'll find the tool in the Administrative Tools folder. When you now start a monitoring on the local network card and request the page again, you should see the HTTP request in the monitoring result: ![]() The server sends a response back to the client with the header Content-Encoding set to gzip. As you can see, the response was encoded using gzip. Furthermore, the data in the packet looks at is compressed, right? ![]() The data content of the package is unreadable due to the compression. The unzipped result (when you disable the setting in IIS again) looks as follows: ![]() Without zipping turned on, the sent data is readable and considerably longer in bytes. And it even takes two packets to send it over the wire to the client (see the 3rd frame that contains the continuation of the response packet). So far so good, IIS can compress the responses before these are sent back to the client if the browser supports this. But how to put this feature on (it's disabled by default since it can have drawbacks on the processor usage on the server) in a hosting environment? The good news is that you can put the compression on at the level of ASP.NET instead of IIS. Today, this is rather difficult since you'd need a 3rd party library to do the compression. In .NET Framework 2.0 however, the support of compression is included in the framework itself. ![]() Compression in ASP.NET 2.0 – the basicsAs I mentioned in the introduction of this article, the .NET Framework 2.0 has a new namespace called System.IO.Compression. It has three members: the DeflateStream en GZipStream classes and the CompressionMode enum. The way these classes work is pretty straightforward. Just create an instance using one of the constructors that takes a stream and a CompressionMode and the result will be a compressed stream that can be used to send over the network for instance. ![]() Implementing a compression HttpModuleNow, to apply the compression on the level of ASP.NET we need to hook in into the ASP.NET runtime using a HTTP Module. Such a module is very easy to create as I'll show you and can be seen as a kind of filter where all the requests/responses go through. An example of a HttpModule is the implementation of the caching feature in ASP.NET. You'll find the default modules in the machine.config.comments file (this is the new location of the machine-wide settings in ASP.NET 2.0) in the "httpModules" section: <httpModules> <clear /> <add name="OutputCache" type="System.Web.Caching.OutputCacheModule" /> <add name="Session" type="System.Web.SessionState.SessionStateModule" /> <add name="SessionID" type="System.Web.SessionState.SessionIDModule" /> <add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" /> <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" /> <add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" /> <add name="RoleManager" type="System.Web.Security.RoleManagerModule" /> <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" /> <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" /> <add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" /> <add name="Profile" type="System.Web.Profile.ProfileModule" /> <add name="PageCountersModule" type="System.Web.PageCountersModule" /> <add name="SqlBatchModule" type="System.Web.DataAccess.SqlBatchModule" /> </httpModules> As you can see, authentication is another example of a module. Now, we're going to add our own module to the ASP.NET runtime for our own application (that is, by adding a httpModule section to our web.config file instead of using the machine.config). Launch the Visual C# 2005 Express development tool. Create a new class project and call it "CompressionModule": ![]() Create a new Class Library project in Visual C# 2005 Express. First we should add a reference to the System.Web.dll assembly in the Solution Explorer. Next, go to the Class1.cs file's code view and add another using directive to the "Using directives" region for System.Web: using System.Web; Next, change the namespace to MSDN.Demo.Compression and change the name of the class to HttpCompressionModule. To create an HTTP module, you should implement the IHttpModule interface, so let's do this: namespace MSDN.Demo.Compression
{
public class HttpCompressionModule : IHttpModule
{Tip: you can take advantage of the IntelliSense options of the Visual Studio editor to do the "basic implementation" of the interface automatically by pressing the tab key. The resulting code view will look something like this: ![]() Our class implements IHttpModule. Now, open up the generated region for the IHttpModule members. This is the place where we need to hook in into the event-driven module's implementation. Inside the Init method, we need to tell the system that we're interested in receiving a call when the request comes in to the ASP.NET runtime. The code is pretty straight-forward: void IHttpModule.Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}As the matter in fact, you can take advantage of the IntelliSense again to do the implementation: ![]() We'll hook in to the BeginRequest event. Last but not least, it's time to write the actual code for the event handler. First, make sure the System.IO and System.IO.Compression namespaces are imported in the class's definition: using System.IO; using System.IO.Compression; So, let's take a look at what we should now to get started with our implementation. Of course we need to know whether the browser accepts gzip or deflate compression by examining the request headers (in particular, the Accept-Encoding header). Next, we need to do the compression of the response itself. I think it's pretty clear that we need access to the Request and Response objects to get this information and to manipulate the response before it's sent to the client. What do we have? Actually, the only thing we have now is this: void context_BeginRequest(object sender, EventArgs e)
{The trick is fairly easy. You should know that the sender parameter contains an instance of the HttpApplication class. Casting is the magic word in this case: // // Get the application object to gain access to the request's details // HttpApplication app = (HttpApplication)sender; Now, we're in business. First, let's take a look at the request to determine whether the client has requested compression or not: //
// Accepted encodings
//
string encodings = app.Request.Headers.Get("Accept-Encoding");
//
// No encodings; stop the HTTP Module processing
//
if (encodings == null)
return;This piece of code retrieves the Accept-Encoding header and checks whether the returned value is null or not. If it's null, we can just go ahead and continue with the processing inside this module (this won't stop the rest of the processing inside the ASP.NET runtime of course). The only thing left to do now is the actual compression: //
// Current response stream
//
Stream baseStream = app.Response.Filter;
//
// Find out the requested encoding
//
encodings = encodings.ToLower();
if (encodings.Contains("gzip"))
{
app.Response.Filter = new GZipStream(baseStream,
CompressionMode.Compress);
app.Response.AppendHeader("Content-Encoding", "gzip");
app.Context.Trace.Warn("GZIP compression on");
}
else if (encodings.Contains("deflate"))
{
app.Response.Filter = new DeflateStream(baseStream,
CompressionMode.Compress);
app.Response.AppendHeader("Content-Encoding", "deflate");
app.Context.Trace.Warn("Deflate compression on");
}That's it. Let's explain. In the first line of code we're getting the response stream using the Filter property of the Response object. The next part of the code is just about taking a decision which compression to apply. In there, the new GZipStream and DeflateStream classes are being used and the Content-Encoding response header is set to the appropriate value. Last but not least I'm putting some little entry in the trace (something that can be dropped later on, or you can put it in a pre-processor #if DEBUG block). Finally, compile the whole thing. ![]() Using the module in ASP.NET 2.0The last thing to do of course is get to use the self-written module in ASP.NET. To do this, we're going to use the Visual Web Developer 2005 Express tool. Create a new web project and go to the default.aspx page to add some fancy text "Hello Galaxy!" to it. In the source, put the trace of the page on: <%@ Page Language="C#" Trace="true" %> Remark the "IntelliSense everywhere" feature in the new tools: ![]() ASP.NET v2.0 – IntelliSense everywhere Next, go to the Windows Explorer and locate the assembly .dll file of our HttpModule we created in the previous paragraph. In my case it's in the My Documents\Visual Studio\Projects\CompressionModule\CompressionModule\bin\Debug folder. Drag-and-drop the file to the web project and make sure it's being placed in the bin folder. The Solution Explorer should look like this: ![]() The HttpModule assembly should be placed in the bin folder of the web application. Finally, change the web.config file's <configuration><system.web> part by adding the next piece of code that installs the HTTP module in the ASP.NET runtime for our application: <httpModules> <add name="HttpCompressionModule" type="MSDN.Demo.Compression.HttpCompressionModule, HttpCompressionModule"/> </httpModules> <trace enabled="true" /> That's it. Now, we're ready to test the solution by pressing F5. Please note that IIS is not required for this and that the ASP.NET Web Matrix way of working is now available in the new Visual Studio 2005 web development tools. A built-in (secure, since it only allows local requests) web server will be fired up to handle the request: ![]() The built-in web server of Visual Studio 2005 announces itself in the system tray. Finally, our "Hello Galaxy!" page appears and the trace tells us that the compression module has done its work: ![]() The trace proves that the HttpModule was processed during our request. Of course, you can take a detailed look at the request again by using the network monitoring tool but keep in mind that remote requests to the Visual Web Developer Web Server are not allowed. ![]() ConclusionThere are a bunch of new features in .NET Framework v2.0 (and the tools of course) and compression is just one of these exciting things. Be sure to check out my other articles on Visual Studio 2005 and the .NET Framework 2.0 as well, there's a lot yet to come. ![]() How to get the .NET Framework 2.0?The pre-release of the .NET Framework v2.0, previously code-named "Whidbey", can be retrieved from http://msdn.microsoft.com/express through the Express Tools. These include the .NET Framework v2.0 beta itself, ASP.NET v2.0 beta and the Visual Studio 2005 Express tools used throughout this article in the demos. ![]() About the author
| ||||||