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):



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:



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.


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.

    <clientCache cacheControlCustom="public" 
                 cacheControlMaxAge="1.00:00:00" />

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.


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">
      <clientCache cacheControlCustom="public" 
                   cacheControlMaxAge="1.00:00:00" />

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.


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.

Database file for ASP.Net Core under Linux

Here is some information about the database of an ASP.Net Core project under Linux. This assumes the project was created using the Yeoman generator or a similar process.

What’s the database?

The database is SQLite which is an embedded database found as a *.db file.

Where can I find it?

You can find it in your /bin/debug folder. It will be named based on your project’s name. ie: MyProject.db.

How can I access it outside of my program?

There are GUI programs to access the database but you can also use the sqlite3 program which is installed by default on some Linux distributions.

To install it on a Debian/Ubuntu based distribution use the following command:

sudo apt-get install sqlite3

To run it use

sqlite3 /pathToDbFile/DbFile.db

Then you can use .tables to list tables, select * from AspNetUsers; to do a simple select and .exit to exit the program.

You can also use .mode line to get better formatted results when using select statements.

How to create/recreate it if something happens?

Use the dotnet ef command.

If you have deleted your database use

dotnet ef database update

If your database is in a broken state and you don’t mind losing your data (if it’s only been used during development for instance) I suggest deleting it and recreating it (again if you don’t mind losing your data).


Height map generation in F# using midpoint displacement

Here is a simple program to generate some height maps. The maps can be generated to png files or txt files (as a serialized array).

Here’s the main program:

module TerrainGen

open System.Drawing

open HeightMap  
open MidpointDisplacement
open TestFramework
open Tests

let heightMapToTxt (heightMap:HeightMap) (filename:string) =
    let out = Array.init (heightMap.Size * heightMap.Size) (fun e -> heightMap.Map.[e].ToString())
    System.IO.File.WriteAllLines(filename, out)

let heightMapToPng (heightMap:HeightMap) (filename:string) =
    let png = new Bitmap(heightMap.Size, heightMap.Size)
    for x in [0..heightMap.Size-1] do
        for y in [0..heightMap.Size-1] do
            let red, green, blue = convertFloatToRgb (heightMap.Get x y) 
            png.SetPixel(x, y, Color.FromArgb(255, red, green, blue))
    png.Save(filename, Imaging.ImageFormat.Png) |> ignore

let main argv =
    consoleTestRunner testsToRun
    let map = newHeightMap 8
    generate map 0.3 0.5
    heightMapToPng map "out.png"
    heightMapToTxt map "out.txt"  

It uses two other modules. HeightMap which contains the height map type and the functions to work with this type. MidpointDisplacement which contains the algorithm proper.

module HeightMap

// contains the height map types and common functions that can be re-used for 
// different generation algorithms

type HeightMap = {Size:int; Map:float array} with     
    member this.Get x y =
        this.Map.[x * this.Size + y]      
    member this.Set x y value =
        this.Map.[x * this.Size + y] <- value

// returns a square matrix of size 2^n + 1
let newHeightMap n : HeightMap =
    let size = ( pown 2 n ) + 1
    {Size = size; Map = Array.zeroCreate (size * size)}  

// normalize a single value to constrain it's value between 0.0 and 1.0
let normalizeValue v =
    match v with
    | v when v < 0.0 -> 0.0
    | v when v > 1.0 -> 1.0
    | _ -> v

// converts a float point ranging from 0.0 to 1.0 to a rgb value
// 0.0 represents black and 1.0 white. The conversion is in greyscale 
let convertFloatToRgb (pct:float) : int * int * int =
    let greyscale = int (255.0 * pct)
    (greyscale, greyscale, greyscale)
// returns the average between two values    
let inline avg (a:^n) (b:^n) : ^n =
    (a + b) / (LanguagePrimitives.GenericOne + LanguagePrimitives.GenericOne)
// returns a floating number which is generated using bounds as a control of the range of possible values
let randomize (rnd:System.Random) (bound:float) : float =   
(rnd.NextDouble() * 2.0 - 1.0) * bound
module MidpointDisplacement

