Configuring HTTP Response caching in ASP.Net and ASP.Net MVC

Configuring HTTP Response caching in ASP.Net and ASP.Net MVC

Also known as simply HTTP caching or Response caching.

About HTTP Response Caching

When requesting a web page from a site, the browser interprets the HTML it receives from the server. It turn, it uses this new information to make more requests for other files; css, JavaScript, images, etc.

Here are the requests as shown by Firefox’s Developer Tools’ network tab when starting a sample ASP.Net / ASP.Net MVC application (it could be any website):

cache1

cache1a

Here we can see the additional files requested and served for displaying the page: Site.css, bootstrap.min.css, etc.

If we hit F5 to reload the same page once again, we get a slightly different result:

cache2

cache2a

This time we can see the same files being requested, with two differences. The HTTP return codes for the subsequent files are 304 instead of 200 (more on that later) and the total for all the 6 requests has gone from 0.11s to 0.07s.

The HTTP status code 200 is the OK status code. In this case it means the GET request was successful and that we have obtained the requested file. Since this was the first time accessing the site we had to download each file.

On the other hand, on the second page load we had HTTP status code 304 which is the NOT MODIFIED status code. When a browser requests some files it keeps them in it’s local cache. This cache is on the user’s machine and managed by the browser.

When receiving the original files the browser checks their http headers which contain amongst other things information about caching. When making a second request it sends (depending on the caching configuration in the headers) some information to validate if the files have been changed and needs to be downloaded again.

If the server can ascertain the files have not changed it returns a 304 result and the browser doesn’t download the file on the subsequent requests saving us some time.

If some of the files have changed, it will download them again. To know if a file has changed the browser will either send a last modification date or an ETag (a sort of fingerprint). We can test that by modifying one of our files, in my case Site.css, and reloading again.

cache3

Now we can see that the server was able to know that Site.css had changed and needed to be downloaded again by the client.

We can also test, by clearing our local cache, by doing a Ctrl-F5 or by starting a private browsing session that in the case where there is no cache anymore the files are downloaded again and that we get a 200 result like in the first image.

In the case where we used the local cache and got 304s, even tough the files haven’t been downloaded a second time we still had to make requests and wait for responses. We can do away with those requests altogether by configuring our http headers. This means that while getting a 304 is better than downloading the whole file, we can do even better by not making any requests at all.

Configuring your ASP.Net or ASP.Net MVC application for HTTP Response caching

What we’ve seen so far applies to all web servers and clients. Let’s see now how to configure our caching for an ASP.Net application running on IIS.

In an ASP.Net or ASP.Net MVC application it is possible to configure this through the web.config file. Here I will show you how to configure this for static files. Take note that these settings do not apply to Visual Studio’s built-in IIS server, only to applications deployed on an IIS instance. You may need to deploy your web application on IIS to test if you have been successful in applying your settings.

The following settings are for IIS 7. For IIS 6 you must use different configuration elements as mentioned here.

<system.webServer>
  <staticContent>
    <clientCache cacheControlCustom="public" 
                 cacheControlMode="UseMaxAge" 
                 cacheControlMaxAge="1.00:00:00" />
  </staticContent>
</system.webServer>

Here we have configured our static files so that the browser will use the cached version for 1 day. MaxAge indicates how long the cached files will be used. The other way to configure this is to use httpExpires which instead specifies a date up until which the cached content will be used.

After configuring this we can run our test again. But this time by navigating on another page that uses the same .css and .js files.

clientcache

As we can see the static content files haven’t been requested. The browser won’t request those files until the max age expires. In the Transferred column, instead of seeing some byte count we see cached which indicates the cached version has been used.

We need to navigate to another page to see those results because reloading the same page with the Network tab will always result in a 304 in the Firefox Developer Tools.

Back to our web.config file. We can also use the location element to target specific folders. This allows us to use various caching policies on different files.

<location path="Content">
  <system.webServer>
    <staticContent>
      <clientCache cacheControlCustom="public" 
                   cacheControlMode="UseMaxAge" 
                   cacheControlMaxAge="1.00:00:00" />
    </staticContent>
  </system.webServer>
</location>

Be careful if you set a MaxAge or httpExpires too far in the future or you might get some problems as you change those files and clients aren’t aware of the changes since they are not making any request.

One way to get around that is to change the name of the file. Say the client has cached Site_ASDF9494.css if you change to Site_BWDF9596.css the browser will treat this as a different file and download it again whatever the caching directives may be. It is possible to append a hash or some form of fingerprint to your files. Better yet, if you are using the bundling facilities in ASP.Net or ASP.Net MVC, a cache busting identifier is appended for you behind the scenes so you don’t have to do anything.

Details on cacheControlCustom

cacheControlCustom allows to specify some custom parameters of which public and privateĀ  are of particular importance. These indicate whether the files are to be treated as public (for example your site’s css file) or private (for example some file served to an authenticated user). The reason is that while we have only talked about browser caching up until now, caching can also happen at other levels, a CDN or a proxy for example. These will not cache private files but may cache public files.

By forgetting to specify this we may cause some sensitive information to be displayed to incorrect users. If some files contain content specific to a particular user and these files are cached on a proxy, they could be served to several different users who are behind this same proxy.

Make sure to be mindful of this one as the default value is public unless specified otherwise.

Conclusion

As modern sites can make a lot of requests, even when taking into account bundling and CDNs, it is important to be aware of how to reduce these requests to a maximum while still keeping a control of our files for future updates. Response caching is a good way of achieving this.

Tales from the trenches: ASP.Net ViewState bug


This post is part of a series of actual problems or bad practices I encountered working on existing C# code bases.

The names of all identifiers, the exact functionalities of the programs and the code samples have all been altered for confidentiality reasons.

While looking into a slow ASP.Net page I noticed that the page was making a lot more service calls than it should have. Visually I could see superfluous calls that should have been merged together.

I profiled the page with the Performance Tools in Visual Studio and also put breakpoints on all service calls and ran it in Debug mode.

One thing that stood out was one property that was supposed to be using ViewState to prevent calls to the service layer but actually wasn’t.

It went something like this :

public int Somevalue 
{
    get 
    {
        if (ViewState["SomeValue"] == null)
        {
            var foo =
            ValueService.GetSomeValue(Convert.ToInt32
                (Request["SomeValue"]));

            ViewState["SomeValue"] = foo;
        }
        
        return (int)ViewState["SomeValue"];
    }
    set 
    {
        ViewState["SomeValue"] = value;
    }
}

// ...

protected override void OnInit(EventArgs e)
{
    int x = SomeValue;	// contrived example
}

I noticed that ValueService.GetSomeValue was getting called on PostBacks. Looking at where it was used, I saw it was being called in the OnInit method. It just so happens that during the OnInit phase of the ASP.Net page life cycle, the ViewState hasn’t be loaded with it’s values. This effectively means that SomeValue will always be null in the OnInit phase and it will have to make a service call every time.

The solution in this case was simply to move the call to a later stage of the page life cycle, in this case the PageLoad event.

The take away from this is to be mindful of the ASP.Net page life cycle events. Also putting breakpoints on service calls while loading the page and submitting the form is a quick way to see if something is amiss with your service calls.

Tales from the trenches: ASP.Net static variable bug


This post is part of a series of actual problems or bad practices I encountered working on existing C# code bases.

The names of all identifiers, the exact functionalities of the programs and the code samples have all been altered for confidentiality reasons.

While working on an ASP.Net control bug, I noticed this more important problem which hadn’t been reported.

The reported bug was about a control that would sometimes not repopulate itself. It was a pretty simple case.

if (ConditionA && lastValue != currentValue)
{
    lastValue == currentValue;
    // more code
}

What happened is that when ConditionA failed, lastValue wasn’t updated. The fix was simply to move the lastValue assignment statement outside of the conditional.

The bigger problem I noticed was when I decided to look at how lastValue was implemented.

It turned out it was implemented as a static member variable. This being an ASP.Net page, a static variable is shared between all instances of the class in the AppDomain.

What this effectively means is that every user of the site shares this same variable.

Suppose user A fills lastValue with “test”, then user B fills currentValue with “test”, the server-side code detects that user B has already used this value as his value (since it’s currently in lastvalue), even if he hasn’t entered anything before. This could lead to some inconsistent behaviour.

The lesson here is two-fold:

1- Do not to cache information with a static variable in an ASP.Net site unless that information is not subject to change from user to user.

2- When working on existing code, make sure you understand correctly how it is implemented. In this case I could have glossed over the implementation of lastValue, which wasn’t declared near the method I was working on and missed this important bug.