YSlow and ASP.NET: 100 points "A" grade is possible

Just in case if somebody doesn’t know what YSlow is? YSlow is Firefox add-on integrated with Firebug. It analyzes web pages and tells you why they are slow. You can run it on you project and see your grade. You can do some easy improvements and maybe even get D grade. However to get higher then D grade becomes a real challenge. Below I will describe how I got A grade (100 points) on my web blog application.

First of all you don’t want measure performance with any external stuff which is beyond of your control such as Google Ads, Google Analytics, Dotnetkicks. So, you need to make a query string switch, which is going to disable/hide all this stuff. In my case it is PageType=NoExternal, so full URL would be http://www.karpach.com/default.aspx?PageType=NoExternal. Also you need to add CDN setting for YSlow. My personal CDN is http://karpach.appspot.com/cdn/. Now if you measure performance of my default.aspx?PageType=NoExternal, you would see that it is A (100). Now let’s see how achieved this.

YSlow

1. Make fewer HTTP requests.

First of all you need to combine stylesheets and javascripts files in one file. You should do this only for your production build using MS Build tasks.

<ItemGroup>

  <TextFiles Include="*.css" Exclude="global.css"/>

</ItemGroup>

<Exec Command="echo y| type %(TextFiles.Identity) >> global.css"/>

This way you can combine your custom css and javascript files.

Some developers would ask: What about WebResource.axd? New AJAX Control Toolkit has ToolkitScriptManager, which can combine most of your WebResource.axd files. It also gzip them (this is important for later sections).

Next, you need to create CSS Sprites. You can read CSS Sprites: Image Slicing’s Kiss of Death article to get better idea what is CSS Sprites. Article doesn’t discuss how to create sprites for repeated backgrounds. The idea is to group such backgrounds. For example, all vertical repeating backgrounds in vbackground.gif and all horizontal repeating backgrounds in hbackground.gif. Then you can use background-position: -OffsetPixels, 0px for vertical repeated backgrounds and background-position: 0px, -OffsetPixels for horizontal repeated backgrounds. Here is the example of CSS Sprite from my blog application.

CSS Sprite

2. Use CDN (Content Delivery Networks)

Content Delivery Networks are expensive. I googled for something cheap and found following article 10 Easy Steps to use Google App Engine as your own CDN. Of course you need to do a few adjustments in order to make it a perfect CDN.

First of all default cache expiration is 600 sec. However, YSlow wants it to be at least 7 days. So, in your app.yaml after api_version add following line:

default_expiration: "7d"

Again you can use msbuild to modify stylesheet file to use CDN. Here is how I did this:

<Import Project=".\References\MSBuild.Community.Tasks.targets" />

<Target Name="Release">

  <FileUpdate Files="$(OutputPath)styles\basic.css" Regex="\.\.\/images/([^\)]*)" ReplacementText="http://karpach.appspot.com/cdn/images/$1" />

</Target>

You need to put on CDN all your images, styles and JavaScript files. Here is a cache problem comes up. What if some visitor accessed your application just before you released new version, which has modified stylesheet and some modified images. This somebody is going to have cached version of stylesheet and images for 7 days, so he is not going to see changes until his browser cache expires. This is not good. If question like this comes up then it is time to see what yahoo did on their web site. For images you can see that they have date stamp and version in image name. For example: trough_2.0_062308.gif. It makes sense. If you modify one of your css sprites just rename it manually with new date stamp and version.

However, you probably don’t want to use the same approach for your stylesheet file. First of all you want to track all versions of your stylesheet in version control system (in my case subversion). For images it doesn’t really matter, since you don’t change them too often. My idea is to have just one physical css stylesheet in my case basic.css. However on a page I would use it like this: basic_14102262.css, where 14102262 is current version of application. Then you can use url rewrite techniques, so basic_14102262.css redirect to basic.css. A user who has cached version of basic_14102261.css would be forced to load new version basic_14102262.css, since file name changed.

Google Aps engine makes url rewrite easy. Modify your app.yaml to have the following:

- url: /cdn/styles/basic_\d*\.css
  static_files: cdn/styles/basic.css
  upload: cdn/styles/basic\.css

