In part 1 we looked at integrating a javascript compressor as part of our build process to shrink javascript files for production (at the time, I didn’t realize that it would lead to a series). Its worth mentioning that if you go down that path, you should really also merge all your files together so that they can be pulled down from a single request – but that’s up to you to figure out how (come on, merging x files together isn’t hard now!)
Today we’ll write a simple HttpModule that’ll help us meet a number of YSlow recommendations. YSlow, created by Yahoo, is a Firebug addon (which itself is a [must have] addon to Firefox)) which identifies potential performance issues with your site. Here’s a screenshot that shows the sort of things YSlow can help identify.
With just a few lines of code, 2 of those F’s can be solved – removing ETags from the server response and adding expiry headers.
public namespace CodeBetter.Web
{
public class YSlowHttpModule : IHttpModule
{
private static readonly List<string> _headersToRemove = new List<string> { "X-AspNet-Version", "X-AspNetMvc-Version", "Etag", "Server",};
private static readonly List<string> _longCacheExtensions = new List<string> {".js", ".css", ".png", ".jpg", ".gif",};
public void Init(HttpApplication context)
{
context.EndRequest += new EventHandler(context_EndRequest);
}
private void context_EndRequest(object sender, EventArgs e)
{
var context = HttpContext.Current;
_headersToRemove.ForEach(h => context.Response.Headers.Remove(h));
var extension = Path.GetExtension(context.Request.Url.AbsolutePath);
if (_longCacheExtensions.Contains(extension))
{
context.Response.CacheControl = "Public";
context.Response.Expires = 44000; //slightly over 1 month
}
}
public void Dispose() {}
}
}
Simply register the module in your web.config and you’re good to go:
<add name="YSlowHttpModule" type="CodeBetter.Web.YSlowHttpModule, YourAssemglyName"/>
Its worth pointing out that this module will only work with IIS 7.x’s integrated pipeline.
The code does two things. First, it strips out extra headers – we only really need to strip out the Etag header from YSlow’s perspective, but I like to remove the other useless stuff as well. The second is that it adds a very long cache control policy to our assets.
You may be wondering why ETags should be removed beyond mindlessly following YSlow’s recommendation. ETags are inferior to a long lasting cache control policy, and as such, it does nothing more than add overhead to each request. Unlike far-reaching expiry headers, ETags still require an open connection to your server to see if the content has changed.
A problem with our YSlowHttpModule is that we’ve told anyone in the request chain that they can cache our static files for a month. What if we need to change an image, or update our css? In part 3 we’ll solve that issue by placing file hashes in our querystrings.

Then use this to handle your stylesheets:
http://blog.waynebrantley.com/2009/12/ultimate-automatic-stylesheet-combining.html
@karl
You don’t have to reference HttpContext.Current, check out my solution here: http://www.bugwriter.me/2010/01/removing-unnecessary-http-header-server.html
@Herman:
I initially tried that, but there are conditions where HttpContext.Current is null within PreSendRequestHeaders. I don’t remember what those conditions were, but I did look into it and found no solution – though plenty of other people noticed the same thing.
I think you should override the PreSendRequestHeaders instead of EndRequest. Also, here is a example to make it work locally (running in Cassini).
http://www.bugwriter.me/2010/01/removing-unnecessary-http-header-server.html
@karl
My bad, I can’t read code.
Not a big fan of getting rid of the response headers.
@Paco
You could add the expiry headers if you make asp.net handle requests to .css, .js and images in “classic mode”…but I don’t think you can remove headers.
@dario-g
Did a quick search and didn’t find anything. Good work..I’ll finish my series which’ll teach the how.
@Micheal
It avoids the hit in the first place. I tend to agree that the big win is getting a 304 from the request – whether thats determined by the server or the client isn’t AS important.
Why is this specific for IIS7?
Checkout mscd.codeplex.com
I’d suggest that the long expires headers isn’t quite *SO* necessary. As long as your handlers, or web-server support the If-Modified-Since request header, you can constantly bump out say, a day, or even 12 hours with a minimal effect.
I’m not removing them all, just 4 useless ones. Are any of the 4 particularly worrying to you?
I am not so sure that removing all response headers is a great idea…
Great stuff… I also like Google’s Page Speed plugin for Firebug.
I like how no only does it recommend things to change, but in some cases actually provides you with what to change.
For example, it will auto-minify JavaScript and Images for you and tell you how much size you’ll save
I like to have a combo of YSlow & Page Speed. Thanks again for your article. Great stuff as always