Introduction

There are a lot of ways to improve performance in web applications. One of the simplest but most effective methods is to cache images on the client. In this article I would like to show how we implemented image caching for our DotNetNuke website.

The Problem

When I was building the website http://www.software-architects.com
and I used a lot of images in the CSS style sheets to display background images for menu items. After
transferring the files to our web server I tested how much traffic a request to our start page would produce with Microsoft Network Monitor. This is a
tool to allow capturing and protocol analysis of network traffic. You can download it from the Microsoft Download Center.

With Microsoft Network Monitor 3.1, I recorded a call to http://www.software-architects.com. As a result I got 20 requests to 20 different files to display one single page. Microsoft Network Monitor shows that
approximately half of the requests are required for the menu images.

There are two different ways to avoid this problem. On one hand you can tell IIS to cache images on the client, and on the other hand you can do this directly in ASP.NET (which is a bit more complicated).

Caching Images in IIS

Caching in IIS is very simple. Select a folder in the left pane or a single file in the right pane and open the
Properties dialog.

That's it! IIS tells the client with the "Cache-Control" header that the content may be cached on the client. The "Expires" header contains the expiration date. So the client knows that after this date it has to ask the server for new content.

This approach works very well if

you can place all your images and other cachable files in one or a few folders,

and, most importantly, you have access to IIS.

Both conditions are not fulfilled in our case. In our DotNetNuke project images are spread
across multiple folders so it would be quite complex to configure IIS. And more importantly, our hosting provider does not give us access to IIS. Thus I had to look for another solution.

Caching Images with a Custom HttpHandler

The first thing I had to solve was bypass IIS to get the request to ASP.NET. I decided to write a custom
HTTP handler which listens to files with the paths *.gif.ashx, *.jpg.ashx and *.png.ashx. You can find a good article about IHttpHandler at the APress website: Use local scope to improve performance.

I built a new class library project in Visual Studio with a class CachingHandler, which is responsible for handling requests to images. CachingHandler implements the interface IHttpHandler like the Page class does. The interface provides the property IsReusable and the method ProcessRequest.

IsResuable indicates if another request can reuse the HTTP handler. This means, we have to guarantee that the ProcessRequest method is thread-safe.

ProcessRequest does the real work. It gets the current context and is responsible for sending the result to the client.

We want our http handler to send a file to the client. As we are listening to files with the paths *.gif.ashx, *.jpg.ashx and *.png.ashx, all we have to do is to remove the ".ashx" from the request path to get the file we want to send to the client. Besides we extract the filename and the extension from the file.

In the next step we load the configuration for the CachingHandler from the
web.config file. Therefore I built a class CachingSection (which I will show a bit later), which contains a property
CachingTimeSpan and a collection FileExtensions, which knows the content type for each file extension. With help of this config class we configure the
HttpCachePolicy object of the response:

SetExpires tells the client how long the content should be valid.

SetCacheability tells the client who is allowed to cache the content. We set the the cacheability to public. This means that the response is cacheable by clients and shared (proxy) caches.

SetValidUnitExpires specifies whether the ASP.NET cache should ignore HTTP Cache-Control headers sent by the client that invalidate the cache.

Finally we add the content-disposition header to the response to tell the client that it should open the file in the browser (inline). Additionally we set the filename to the name without the extension
.ashx, because this is the name that will be displayed when you try to download the file. Then we use WriteFile to send the file to the client.

Defining Custom Configuration Sections in web.config

In the HTTP handler we use a custom class to read some configuration information from the
web.config file. Therefore I built the class CachingSection derived from ConfigurationSection. In this class I implemented a property CachingTimeSpan which holds a TimeSpan value for the time to cache objects on the client, and a property FileExtensions which holds a collection of FileExtension objects. To map these properties to elements in web.config you simply have to add a ConfigurationProperty attribute to each property, which can be set in web.config.

To support not only single values but also collections we have to implement a class derived from ConfigurationElementCollection. In our sample we need a collection to configure a list of valid extensions with their corresponding content types.

All we have to do now is to add a configuration section to our web.config. In the configSections tag we add a new sectionGroup with the name SoftwareArchitects. In this group we add a section named Caching. The attribute type specifies the class and the assembly of our CachingSection class. Of course we have to add the assembly with the CachingSection class to the
bin folder of the web application. Then we can add a new tag with the name of the group to the configuration tag. Inside of the group we add a new tag with the name of the section, and in this section all properties we have defined in the CachingSection class are now available.

Now there is only one last thing missing until we can use the CachingHandler. We have to add it to the
httpHandlers section in web.config. There we have to add an entry for each file extension we want to map to our
HTTP handler. I decided to support images with the extensions .gif, .jpg and .png. So I added a handler for the paths *.gif.ashx, *.jpg.ashx and *.png.ashx. In the
type attribute I specified the class and assembly of the HTTP handler. Of course the assembly must be placed in the
bin folder as well.

You could also use other file extensions like *.gifx. But to do so you need to have access to IIS to configure the new extension to be
handled by aspnet_isapi.dll. As I do not have access to IIS of our hosting provider, I had to use *.ashx, because it is already mapped to aspnet_isapi.dll.

