I’ve been tweaking the performance of BlogEngine.NEXT today using my favorite tool: YSlow for FireBug. One of the things YSlow checks for is the expires HTTP header for static content such as images, script files and style sheets. Since BlogEngine.NET has always used custom HTTP handlers for serving scripts and stylesheets, only the static images have been a problem.

The problem

The problem is that with images on hosted environments on IIS 6, it’s impossible to control the serving of them without redirecting them through an HTTP handler. That’s not a good idea for several reasons:

  • It adds unnecessary overhead by going through the ASP.NET ISAPI
  • You need to add custom code to handle the requests
  • You need to change the URL from .gif to .gif.axd or similar

Here is what YSlow finds on my website that needs the expires header set to a far future date:

As you can see, it is all my static images that lacks the expires header.

The solution

If you run IIS 6 there is no good way of adding an expires header to images unless you have control over the IIS. If your site is hosted then you probably have no control at all. If you are using IIS 7 however, you can very easily add the header in your web.config’s system.webServer section like so:

 <clientCache httpExpires="Sun, 29 Mar 2020 00:00:00 GMT" cacheControlMode="UseExpires" />

What happens is that all static content will now have an expires HTTP header set to the year 2020. Static content means anything that isn’t served through the ASP.NET engine such as images, script files and styles sheets. This is one of the very easy tricks that will increase the performance of your site as well as your YSlow score.


Comment by Mark S. Rasmussen

Just make sure you have a strategy for changing those static files, in case they need to be changed before the year 2020. While expires gives best performance, a 302/etag solution might be better if - albeit static - the files need to be changed at some point. Otherwise you'll just have to make sure to generate new resource url's when they change.

Comment by Mads Kristensen

Good point. Static images normally don't change, but if they do, always change the URL to the image. The easiest way is to add a version number as a URL parameter to the images.

Comment by Phil Scott

Came here to say exactly what Mark said. That's one thing this example really, really needs to say. YSlow score isn't for everyone. Yahoo created it for sites that get Yahoo like traffic. The trade off for the maintenance vs performance for a blog site might not be worth it. I don't worry about the CDN, Expires or Etags complaints for most work that I do. I'm far more likely to cause myself problems (or cost with CDN) than to have to worry the performance of sending a 304 response to users. I just wish YSlow didn't rank that one so high up.

That being said, I'm just a huge nerd so I like seeing a little A on my YSlow score. With BlogEngine it's pretty easy to take care of changing the url with every change - i just put the version number in my theme name.

Phil Scott

Comment by Ben Amada

After downloading the latest build of BE, when trying to launch it in VS2008 with F5, I was getting a 500.19 error from IIS7. It turns out the new <staticContent> section in the web.config was causing this error on my machine (Vista). On my machine, I had the following setting in my applicationHost.config file in %windir%\system32\inetsrv\config:

<section name="staticContent" overrideModeDefault="Deny" />

I needed to change Deny to Allow, and everything started working. I read somewhere 'Deny' for staticContent might be the default on Vista/IIS7, but not Win2008/IIS7.

Comment by Carsten Petersen

It also seams like YSlow is complaining on ETag's on images (in general). It is possible to fix this, by adding empty ETag in web.config (gives A score on ETag's).

<add name="ETag" value=" "/>

(This doesn't override custom ETag's)

Carsten Petersen

Comment by Will Stanley

Very helpful, but since my website is in a development period and things may change after going live I fished around to see if I could set the expiry to a dynamic value in the web.config.

I found this link.. .


The end result was that the following was added to the web.config file....

<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="30.00:00:00" />

If I am mistaken let me know.



Will Stanley

Comment by DC25

Useful info, although I'm still stuck on iis 6 atm :(. Just a quick note, according to the HTTP/1.1 RFC, the expires header shouldn't send a date more than a year in advance (see section 14.21). I doubt it does any harm and I'm certain most browsers will handle it correctly but thought it was worthwhile mentioning.

As for versioning issues, I tend to use the update date as a query parameter so that I don't have to remember version numbers, eg. image.gif?20090529.

Also depending upon your normal visitor profile you may get just as much mileage from setting the expires header just a week in the future which may mitigate some of the versioning issues.

Comment by EricSB

Thanks for the great information! I've been wondering about this but just haven't taken the time to dig into the issue. Thank you for posting this, I've added this to my favorites and will keep it around as a very useful tidbit. :)