Click here to Skip to main content
11,924,825 members (54,500 online)
Click here to Skip to main content
Add your own
alternative version


207 bookmarked

Caching Images in ASP.NET

, 1 Jul 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
One of the simplest but most effective methods to improve performance in web applications is to cache images on the client.


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


Check "Enable content expiration" and choose when your content should expire.


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.

namespace SoftwareArchitects.Web
  public class CachingHandler : IHttpHandler
    public bool IsReusable
      get { return true; }
    public void ProcessRequest(HttpContext context)

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.

public void ProcessRequest(HttpContext context)
  string file = context.Server.MapPath
    (context.Request.FilePath.Replace(".ashx", ""));
  string filename = file.Substring(file.LastIndexOf('\\') + 1);
  string extension = file.Substring(file.LastIndexOf('.') + 1);

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.
  • ContentType sets the MIME type of the response.
CachingSection config = (CachingSection)context.GetSection(
if (config != null)

    FileExtension fileExtension = config.FileExtensions[extension];
    if (fileExtension != null)
      context.Response.ContentType = fileExtension.ContentType;

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.

    "inline; filename=" + filename);

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.

namespace SoftwareArchitects.Web.Configuration
  /// <summary>
  /// Configuration for caching
  /// </summary>
  public class CachingSection : ConfigurationSection
    [ConfigurationProperty("CachingTimeSpan", IsRequired = true)]
    public TimeSpan CachingTimeSpan 
      get { return (TimeSpan)base["CachingTimeSpan"]; }
      set { base["CachingTimeSpan"] = value; }

    [ConfigurationProperty("FileExtensions", IsDefaultCollection = true, 
      IsRequired = true)]
    public FileExtensionCollection FileExtensions 
      get { return ((FileExtensionCollection)base["FileExtensions"]); }

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.

You can download the complete code for the file CachingSection.cs.

/// <summary>
/// List of available file extensions
/// </summary>
public class FileExtensionCollection : ConfigurationElementCollection

Finally we need a class for each extension, which holds a property for the extension and a property for the content type.

/// <summary>
/// Configuration for a file extension
/// </summary>
public class FileExtension : ConfigurationElement
    [ConfigurationProperty("Extension", IsRequired = true)]
    public string Extension
      get { return (string)base["Extension"]; }
      set { base["Extension"] = value.Replace(".", ""); }
    [ConfigurationProperty("ContentType", IsRequired = true)]
    public string ContentType
      get { return (string)base["ContentType"]; }
      set { base["ContentType"] = value; }

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.

    <sectionGroup name="SoftwareArchitects">
      <section name="Caching" requirePermission="false" 
        SoftwareArchitects.Web.CachingHandler" />

    <Caching CachingTimeSpan="1">
        <add Extension="gif" ContentType="image\gif" />
        <add Extension="jpg" ContentType="image\jpeg" />
        <add Extension="png" ContentType="image\png" />


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.

    <add verb="*" path="*.gif.ashx" 
    <add verb="*" path="*.jpg.ashx" 
    <add verb="*" path="*.png.ashx" 

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

<img src="/Portals/1/App_Themes/Standard/Images/LogoWithMenuBackground.png.ashx" />

On the other hand the web site contains a theme Standard with a stylesheet Style.css. In the stylesheet I use a background image. Again the image source ends with .ashx.

  margin: 0px;
  padding: 0px;
  background-image: url(Images/MenuBackground.png.ashx);
  background-repeat: repeat-x;

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.

<trace enabled="true" pageOutput="false" requestLimit="50" mostRecent="true" />

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.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Karin Huber
Software Developer software architects
Austria Austria
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

You may also be interested in...

Comments and Discussions

QuestionFirfiox 3.0.4 ASP.Net 3.5 - caching not working Pin
Mahin Gupta18-Dec-08 0:46
memberMahin Gupta18-Dec-08 0:46 
GeneralBut why use .Net when IIS do it right ?! Pin
spegase28-Sep-08 5:51
memberspegase28-Sep-08 5:51 
GeneralRe: But why use .Net when IIS do it right ?! Pin
RobKraft28-Sep-08 9:46
memberRobKraft28-Sep-08 9:46 
QuestionThanks for the article. Could you explain the .ashx suffix? Pin
RobKraft8-Sep-08 12:06
memberRobKraft8-Sep-08 12:06 
AnswerRe: Thanks for the article. Could you explain the .ashx suffix? Pin
Karin Huber15-Sep-08 2:51
memberKarin Huber15-Sep-08 2:51 
GeneralRe: Thanks for the article. Could you explain the .ashx suffix? Pin
RobKraft28-Sep-08 9:40
memberRobKraft28-Sep-08 9:40 
QuestionIIS Solution for all images Pin
kensai9-Jun-08 23:20
memberkensai9-Jun-08 23:20 
AnswerRe: IIS Solution for all images Pin
mahan11011011014-Jun-08 20:14
membermahan11011011014-Jun-08 20:14 
the iis has solution for compressing and content caching and expiration but this article is suitable for when you don't have access to your iis in the host.
GeneralGreat Article Pin
Sabarinathan Arthanari28-Apr-08 22:58
memberSabarinathan Arthanari28-Apr-08 22:58 
Questionit doesn't work in ie7? Pin
conannb25-Mar-08 1:13
memberconannb25-Mar-08 1:13 
Answer[Message Removed] Pin
Mojtaba Vali26-May-08 23:36
memberMojtaba Vali26-May-08 23:36 
GeneralDynamic Caching Pin
NandanPatel21-Feb-08 4:58
memberNandanPatel21-Feb-08 4:58 
GeneralRe: Dynamic Caching Pin
Karin Huber24-Feb-08 10:01
memberKarin Huber24-Feb-08 10:01 
GeneralRe: Dynamic Caching Pin
NandanPatel25-Feb-08 6:40
memberNandanPatel25-Feb-08 6:40 
GeneralRe: Dynamic Caching Pin
Karin Huber26-Feb-08 10:58
memberKarin Huber26-Feb-08 10:58 
Generalbad article Pin
hannesMVP25-Jan-08 0:56
memberhannesMVP25-Jan-08 0:56 
GeneralRe: bad article [modified] Pin
Karin Huber25-Jan-08 2:17
memberKarin Huber25-Jan-08 2:17 
GeneralFile name Pin
RexNebular22-Jan-08 4:23
memberRexNebular22-Jan-08 4:23 
GeneralRe: File name Pin
Karin Huber25-Jan-08 2:28
memberKarin Huber25-Jan-08 2:28 
Generalgood article Pin
Abhijit Jana17-Jan-08 19:12
memberAbhijit Jana17-Jan-08 19:12 
Generalempty Pin
Reynaldo Ferrer16-Jan-08 1:08
memberReynaldo Ferrer16-Jan-08 1:08 
GeneralRe: I love you Pin
onelove zeng15-Apr-08 0:11
memberonelove zeng15-Apr-08 0:11 
JokeRe: I love you Pin
Reynaldo Ferrer15-Apr-08 12:46
memberReynaldo Ferrer15-Apr-08 12:46 
Generalcaching dynamic images Pin
Anas Ghanem14-Jan-08 21:58
memberAnas Ghanem14-Jan-08 21:58 
GeneralRe: caching dynamic images Pin
merlin98115-Jan-08 1:27
membermerlin98115-Jan-08 1:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.151125.3 | Last Updated 1 Jul 2013
Article Copyright 2008 by Karin Huber
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid