HTTP compression of WebResource.axd and pages in ASP.NET

Sep 16, 2007

A month ago I challenged Simone to a friendly duel about who could get the best YSlow score. I did all sorts of things to improve the score but the one thing I never figured out was how to gzip compress the WebResource.axd handler. Then yesterday I was playing around with Fiddler and noticed that IE doesn’t cache WebResource.axd but loads it at every request to a page. Then I knew I had to do something about it and I might as well give the gzip compression another try.

I already have a HTTP compression module that works like a charm, so I just needed it to compress WebResource.axd and implement the right client cache mechanisms as well. Sounds simple right?

The compression

I knew this was my Achilles heel since I’ve failed at it before. Lucky for me, Miron Abramson just did it a week ago so I took a look at his solution. It was exactly what I needed, so I picked out the parts I needed and stuck them into my own compression module.

What Mirons code does is to create an HTTP request to the WebResource.axd, copy the response to a memory stream, compress it and serve it back to the client. That is a very clever approach but had a big problem. Every time the browser requested the WebResource.axd handler his module created an HTTP request. That was too big an overhead for me. I fixed it easily by caching the response at the first request and then served the compressed bytes from the cache from then on. Apart from that, his code rocks.

The caching

First of all I set the expires, last-modified and ETag HTTP headers that let’s any browser cache the response. Browsers are a little different when it comes to caching web resources so by specifying both an ETag and last-modified header takes care of them all.

Further more I added support for the If-None-Match request header. What it does is that when a page is refreshed by hitting F5 or similar, the browser sends the If-None-Match header to the server with the ETag as the value. Then I can simply check if the incoming ETag value is the same as the server ETag and terminate the response with a 304 Not modified response header.

That saves bandwidth and tells the browser to use the version from its cache instead of loading the content from the server again. The thing is that files served from WebResource.axd are always static so no harm can be done by using all means to let the browser cache them.

Limitations

The module expects that all files served by the WebResource.axd are JavaScript. That means that you probably run into trouble if you serve images or other file types from the WebResource.axd handler. Most websites don’t and I don’t like doing that in my projects, so that’s why I didn’t spend extra time on supporting it.

Download

Get the CompressionModule.cs file below to compress both your pages and WebResource.axd handler.

CompressionModule.zip (2,27 kb)

PS. I won the YSlow challenge.

* $4.95/month ASP.NET Hosting with FREE SQL 2012 DB! – Click Here!

Comments (34) -

Richard O'Donnell
Richard O'Donnell United Kingdom
9/16/2007 4:20:23 PM #

The reason why your WebResource.axd is not cached in IE is probably because you have deployed your ASP.NET in debug mode. Scott Gu has written about this before:

weblogs.asp.net/.../...D20_true_1D20_-enabled.aspx

Love BlogEngine.NET BTW!

Mads Kristensen
Mads Kristensen Denmark
9/16/2007 4:25:10 PM #

No, that's not it. This site has always been deployed in release mode. It's a bug in the WebResource.axd handler that it doesn't allow browsers to cache it properly

Dave Transom
Dave Transom New Zealand
9/16/2007 10:08:51 PM #

Hi Mads,

