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

by Mads Kristensen 17. September 2007 00:14

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.

* Only $4.95/month ASP.NET & Windows 2008 + IIS 7 Hosting! FREE SQL Included

Tags: , ,

Server-side

Comments

9/17/2007 1:20:23 AM #

Richard O'Donnell

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!

Richard O'Donnell United Kingdom |

9/17/2007 1:25:10 AM #

Mads Kristensen

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

Mads Kristensen Denmark |

9/17/2007 7:08:51 AM #

Dave Transom

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?


Dave Transom New Zealand |

9/17/2007 7:25:59 AM #

Franklin Rau

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

Franklin Rau United States |

9/17/2007 1:46:20 PM #

Ted Jardine

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).

Ted Jardine Canada |

9/17/2007 11:13:26 PM #

pingback

Pingback from mhinze.com

10 Links Today (2007-09-17)

mhinze.com |

9/18/2007 1:38:00 AM #

Brad

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.

Brad United States |

9/18/2007 1:44:23 AM #

Simone Chiaretta

<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 Italy |

9/18/2007 8:46:34 PM #

Simone Chiaretta

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

Simone Chiaretta Italy |

9/18/2007 10:34:02 PM #

Mads Kristensen

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.

Mads Kristensen Denmark |

9/19/2007 1:43:09 AM #

michael

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)

michael United States |

9/19/2007 2:10:07 AM #

Mads Kristensen

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.

Mads Kristensen Denmark |

9/19/2007 4:02:30 AM #

michael

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.

michael United States |

9/19/2007 4:15:43 AM #

B. Crais

Just what I was looking for...thanks!

B. Crais United States |

9/19/2007 10:21:28 AM #

pingback

Pingback from mironabramson.com

Compress your pages, css, js and WebResources.axd files for better performance

mironabramson.com |

9/19/2007 11:28:27 AM #

Miron Abramson

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/blog/post/Compress-your-pages%2c-css%2c-js-and-WebResourcesaxd-files-for-better-performance.aspx'>compressing pages, css, js and WebResources.axd files.</a>

Miron Abramson Israel |

9/19/2007 11:31:07 AM #

Miron Abramson

Oops, Sorry for the mes in the last comment.

Miron Abramson Israel |

9/20/2007 12:25:42 AM #

Max

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!

Max United States |

9/20/2007 3:49:02 AM #

Mads Kristensen

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.

Mads Kristensen Denmark |

9/20/2007 10:39:26 AM #

Blah

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.

Blah United States |

9/21/2007 2:18:10 AM #

Mark Jensen

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

Mark Jensen Denmark |

9/24/2007 9:33:43 PM #

trackback

Trackback from Frank's Developer Blog

Javascript and CSS caching and Compression with Webresource.axd and Scriptresource.axd Support

Frank's Developer Blog |

9/24/2007 11:59:32 PM #

pingback

Pingback from developer.franklinrau.com

Javascript and CSS Caching and Compression with Webresource.axd and Scriptresource.axd Support

developer.franklinrau.com |

9/27/2007 4:00:11 AM #

James

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?

James United States |

9/30/2007 10:53:00 AM #

trackback

Trackback from Work vs. Play

A Few Quick Wins...

Work vs. Play |

10/15/2007 5:50:57 AM #

max

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!

max United States |

10/15/2007 8:47:04 AM #

trackback

Trackback from .NET  adventures

Compress your pages, css, js and WebResources.axd files for better performance

.NET adventures |

10/15/2007 9:49:45 AM #

Miron Abramson

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

Miron Abramson Israel |

10/23/2007 12:09:06 AM #

BrandSpankingNewbie

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 United States |

10/23/2007 12:40:08 AM #

BrandSpankingNewbie

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

BrandSpankingNewbie United States |

11/7/2007 5:10:29 AM #

Ponnu

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

Ponnu India |

11/27/2007 1:22:29 PM #

Roops

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?

Roops India |

1/9/2008 6:35:32 AM #

Thomasabcd

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

Thomasabcd Denmark |

4/22/2008 5:09:28 PM #

Hassan Abdullah

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,

Hassan Abdullah Lebanon |

4/24/2008 9:40:13 AM #

lichun

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

lichun People's Republic of China |

7/8/2008 6:42:33 PM #

js

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?

js India |

10/19/2008 7:01:16 PM #

Thomas Hansen

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 ... ;)

Thomas Hansen Norway |

11/21/2008 9:45:55 AM #

PKS

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

PKS India |

12/2/2008 5:26:35 AM #

Kevin L. Kitchens

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.

Kevin L. Kitchens United States |

2/5/2009 2:19:46 AM #

Ali Özgür

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.

Ali Özgür Turkey |

Comments are closed

About the slave

Mads Kristensen Mads Kristensen
Web developer at ZYB and founder of BlogEngine.NET. More...

LinkedIn ZYB Facebook Last.fm Twitter View Mads Kristensen's profile on Technorati

The Lounge

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2008