3. Add an Expires header

Everything under CDN would already have it. For all other files I created HttpModule:

private readonly static string[] CACHED_FILE_TYPES = new string[] { ".jpg", ".gif", ".png",".css" };

public void Init(HttpApplication context)

{           

    context.AcquireRequestState += new EventHandler(context_AcquireRequestState);

}

 

void context_AcquireRequestState(object sender, EventArgs e)

{

    HttpContext context = HttpContext.Current;

    if (context != null && context.Response != null)

    {               

        string fileExtension = Path.GetExtension(context.Request.PhysicalPath).ToLower();

        if (context.Response.Cache != null && Array.BinarySearch<string>(CACHED_FILE_TYPES, fileExtension) >= 0)

        {                   

            HttpCachePolicy cache = context.Response.Cache;

            TimeSpan duration = TimeSpan.FromDays(365);                                       

            cache.SetCacheability(HttpCacheability.Public);

            cache.SetExpires(DateTime.Now.Add(duration));

            cache.SetValidUntilExpires(true);

            cache.SetNoServerCaching();

            cache.SetMaxAge(duration);                                        

        }

    }

Ideally you won’t need this module, since all qualified files should be under CDN.

4. Gzip components

You can read Building a GZip JavaScript Resource Compression Module for ASP.NET article how to create gzip http module. Just keep in mind that IE6 doesn’t have 100% gzip support.

5. Put CSS at the top.

This is an easy one. It’s just common sense. Keep this in mind when you are developing server controls. Use Header.Controls collection to add your stylesheet include.

6. Put JS at the bottom.

Sometimes it’s really difficult to follow this rule. For example, I always want jquery script include at the top, so I don’t need to worry if library script is loaded or not. Anyway for your custom scripts use inline script to check if library is loaded and then use it.

For example:

Library.js

function DoSomething()

{

 

}

And then inline script:

<script>

    if (typeof (DoSomething) == 'undefined')

    {

        alert('Library is not loaded yet');

    }

</script>

7. Avoid CSS expressions.

CSS expressions work only in IE, so if you build cross browser application you should not use them anyway. The only good use for them is min-width/min-height hack for IE6.

8. Make JS and CSS external.

This is also common sense. Just read what Yahoo suggests http://developer.yahoo.com/performance/rules.html#external

 

9. Reduce DNS lookups.

Try to copy external resources (images, javascripts) to your websites.

For example, my blog has a link to w3 validation service. Originally icon for it was located on w3 schools web site, so I copied it to my project. Now this icon is local, so I have 1 less dns lookup.

10. Minify JS.

Do minification during production build using MS Build. I using YUI compressor for this purpose. Here is an example of MS Build script.

<Target Name="Compress">

  <Message Text="Create temp files ..." />

  <Copy SourceFiles=".\$(ProjectName)\Javascript\ColorPicker.js" DestinationFiles=".\$(ProjectName)\Javascript\ColorPicker.js.full"/>

  <Copy SourceFiles=".\$(ProjectName)\Styles\ColorPicker.css" DestinationFiles=".\$(ProjectName)\Styles\ColorPicker.css.full"/>

  <Exec Command="java -jar yuicompressor-2.4.2.jar --type js .\$(ProjectName)\Javascript\ColorPicker.js.full >.\$(ProjectName)\Javascript\ColorPicker.js"/>

  <Exec Command="java -jar yuicompressor-2.4.2.jar --type css .\$(ProjectName)\Styles\ColorPicker.css.full >.\$(ProjectName)\Styles\ColorPicker.css"/>

</Target>

11. Avoid Redirects

You should not have a problem with this. Just read what yahoo recommends.

12. Remove Duplicate Scripts

Be careful when you are developing custom server and user controls. Make sure that for multiple controls of the same kind include script just one time.

13. Configure ETags

Cache module provided above and CDN should take care of this issue. Just keep in mind that ASP.NET has special method for ETags:

Response.Cache.SetETag

P.S. The article was mentioned on Channel 9 this week: Martin Woodward, MVP Summit, Web Perf, Show Off, and a VSTS Pep Talk

P.S.S YSlow and ASP.NET: 100 points "A" grade year 2012 update article

kick it on DotNetKicks.com


Posted on Saturday, February 28, 2009 by | Comments (8) | Add Comment

Comments

Gravatar

Re:YSlow and ASP.NET: 100 points "A" grade is possible

Hey thanks, I can now save some bandwidth by browsing your blog without ads!

Posted on 3/2/2009 6:30:13 AM by theclown #
Gravatar

Re:YSlow and ASP.NET: 100 points "A" grade is possible

Once you combine all your css and javascript using msbuild, do you also replace the references in your html? How do you do this?

Posted on 3/2/2009 2:16:17 PM by Mark #
Gravatar

Re:YSlow and ASP.NET: 100 points "A" grade is possible

same question as theclown, how do you update your view to use the new combined name for your css/js?

Posted on 3/3/2009 2:09:56 AM by recursieve #
Gravatar

Yes, I do replace references based on web.config value.

<appSettings>
 <clear/>
 <add key="CDNPath" value="http://karpach.appspot.com/cdn/" />
 <add key="IsProduction" value="true" />
</appSettings>

You can have two PlaceHolder-s in your head section. One for your local development server and one for production server.
Just hide and show correct section base on web.config value.

Gravatar

Re:YSlow and ASP.NET: 100 points "A" grade is possible

We have only A scores except for the CDN thing, and it's all right out of the box with our Ajax Library ;) - http://ra-ajax.org

Posted on 3/3/2009 7:47:45 AM by Thomas Hansen #
Gravatar

Thomas,
As far as I can see you do not gzip page itself :-)