Good stuff. I was having a peek (like I do at all your code Smile and saw something that might present a little issue I ran into the other day.

I was setting the cacheability of the css resources on my last project to public but ran into an issue where the gzipped version was being cached by a downstream proxy and some of the clients (browsers) behind that proxy that weren't configured for gzip they would only receive the cached version and would not be decompressed. The user was using IE6.

For now, I've gone with the <code>Cache.SetCacheability( HttpCacheability.Private );</code>, which means nothing should cache between the server and client, and it fixed the problem.

I saw in your code that the IsBrowserSupported method excludes IE 6; do you think that is perhaps were my issue lies? or do you think there may be a problem with private verses public cacheability and accept-encoding?


Franklin Rau
Franklin Rau United States
9/16/2007 10:25:59 PM #

Great article.  Always looking for ways to improve performance.  Thanks for sharing.

Ted Jardine
Ted Jardine Canada
9/17/2007 4:46:20 AM #

Any more info on "It's a bug in the WebResource.axd handler that it doesn't allow browsers to cache it properly"? Hadn't heard of that one before (and as above, I first thought of the ScottGu article on debug=true which didn't make sense that you wouldn't be aware of that).

Brad
Brad United States
9/17/2007 4:38:00 PM #

Is there a way to include Javascript files and CSS files in the compression as well?

Fiddler says they aren't being compressed and:

"app.Context.CurrentHandler is System.Web.UI.Page" in context_PostReleaseRequestState seems to be a specific check for only page level pages.

Simone Chiaretta
Simone Chiaretta Italy
9/17/2007 4:44:23 PM #

<blockquote>
PS. I won the YSlow challenge.
</blockquote>
This is going to be a never ending challenge.. stay tuned for more improvements Smile

Simone Chiaretta
Simone Chiaretta Italy
9/18/2007 11:46:34 AM #

I saw you moved the compression code from the PostReleaseRequestState to the PreRequestHandlerExecute.
Is there a reason for that?

Mads Kristensen
Mads Kristensen Denmark
9/18/2007 1:34:02 PM #

No, not really. It was what Miron did so I figured it to be a valid event to use. It works with both events though.

michael
michael United States
9/18/2007 4:43:09 PM #

Am I the only one getting a 401 error?

The remote server returned an error: (401) Unauthorized.
   at System.Net.HttpWebRequest.GetResponse()
   at CompressionModule.AddCompressedBytesToCache(HttpApplication app, String key) in ...CompressionModule.cs:line 185
   at CompressionModule.context_EndRequest(Object sender, EventArgs e) in ...CompressionModule.cs:line 133
   at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

Mads Kristensen
Mads Kristensen Denmark
9/18/2007 5:10:07 PM #

I'll bet it's because you run on medium trust, right? Then you cannot send http requests. There is a workaround for sending requests to your own site in the web.config, but I can't remember what it is.

michael
michael United States
9/18/2007 7:02:30 PM #

Actually, looks like the 401 error was happening because I'm using Windows authentication. Prior to the GetResponse I added the line:

request.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;

Seems to work.


I also added some code to handle different types of WebResources (seems like some control developers are putting CSS as an embedded resource). Perhaps it's not the most elegant but I added

app.Application.Add(key + "contenttype", response.ContentType);

right after getting the response and before compressing the response. In the context_EndRequest I replaced the content type = javascript with

app.Context.Response.ContentType = (string)app.Application[key + "contenttype"];

Great work again! Now just gotta figure out a way to reduce the number of requests to further bump up our grade.

B. Crais
B. Crais United States
9/18/2007 7:15:43 PM #

Just what I was looking for...thanks!

Miron Abramson
Miron Abramson Israel
9/19/2007 2:28:27 AM #

Thank you for highlight the performance issue in my code.
I already publish a new post with updated code and improvements for
<a href='mironabramson.com/.../...r-better-performance.aspx'>compressing pages, css, js and WebResources.axd files.</a>

Miron Abramson
Miron Abramson Israel
9/19/2007 2:31:07 AM #

Oops, Sorry for the mes in the last comment.

Max
Max United States
9/19/2007 3:25:42 PM #

is there much of a performance overhead using this method?  I had read that using IIS compression for dynamic pages could cause alot of overhead, was wondering if the same thing applied here.  I am already using IIS Compression for .js and .css files

Form validation scripts from WebResource.axd went from 41K to about 8K!

Mads Kristensen
Mads Kristensen Denmark
9/19/2007 6:49:02 PM #

The overhead is minimal. I did some tests on it about a year ago and even though I don't remember the exact numbers, I do remember it being a very low impact. Besides, the WebResource.axd is cached so it actually doesn't do compression at every request.

Blah
Blah United States
9/20/2007 1:39:26 AM #

This module doesn't work, it returns numerous 404 errors when dealing with WebResource.axd requests. When the module is removed from the pipeline the requests work perfectly. Very frustrating.

Mark Jensen
Mark Jensen Denmark
9/20/2007 5:18:10 PM #

Very nice module i must say!

I had several difficulties using it together with ASP.NET AJAX.
AJAX request sends a header so you can just filter them out.

Place this at the start of the context_PostReleaseRequestState method.
---
// ASP.NET Ajax request
if (HttpContext.Current.Request.Headers["X-MicrosoftAjax"] != null)
    return;
---

James
James United States
9/26/2007 7:00:11 PM #

Will this break the Ajax Control Toolkit?  I noticed you mentioned that it expects all embedded resources to be js files.  I have a couple dlls that have embedded graphics and css files, how will these be affected?

max
max United States
10/14/2007 8:50:57 PM #

This works great for me except when I try to use CSS Friendly Control Adapters (http://www.asp.net/cssadapters/), then it breaks and I get a 404 on the WebResource.axd files that were serving up javascript form form validation.

Anyone have an idea of how to get around this?  I'd love to be able to use both of these things together!

Miron Abramson
Miron Abramson Israel
10/15/2007 12:49:45 AM #

I recently posted a new & much better Compression module for WebResources.
Actually I rewrite the AssemblyResourceLoader to work better and for compression support.

BrandSpankingNewbie
BrandSpankingNewbie United States
10/22/2007 3:09:06 PM #

Sorry for a low-level question, but how is this implemented?  I'm used to the idea of HttpModules being DLL's dropped in the /bin folder, with a reference in the web.config - but that doesn't seem to be the intention here.  Thanks in advance!

BrandSpankingNewbie
BrandSpankingNewbie United States
10/22/2007 3:40:08 PM #

My apologies - I found this page:
blog.madskristensen.dk/.../...on-in-ASPNET-20.aspx and tried adding this to the <httpModules> section of the web.config:
<add type="CompressionModule" name="CompressionModule" />

And it works fantastically.  Thanks for sharing

Ponnu
Ponnu India
11/6/2007 8:10:29 PM #

Hi Mads,

Great job with the HTTP compression. I have a doubt, even IIS 6.0 supports compression, how different is this HTTP compression from IIS 6.0 compression when related to webresource.axd specifically. Could you please shed some light on this as I am very new to this.

Also I am looking at compressing webresource(the infragistics ones) files which is costing my performance, I used your CompressionModule by including it in App_Code and adding an entry in web.config for the same, but didn't find that much difference in webresource files. Can you let me know what the problem could be.

Thanks
Ponnu

Roops
Roops India
11/27/2007 4:22:29 AM #

I have to download an excel file in the application. When I use this compression module, the excel file gets compressed too. It is then seen filled with junk chars .How can I avoid compression for a single page keeping httpmodule tag in the web.config?

Thomasabcd
Thomasabcd Denmark
1/8/2008 9:35:32 PM #

Hi Mads,

Great module. I am having one problem though. I am trying to use the asp:substitution-control for caching, but it doesn't work when I use the compressionmodule. Any ideas on how to solve this?

Thanks,
Thomas

Hassan Abdullah
Hassan Abdullah Lebanon
4/22/2008 8:09:28 AM #

Great Module Mads,

but I noticed this in CompressResponse method:

            else if (IsEncodingAccepted(GZIP))
            {
                compress = new GZipStream(ms, CompressionMode.Compress);
                app.Application.Add(key + "enc", DEFLATE);
            }

I think it should be GZIP

Thanks,

lichun
lichun People's Republic of China
4/24/2008 12:40:13 AM #

in IE 7,have an error,the img of treeview don't show

js
js India
7/8/2008 9:42:33 AM #

I have implemented this module and its working fine compressing WebResource.axd. But when I am testing the site with Yslow it is showing :
"WebResource.axd do not have a far future Expires header"
whereas I have already set this on my apache server.
And if I remove this MbCompression module from my application it doesnot show any problem with expire header.
How can we solve this?

Thomas Hansen
Thomas Hansen Norway
10/19/2008 10:01:16 AM #

Hi Mads, Kariem solved this a week ago and did a writeup about it at; ra-ajax.org/...-compression-a-simple-solution.blog

Though your is cool too ... ;)

PKS
PKS India
11/21/2008 12:45:55 AM #

Hi All,

I am using this compression module for compressing the Web Resources but recently I ran into one problem when the site became SSL enabled. All of the sudden, none of the ASP.NET Menus were working. Whenever I hover mouse over ASP.NET menu, there will be one dialog box shown up with message as: Do you want to save the file? <WebResource.axd>

I have no idea why is this happening only for SSL enabled sites. If anyone has any idea, please let me know.

Thanks in advance,
PKS

Kevin L. Kitchens
Kevin L. Kitchens United States
12/1/2008 8:26:35 PM #

How do I implement this in my web application???  Sorry to sound like a doofus, but I'm using Blowery right now and it's configured in my web.config.  I got the .cs compression class, but not quite sure where to compile it.  TIA.

Ali &#214;zg&#252;r
Ali Özgür Turkey
2/4/2009 5:19:46 PM #

I'm using a composite compression module derived from BlogEngine.NET and HttpCompress. In our project we also use Anthem.NET. I explicitly specified ContentType for Anthem.NET responses in order to be able to excluded that kind of responses from being compressed. But I realized that moving compression from PostReleaseRequestState to the PreRequestHandlerExecute causes some problems, specifically compression occures before Anthem.NET sets ContentType to the explicitly set value.

I think PreRequestHandlerExecute may not be the right place to put compression.

Pingbacks and trackbacks (6)+

Comments are closed

About the author

Mads Kristensen

Mads Kristensen
Program Manager at the Microsoft Web Platform team and founder of BlogEngine.NET.

More...

Month List

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer’s view in any way.