Finally I added the extension .ashx to all images in the web site (in .css files and .aspx files). When I monitored a request to the main page of http://www.software-architects.com again, the first request still generated 20 requests to the web server but from the second request on it took only 7 requests to load the page, because the images were cached on the client.

You can see how it works on our website at http://www.software-architects.com/devblog/2008/01/19/Caching-in-ASPNET. Right-click on an image and open the
Properties dialog. You will see that the URL ends with .ashx. When you right-click on an image and select "Save Picture as..." the suggested filename does not include the extension
.ashx because of the content-disposition header.

Of course you can use the handler for other file types like JavaScript files or
CSS files, too. So you could reduce the number of requests again.

Testing the CachingHandler

You can easily test the caching of images with a simple web site. I added a web site project with the name CachingWebSite to the Visual Studio Solution with which you can try how it works (download complete solution). On the one hand, the web site contains a page
Default.aspx which contains an image tag. You can see that the image source ends with .ashx.

In the web.config of the web site I inserted a custom section to configure the CachingHandler and an
HTTP handler for each extension, exactly as explained above. Furthermore I added the trace tag to the
system.web section to trace every request to a file.

When I start my web site project I see the Default.aspx page with the logo, which is defined in Default.aspx, and with the background image, which is defined in the stylesheet.

To view the trace I opened a new tab in IE and replaced Default.aspx with Trace.axd in the URL. The trace shows that four requests were necessary to display the page Default.aspx. For the first request and every time the users hits F5, all files are sent to the client.

When I switch back to the first tab I have three possibilities to reload the page. I could

press F5

click the "Reload page ..." link

click the "Reload page ..." button

Pressing F5 would reload all the content, whereas clicking the link or the button would only reload content which is not cached on the client. I clicked the link and the button for the following screenshot. As you can see there were only requests to
Default.aspx and Style.css added in the trace.

If the user navigates to a page via a hyperlink or does a post back only, files which are not cached on the client are requested from the server. Requests 5 and 6 were caused
by clicking the link whereas requests 7 and 8 were caused by clicking the button.

License

Share

About the Author

Hi, my name is Karin Huber. Since 1998 I have been working as a developer and IT consultant focusing on building database oriented web applications. In 2007 my friend Rainer and I decided that we want to build a business based on COTS (component off-the-shelf) software. As a result we founded "software architects".

These days we are offering our first version of the time tracking software called 'time cockpit'. You can find more information at www.timecockpit.com.

Comments and Discussions

I updated the file with same name but the bigger image but it shows me the earlier image instead of new image. Then I did hard refresh(ctrl+F5) then it reloaded the new image. Any solution to this problem.

That's exactly what all this article was about. The caching is enabled to prevent reloading the resource from server before the content expired (value when cache will expire was set via Response.Cache.SetExpires(DateTime expiry).

So if no caching is needed, set the cacheability to false or skip adding the cache-control headers. (Which have been set via Response.Cache.Set...)

This works well for IIS6, but shouldn't be needed in IIS7. Instead, you can use the clientCache[^] element in your web.config file to configure the caching headers for a folder or, using the <location> element, an individual file.

Hi Karin, your article was what I looking for! (at first glance).
But when I tried the first option (Caching images in IIS), simply it doesn't work as expected.
Using Chrome's Developers Tools I capture the trafic between server and client, and surprise...:
In the network tab, it shows Chrome makes the requests for every image to compare with the cached image, with status 304 (Not modified), so the traffic seems to be the same that before.
You tried this option? Wich client you used?

First of all, thanks for the code, I've been using a similar approach for some time now (and not just for images); second, while it's ok to use file extensions, I think that obtaining a given file mime-type and then checking the latter to see if to cache the data or not may be a better idea; basically, you may add some code like the one shown here[^] to "sniff" the mime type directly from the file and then, using it, decide if the file should be cached or not

1. using a .net ISASPI_REWRITTER like "Intelligencia.UrlRewriter" , That this is what i using, if you dont have a ISPAI Extansion on your shared hosing server.
all you need to do is add a new rule like this.

<rewrite url="(.+).jpg" to="$1.ashx" />

and it's will automaticly will "replace" all the needed file's , all you need to do is adding the ruls of the extansion you need.
BUT!
1 probleam with this thing, i't will only create a cache for files that in the page scope of the http requet, if lets say that a css will ask for an image, then it's no good, but you can alwayes set the css with the image.jpg.ashx extansion.

2. using the ISAPI Filter, adding the rule like before, the scope is all the HTTP Requests, so you can rest your mind, and you are done, with the image chacing.

another greate thing about that, is that you can crete ruls for spacial directory and pass cache time on the rule, like:

and evry folder can now get a param holding the cache time for it, so you can set chace for stuff that you know you are going to remove soon..
and becuse we using a HTTP Hendler, we can set a "LAST_VERSION_UPDATE" of the site in the config, so if we changing somthing dramaticly, it can refrash the file.

thanks for this informative article. its true that apart from its benefits, ASP.NET has some limitations as well which ends up in performance issues. i am a big admirer of distributed caching to solve the performance issues.