open HeightMap

// set the four corners to random values
let initCorners (hm:HeightMap) (rnd) =
    let rnd = System.Random()    
    let size = hm.Size   
    hm.Set 0 0 (rnd.NextDouble())
    hm.Set 0 (size - 1) (rnd.NextDouble())
    hm.Set (size - 1) 0 (rnd.NextDouble())
    hm.Set (size - 1) (size - 1) (rnd.NextDouble())
// set the middle values between each corner (c1 c2 c3 c4)
// variation is a function that is applied on each pixel to modify it's value
let middle (hm:HeightMap) (x1, y1) (x2, y2) (x3, y3) (x4, y4) (variation) =   
    // set left middle
    if hm.Get x1 (avg y1 y3) = 0.0 then 
        hm.Set x1 (avg y1 y3) (avg (hm.Get x1 y1) (hm.Get x3 y3) |> variation)      
    // set upper middle
    if hm.Get (avg x1 x2) y1 = 0.0 then
        hm.Set (avg x1 x2) y1 (avg (hm.Get x1 y1) (hm.Get x2 y2) |> variation)
    // set right middle
    if hm.Get x2 (avg y2 y4) = 0.0 then 
        hm.Set x2 (avg y2 y4) (avg (hm.Get x2 y2) (hm.Get x4 y4) |> variation)
    // set lower middle
    if hm.Get (avg x3 x4) y3 = 0.0 then
        hm.Set (avg x3 x4) y3 (avg (hm.Get x3 y3) (hm.Get x4 y4) |> variation)           

// set the center value of the current matrix to the average of all middle values + variation function
let center (hm:HeightMap) (x1, y1) (x2, y2) (x3, y3) (x4, y4) (variation) =
    // average height of left and right middle points
    let avgHorizontal = avg (hm.Get x1 (avg y1 y3)) (hm.Get x2 (avg y2 y4))
    let avgVertical = avg (hm.Get (avg x1 x2) y1) (hm.Get (avg x3 x4) y3)
    // set center value
    hm.Set (avg x1 x4) (avg y1 y4) (avg avgHorizontal avgVertical |> variation) 

let rec displace (hm) (x1, y1) (x4, y4) (rnd) (spread) (spreadReduction) =
    let ulCorner = (x1, y1) 
    let urCorner = (x4, y1)
    let llCorner = (x1, y4)
    let lrCorner = (x4, y4)
    let variation = (fun x -> x + (randomize rnd spread)) >> normalizeValue
    let adjustedSpread = spread * spreadReduction
    // the lambda passed in as a parameter is temporary until a define a better function
    middle hm ulCorner urCorner llCorner lrCorner variation 
    center hm ulCorner urCorner llCorner lrCorner variation
    if x4 - x1 >= 2 then
        let xAvg = avg x1 x4
        let yAvg = avg y1 y4
        displace hm (x1, y1) (xAvg, yAvg) rnd adjustedSpread spreadReduction
        displace hm (xAvg, y1) (x4, yAvg) rnd adjustedSpread spreadReduction
        displace hm (x1, yAvg) (xAvg, y4) rnd adjustedSpread spreadReduction
        displace hm (xAvg, yAvg) (x4, y4) rnd adjustedSpread spreadReduction
let generate hm startingSpread spreadReduction =
    let rnd = System.Random()
    let size = hm.Size - 1    
    initCorners hm rnd
displace hm (0, 0) (size, size) rnd startingSpread spreadReduction

The algorithm is pretty similar to diamond-square, in fact I have seen some people call it so, but it’s subtly different (in how to various sub-sections are divided) from the canon example, which is why I’m referring to it as midpoint displacement rather than diamond-square.

I’m pretty happy with the output of the results. It’s better than any map I have done before. Here is an example :


The code would need some optimization has it’s running out of memory fairly quick when generating larger maps.

You can find it as part of a larger repo on GitHub, that I have sadly abandoned.