Tuesday, April 19, 2011

Optimizing CSS in ASP.NET MVC

 The CSS files in a web application don't change very often. For this reason they are usually cached by the browser to improve performance. These are the steps that I take to optimize CSS files from my web apps. Although I'm using ASP.NET MVC this can be applied to any web app written in any language on any platform.

  1. Combine all CSS files into a single file.
  2. Replace tokens.
  3. Minify the combined CSS file.
  4. Cache the CSS file on the server.
  5. Name the CSS file with a timestamp.
  6. Compress the output to the browser.
  7. Set the browser cache date to a far future date.

1. Combine all CSS files

To make a project more manageable you might want to maintain multiple CSS files. I setup a pattern so that I can easily combine the files without changing the code. For example, precede each CSS file with a 2 digit number that dictates the order in which they should be combined:

01_main.css
02_forums.css
03_footers.css

The first time your CSS file is requested your code will read all the CSS files from the CSS folder and combine them in alphabetical order.

Complications: Sometimes you might want another css file added in there based on browser. For example, you might have another CSS file you only want combined with the "numbered" css files if the browser is IE6 or IE7. In cases such as that you would create several different combined files on the server and serve up the appropriate one at runtime.

At the end of this step you have an in-memory string representing the combined CSS files.

2. Replace tokens

This is an optional step but you might have image URLs in your CSS that reside on different subdomains depending on your environment. For example, in my dev and test environments the images are in a path off the dev and test domains. However, in production, the images are on one or more sub-domains in order to parallelize the image downloads in the browser.

If your configuration follows this pattern then you might want to put tokens in the CSS files which are replaced at runtime depending on the environment.

3. Minify the combined CSS file

Run the combined CSS file through a minifier to make it as small as possible. This file is only going to be read and used by a browser so pretty formatting and extra whitespace is no longer needed.

If you're developing on the .NET platform then you can use the YUI Compressor for .NET to minify your CSS. Once you've included the library in your project it will require a single line of code:

string minifiedCSS = CssCompressor.Compress(combinedCSS.ToString());

4. Cache the CSS file on the server

Once you've built and minified the CSS file (steps 1 and 2) you will probably want to cache this on the server so you don't have to do this again. How you cache it is up to you. ASP.NET has a cache object you can put it in or you can use a static variable, or singleton etc.

5. Name the CSS file with a timestamp

In the <head> section of your HTML you need to reference your CSS file. I do this by referencing a controller and action with a timestamped filename as the single parameter. For example, the served up HTML will look something like this:

<head>
...
<link href="/site/css/20110418095157.css" type="text/css" rel="stylesheet" />
...
</head>

In my .master file for the site or _Layout file for my razor views I have the following code:

<link href="<%:MySingleton.Instance.GetCssFileName() %>" type="text/css"
rel="stylesheet" />

The first time that the static MySingleton.Instance.GetCssFileName() function is called it pulls all of the relevant CSS files and finds the one with the most recent date. Using that date it constructs the filename using the following .NET format pattern: yyyyMMddHHmmss. This computed filename is also cached so that it is only ever created once.
 
6. Compress the output to the browser
 
I do this by telling IIS to compress all output to the browser. How to enable compression on IIS6 and IIS7. If you don't want to enable compression on IIS then you can add a filter (attribute) to your action.
 
7. Set the browser cache date to a far future date
 
In the MVC Action that returns the CSS file set the HTTP headers to be far in the future. You never have to expire this CSS file because the name will change which will force a fresh request of the CSS files after one of them has been modified. Here is the type of code you would add to the start of your Action method to set the HTTP headers appropriately.
 
TimeSpan duration = TimeSpan.FromSeconds(15552000);
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetExpires(DateTime.Now.Add(duration));
Response.Cache.SetMaxAge(duration);
 
 
 

3 comments:

  1. Check out Chirpy, it's a VS plugin that automates almost all of this with every save:
    http://chirpy.codeplex.com/

    ReplyDelete
  2. Looks like .NET 4.5 will have most of this in it when it comes out. Here's Scott Guthrie's post about it:
    New Bundling and Minification Support (ASP.NET 4.5 Series)
    weblogs.asp.net/.../new-bundling-an

    ReplyDelete
  3. I'm using Chirpy, it's great! But I want to change the filename of the combined file on Publish, either with a timestamp or even better, the revision number from my svn-repository.
    Any ideas?

    ReplyDelete