Sponsored By Aspose - File Format APIs for .NET

Aspose are the market leader of .NET APIs for file business formats – natively work with DOCX, XLSX, PPT, PDF, MSG, MPP, images formats and many more!

ASP.NET Performance – Part 2 – YSlow

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.

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

13 Responses to ASP.NET Performance – Part 2 – YSlow

  1. Herman says:

    @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

  2. karl says:

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

  3. Herman says:

    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

  4. fschwiet says:

    @karl

    My bad, I can’t read code.

  5. Not a big fan of getting rid of the response headers.

  6. karl says:

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

  7. Paco says:

    Why is this specific for IIS7?

  8. dario-g says:

    Checkout mscd.codeplex.com :)

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

  10. karl says:

    I’m not removing them all, just 4 useless ones. Are any of the 4 particularly worrying to you?

  11. fschwiet says:

    I am not so sure that removing all response headers is a great idea…

  12. Elijah Manor says:

    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