Gravatar

Re:YSlow and ASP.NET: 100 points "A" grade is possible

You are just fooling yourself into thinking you have an A but removing everything that would bring the grade down.

By not measuring the performance with external resources you've completely invalidated the tests. They may take you down to an F due to load times, size, not gizipping, not setting a far future expires header, etc. If this is the case you'd want to reevaluate their use.

Posted on 3/12/2009 11:31:57 PM by Paul #
Gravatar

First of all even if I turn everything on a grade is C(75), which is not bad at all.

Technically you are right. However on another hand I can't optimize Google Ads, Google Analytics or Dotnetkiks (this is what I am disabling). Since I can't optimize it, why should I count it? Anyway this staff is minor and doesn't affect user experience of how fast my blog shows up. Who cares if ads shows up later, anyway they are rendered in iframe.

Gravatar

Re:YSlow and ASP.NET: 100 points "A" grade is possible

Ah, I have been stuck at 99 in ySlow for one of my sites and it's driving me nuts. I have no idea where to make up the 1 point!!!!

Posted on 5/5/2010 10:07:58 AM by Sean #
Gravatar

Which category does not have A grade?

Gravatar

Re:YSlow and ASP.NET: 100 points "A" grade is possible

Hi,
I'm trying to use your HttpModule for add expire headers to statics files, but seems that request for images,(png, gif,jpg) are not handled by HttpModule,(only aspx,adx, etc.).
Can you help me?

Posted on 9/3/2010 7:18:57 AM by Fausto #
Gravatar

If you are using IIS6 then you need to add extension mapping in IIS. See http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/5c5ae5e0-f4f9-44b0-a743-f4c3a5ff68ec.mspx?mfr=true. You can do this in IIS7 too, but there is a simpler solution.

Gravatar

Re: YSlow and ASP.NET: 100 points "A" grade is possible

Hi,

The 3 point "3. Add an Expires header".

Where I have to call the "Init" method and how I get the "HttpApplication context" parameter that needs to be passed to the method.

Thanks,

Omar

Posted on 2/15/2012 2:36:16 AM by ramo2712 #
Gravatar

In Visual Studio Solution Explorer do right click and select add new item. On left side select web and then on right side click on ASP.NET Module. Visual Studio would create a starting point for your HttpModule. Init method would be there. Since it is HttpModule it would be called by ASP.NET framework, just don't forget to register it in web.config.

New Comment

Your Name:
Email (for internal use only):
Comment:
 
Code above:

Categories

Valid HTML5