---
title: "ASP.NET Core Caching in Practice - Muhammad Rehan Saeed"
description: "How to use and implement Cache-Control, Cache-Control Immutable, E-Tag, Last-Modified and If-Modified-Since Caching HTTP headers in ASP.NET Core."
canonical_url: "https://rehansaeed.com/asp-net-core-caching-in-practice/"
last_updated: "2017-10-20T00:00:00+00:00"
meta:
  author: "Muhammad Rehan Saeed"
  keywords: ".NET,.NET Core,ASP.NET Core,ASP.NET MVC,C#,Cache-Control,Caching,E-Tag,If-Modified-Since"
  "og:description": "How to use and implement Cache-Control, Cache-Control Immutable, E-Tag, Last-Modified and If-Modified-Since Caching HTTP headers in ASP.NET Core."
  "og:image": "https://rehansaeed.com/_og/s/o_e73b540eeb23f359.png"
  "og:image:alt": "ASP.NET Core Caching in Practice - Muhammad Rehan Saeed"
  "og:image:height": "630"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "ASP.NET Core Caching in Practice"
  "twitter:image": "https://rehansaeed.com/_og/s/o_e73b540eeb23f359.png"
  "twitter:image:alt": "ASP.NET Core Caching in Practice - Muhammad Rehan Saeed"
---

> Index of every page on this site: [llms.txt](https://rehansaeed.com/llms.txt)

# [ASP.NET Core Caching in Practice](https://rehansaeed.com/asp-net-core-caching-in-practice/)

Posted 20 October 2017 - **5 min read**

![ASP.NET Core-Caching-in-Practice](https://rehansaeed.com/_ipx/f_png&s_2560x1440/images/hero/ASP.NET-Core-Caching-in-Practice-1366x768.png)

# Link to this sectionCache-Control HTTP Header

The [Cache-Control](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching) HTTP header can be used to set how long your resource can be cached for. However, the problem with this HTTP header is that you need to be able to predict the future and know before hand when the cache will become invalid. For some use cases, like writing an API where someone could change the resource at any time that's just not feasible.

I recommend you read the response caching middleware [documentation](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/middleware), it's not necessary as I do a quick overview next but the knowledge below builds upon it. The simple way to set the cache control header is directly on the action method like so:

```cs
[HttpGet, ResponseCache(Duration = 3600, Location = ResponseCacheLocation.Any)]
public IActionResult GetCats()
```

Adding the `ResponseCache` attribute just adds the `Cache-Control` HTTP header but does not actually cache the response on the server. To do that you also need to add the response caching middleware like so:

```cs
public void Configure(IApplicationBuilder application) =>
    application.UseResponseCaching().UseMvc();
```

Instead of hard coding all of your cache settings in the `ResponseCache` attribute, it's possible to store them in the `appsettings.json` configuration file. To do so, you need to use a feature called cache profiles which look like this:

```cs
[HttpGet, ResponseCache(CacheProfile="Cache1Hour")]
public IActionResult GetCats()

public class Startup
{    
    private readonly IConfiguration configuration;
    
    public Startup() => this.configuration = configuration;

    public void ConfigureServices(IServiceCollection services) =>
        services
            .Configure<Dictionary<string, CacheProfile>>(configuration.GetSection("CacheProfiles"))
            .AddMvc(options =>
            {
                // Read cache profiles from appsettings.json configuration file
                var cacheProfiles = this.configuration.GetSection<Dictionary<string, CacheProfile>>();
                foreach (var keyValuePair in cacheProfiles)
                {
                    options.CacheProfiles.Add(keyValuePair);
                }
            });
            
    // Omitted
}
```

```json
{
  "CacheProfiles": {
    "Cache1Hour": {
      "Duration": 3600,
     "Location": "Any"
    }
  },
  // Omitted...
}
```

Now all your caching can be configured from a single configuration file.

## Link to this sectionCache-Control Immutable Directive

`Cache-Control` also has a new draft directive called `immutable`. When you add this to the HTTP header value, you are basically telling the client that this resource never changes even if it has expired. You might be asking, why do we need this? Well, it turns out that when you refresh a page in a browser, it goes off to the server and checks to see if the resource has expired or not.

```http
Cache-Control: max-age=365000000, immutable
```

It turns out that you get a massive reduction in requests to your server by implementing this directive. Read more about it in these links:

- [IETF Draft Spec](https://tools.ietf.org/html/draft-mcmanus-immutable-00)
- [Using Immutable Caching To Speed Up The Web](https://hacks.mozilla.org/2017/01/using-immutable-caching-to-speed-up-the-web/)
- [Cache-Control](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)
- [Cache-Control: immutable](https://bitsup.blogspot.co.uk/2016/05/cache-control-immutable.html)
- [This browser tweak saved 60% of requests to Facebook](https://code.facebook.com/posts/557147474482256)

This directive has not yet been implemented in ASP.NET Core but I've raised an issue on GitHub [here](https://github.com/aspnet/HttpAbstractions/issues/763) and there is also another issue [here](https://github.com/aspnet/ResponseCaching/issues/97) to add the immutable directive to the static files middleware. If you really wanted to, it's really easy to add this directive today, as you just need to append the word `immutable` onto the end of your `Cache-Control` HTTP header.

A word of warning! You need to make sure that your resource really never changes. You can do this in Razor by using the `asp-append-version` attribute on your script tags:

```html
<script src="~/site.js" asp-append-version="true"></script>
```

This will append a query string to the link to site.js which will contain a hash of the contents of the file. Each time the file changes, the hash is changed and thus you can safely mark the resource as immutable.

# Link to this sectionE-Tags

[E-tags](https://en.wikipedia.org/wiki/HTTP_ETag) are typically generated in three ways (Read the link to understand what they are):

1. Hashing the HTTP response body - You'd want to use a very fast and collision resistant hash function like MD5 (MD5 is broken security wise and you should never use it but it's ok to use it for caching). Unfortunately, this method is slow because you have to load the entire response body into memory (which is not the default in ASP.NET Core which streams it straight to the client for better performance) to hash it. If you're still interested in implementing this `E-Tag`'s using this method [Mads Kristensen](https://madskristensen.net/post/send-etag-headers-in-aspnet-core) wrote a nice blog post showing how it can be done.
2. Last modification timestamp - The `E-Tag` can literally be the time the object was last modified which you can store in your database (I usually store created and modified timestamps for anything I store in a database anyway). This solves the performance problem above but now what is the difference between doing this and using the Last Modified HTTP header?
3. Revision Number - This could be some kind of integer stored in the database which gets incremented each time the data is modified. I don't see any advantage of doing this over using the last modification timestamp above, unless you have a naturally occurring revision number in your data that you could use.

One additional thing you need to be careful of is the `Accept`, `Accept-Encoding` and `Accept-Language` HTTP headers. Any time you send a different response based on these HTTP headers, your `E-Tag` needs to be different e.g. a JSON non-gzip'ed response in Mandarin needs to have a different `E-Tag` to an XML gzip'ed response in Urdu.

For option one, this can be achieved by calculating the hash after the response body has gone through GZIP compression. For the second and third options, you would need to append the value of the Accept HTTP headers to the last modified date or revision number and then hash all of that.

# Link to this sectionLast-Modified & If-Modified-Since

I'm assuming you already know about the [Last-Modified](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified) and [If-Modified-Since](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since) HTTP headers. If not, go ahead and read the links. Below is an example controller and action method that returns a list of cats.

```cs
[Route("[controller]")]
public class CatsController : ControllerBase
{
    private readonly ICatRepository catRepository;
    private readonly ICatMapper catMapper;

    public CatsController(
        ICatRepository catRepository,
        ICatMapper catMapper)
    {
        this.catRepository = catRepository;
        this.catMapper = catMapper;
    }

    [HttpGet("")]
    public async Task<IActionResult> GetCats(CancellationToken cancellationToken)
    {
        var cats = await this.catRepository.GetAll(cancellationToken);
        var lastModified = cats.Count == 0 ? 
            (DateTimeOffset?)null : 
            cats.Max(x => x.ModifiedTimestamp);

        this.Response.GetTypedHeaders().LastModified = lastModified;

        var requestHeaders = this.Request.GetTypedHeaders();
        if (requestHeaders.IfModifiedSince.HasValue &&
            requestHeaders.IfModifiedSince.Value >= lastModified)
        {
            return this.StatusCode(StatusCodes.Status304NotModified);
        }

        var catViewModels = this.catMapper.MapList(cats);
        return this.Ok(catViewModels);
    }
}
```

All of our cats have a ModifiedTimestamp, so we know when they were last changed. There are four scenarios that this action method handles:

1. Our repository does not contain any cats, so just always return an empty list.
2. No `Last-Modified` HTTP header exists in the request, so we just return all cats.
3. `Last-Modified` HTTP header exists and cats have been modified since that date, so return all cats.
4. `Last-Modified` HTTP header exists but no cats have been modified since that date, so return a 304 Not Modified response.

In all cases, except when we have no cats at all, we set the `Last-Modified` date to the latest date than any cat has been modified.

# Link to this sectionConclusions

Which caching HTTP headers you pick, depends on your data but at a minimum, I would add `E-Tag`'s or `Last-Modified`. Add `Cache-Control` where possible, usually for static assets.

## Web Mentions

[What's this?](https://en.wikipedia.org/wiki/Webmention)

### 0 Linked

### 0 Replies

## GitHub Comments

### 19 Comments

[Comment on GitHub](https://github.com/RehanSaeed/rehansaeed.github.io/issues/53)

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

12 May 2020

[![Robin](https://camo.githubusercontent.com/095e3b4df2a4cc3f7edb4472772f67ce5ee4561792fc4b91cd53b53cc31ff35b/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f63376630363935376439373964643932666237643634623764376430303262663f733d393626643d6d6d26723d67)](https://camo.githubusercontent.com/095e3b4df2a4cc3f7edb4472772f67ce5ee4561792fc4b91cd53b53cc31ff35b/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f63376630363935376439373964643932666237643634623764376430303262663f733d393626643d6d6d26723d67) **[Robin](http://floodassist.co.uk/)** commented on **2017-10-26 15:48:26**

hmmm, I'm doing similar things by adding the following to my Configure method in startup.cs:

```
app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =
    {
        const int durationInSeconds = 60 * 60 * 24 * 1; //one day!
        ctx.Context.Response.Headers.Add("cache-control", new[] { "public,max-age=" + durationInSeconds });
        ctx.Context.Response.Headers.Add("Expires", new[] { DateTime.UtcNow.AddSeconds(durationInSeconds).ToString("R") }); // Format RFC1123
    }
});
```

I'm only concerned with caching "big" things like images etc (which is why its for static files).

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

12 May 2020 (edited 12 May 2020)

[![Shahzad Hassan](https://camo.githubusercontent.com/0e25150d582639209d75a3759473963fd9037a9855f9d038ba458448366d4f1a/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f37643566306636396566323735353137633232303437656339663761663332303f733d393626643d6d6d26723d67)](https://camo.githubusercontent.com/0e25150d582639209d75a3759473963fd9037a9855f9d038ba458448366d4f1a/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f37643566306636396566323735353137633232303437656339663761663332303f733d393626643d6d6d26723d67) **[Shahzad Hassan](https://www.softmatters.co.uk/)** commented on **2017-10-26 16:08:14**

Thanks for sharing. Yet again, a very nice blog indeed. I have a question for you.

I know that Response Caching cannot be used if `Authorization` header is present in the request. I have a scenario where multiple API subscribers need to pass in a `Bearer` token in the `Authorization` header so the request can be authenticated/authorized. However, the response of a REST API call is exactly the same regardless of who is calling, as far as they are authenticated.

How would you approach to solve this particular problem? Would you recommend implementing a custom caching layer using [ResourceFilters](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters#resource-filters) or something as they will be second in a row after the `AuthorizeFilters`? What are your thoughts on it?

Thanks

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

12 May 2020

[![Muhammad Rehan Saeed](https://camo.githubusercontent.com/8a07049df41b8a3839a3f4342f09ea8e6e7fa0065667687f04beca2d088a1dca/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f32623266393030653361633066353535316462646537323864336262343935343f733d393626643d6d6d26723d67)](https://camo.githubusercontent.com/8a07049df41b8a3839a3f4342f09ea8e6e7fa0065667687f04beca2d088a1dca/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f32623266393030653361633066353535316462646537323864336262343935343f733d393626643d6d6d26723d67) **[Muhammad Rehan Saeed](https://rehansaeed.com/)** commented on **2017-11-13 09:03:31**

> hmmm, I'm doing similar things by adding the following to my Configure method in startup.cs:
>
> ```
> app.UseStaticFiles(new StaticFileOptions
> {
>     OnPrepareResponse = ctx =
>     {
>         const int durationInSeconds = 60 * 60 * 24 * 1; //one day!
>         ctx.Context.Response.Headers.Add("cache-control", new[] { "public,max-age=" + durationInSeconds });
>         ctx.Context.Response.Headers.Add("Expires", new[] { DateTime.UtcNow.AddSeconds(durationInSeconds).ToString("R") }); // Format RFC1123
>     }
> });
> ```
>
> I'm only concerned with caching "big" things like images etc (which is why its for static files).

Looks reasonable. Setting `Cache-Control`, `Expires` and `Pragma` HTTP headers is complicated, which is why I use the helper method.

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

12 May 2020 (edited 12 May 2020)

[![Muhammad Rehan Saeed](https://camo.githubusercontent.com/8a07049df41b8a3839a3f4342f09ea8e6e7fa0065667687f04beca2d088a1dca/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f32623266393030653361633066353535316462646537323864336262343935343f733d393626643d6d6d26723d67)](https://camo.githubusercontent.com/8a07049df41b8a3839a3f4342f09ea8e6e7fa0065667687f04beca2d088a1dca/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f32623266393030653361633066353535316462646537323864336262343935343f733d393626643d6d6d26723d67) **[Muhammad Rehan Saeed](https://rehansaeed.com/)** commented on **2017-11-13 09:11:13**

> Thanks for sharing. Yet again, a very nice blog indeed. I have a question for you.
>
> I know that Response Caching cannot be used if `Authorization` header is present in the request. I have a scenario where multiple API subscribers need to pass in a `Bearer` token in the `Authorization` header so the request can be authenticated/authorized. However, the response of a REST API call is exactly the same regardless of who is calling, as far as they are authenticated.
>
> How would you approach to solve this particular problem? Would you recommend implementing a custom caching layer using [ResourceFilters](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters#resource-filters) or something as they will be second in a row after the `AuthorizeFilters`? What are your thoughts on it?
>
> Thanks

[This](https://github.com/aspnet/ResponseCaching/issues/128) GitHub issue in the aspnet/ResponseCache repository deals with caching being turned off when the `Authorization` HTTP header is present.

In my opinion, the best way to go might be to override or replace the logic in the `ResponseCache` middleware where caching is turned off based on the `Authorization` HTTP header.

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

12 May 2020

[![Shahzad Hassan](https://camo.githubusercontent.com/0e25150d582639209d75a3759473963fd9037a9855f9d038ba458448366d4f1a/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f37643566306636396566323735353137633232303437656339663761663332303f733d393626643d6d6d26723d67)](https://camo.githubusercontent.com/0e25150d582639209d75a3759473963fd9037a9855f9d038ba458448366d4f1a/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f37643566306636396566323735353137633232303437656339663761663332303f733d393626643d6d6d26723d67) **Shahzad Hassan** commented on **2017-11-23 08:26:51**

> [This](https://github.com/aspnet/ResponseCaching/issues/128) GitHub issue in the aspnet/ResponseCache repository deals with caching being turned off when the Authorization HTTP header is present.
>
> In my opinion, the best way to go might be to override or replace the logic in the `ResponseCache` middleware where caching is turned off based on the `Authorization` HTTP header.

Ok, Thanks. Makes sense, l will have a look.

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

12 May 2020

[![John](https://camo.githubusercontent.com/97f955c2d8396debf29d1fbdb913ce2ae703b11db57ddef581dc6748cedccf82/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f62636530356332376530373566306265643031633164656365613334333263363f733d393626643d6d6d26723d67)](https://camo.githubusercontent.com/97f955c2d8396debf29d1fbdb913ce2ae703b11db57ddef581dc6748cedccf82/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f62636530356332376530373566306265643031633164656365613334333263363f733d393626643d6d6d26723d67) **John** commented on **2018-06-29 08:16:29**

Nice post. The way you controller works it's kind confusing me. You always hit the db to fetch all the data anyway.. so no much optimization there. Or maybe was just a silly example? Thanks!

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

12 May 2020

[![Muhammad Rehan Saeed](https://camo.githubusercontent.com/8a07049df41b8a3839a3f4342f09ea8e6e7fa0065667687f04beca2d088a1dca/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f32623266393030653361633066353535316462646537323864336262343935343f733d393626643d6d6d26723d67)](https://camo.githubusercontent.com/8a07049df41b8a3839a3f4342f09ea8e6e7fa0065667687f04beca2d088a1dca/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f32623266393030653361633066353535316462646537323864336262343935343f733d393626643d6d6d26723d67) **[Muhammad Rehan Saeed](https://rehansaeed.com/)** commented on **2018-06-29 09:23:56**

> Nice post. The way you controller works it's kind confusing me. You always hit the db to fetch all the data anyway.. so no much optimization there. Or maybe was just a silly example? Thanks!

Yes, that's a good point. The code I've shown optimises what gets sent over the wire but not the database calls. If you wanted to optimise the database call, you could start with a query that returns the count for the number of items and the latest modified timestamp and use that to figure out if you need to do another query to get all of the data or return a 304 not modified response.

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

12 May 2020 (edited 12 May 2020)

[![Noah](https://camo.githubusercontent.com/5d0e61aadaeab789925a1b7811f32f3019ebb96bf6a64f8f5f0b4d61f91b5117/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f65653862333962633233363632323633616361613063373331333164346363383f733d393626643d6d6d26723d67)](https://camo.githubusercontent.com/5d0e61aadaeab789925a1b7811f32f3019ebb96bf6a64f8f5f0b4d61f91b5117/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f65653862333962633233363632323633616361613063373331333164346363383f733d393626643d6d6d26723d67) **Noah** commented on **2018-11-16 07:43:47**

Great article about caching. While, I have read HTTP-based cache mechanism [here](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-2.1), and I want to know does HTTP-based cache make sense for an Web API application? Back to your example for `Last-Modified` & `If-Modified-Since`, is it reasonable for an API client receiving 304 response? Could you provide some advice and best practices about implementing HTTP-based cache for a Web API application? Thanks.

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

12 May 2020

[![Muhammad Rehan Saeed](https://camo.githubusercontent.com/8a07049df41b8a3839a3f4342f09ea8e6e7fa0065667687f04beca2d088a1dca/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f32623266393030653361633066353535316462646537323864336262343935343f733d393626643d6d6d26723d67)](https://camo.githubusercontent.com/8a07049df41b8a3839a3f4342f09ea8e6e7fa0065667687f04beca2d088a1dca/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f32623266393030653361633066353535316462646537323864336262343935343f733d393626643d6d6d26723d67) **[Muhammad Rehan Saeed](https://rehansaeed.com/)** commented on **2018-11-16 09:06:25**

> Great article about caching. While, I have read HTTP-based cache mechanism [here](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-2.1), and I want to know does HTTP-based cache make sense for an Web API application? Back to your example for `Last-Modified` & `If-Modified-Since`, is it reasonable for an API client receiving 304 response? Could you provide some advice and best practices about implementing HTTP-based cache for a Web API application? Thanks.

It does make sense sometimes. If the resource you are getting has a last modified date that you store in the database, you can take that and turn it into `Last-Modified` & `If-Modified-Since` HTTP headers. If your client is a web browser, then it will respect the header. If the client is custom code such as C#'s `HttpClient`, then you will need to write some extra code to respect the HTTP headers and cache responses.

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

12 May 2020

[![Muhammad Rehan Saeed](https://camo.githubusercontent.com/8a07049df41b8a3839a3f4342f09ea8e6e7fa0065667687f04beca2d088a1dca/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f32623266393030653361633066353535316462646537323864336262343935343f733d393626643d6d6d26723d67)](https://camo.githubusercontent.com/8a07049df41b8a3839a3f4342f09ea8e6e7fa0065667687f04beca2d088a1dca/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f32623266393030653361633066353535316462646537323864336262343935343f733d393626643d6d6d26723d67) **[Muhammad Rehan Saeed](https://rehansaeed.com/)** commented on **2018-12-01 10:59:58**

> Great article about caching. While, I have read HTTP-based cache mechanism [here](https://docs.microsoft.com/en-us/aspnet/core/performance/caching/response?view=aspnetcore-2.1), and I want to know does HTTP-based cache make sense for an Web API application? Back to your example for `Last-Modified` & `If-Modified-Since`, is it reasonable for an API client receiving 304 response? Could you provide some advice and best practices about implementing HTTP-based cache for a Web API application? Thanks.

I can make sense for an API. You just need to know when the resource was last changed. Also, the client has to support caching headers and store resources in some way that makes sense.

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

12 May 2020

[![Abhi](https://camo.githubusercontent.com/5e0cbb5e5f680a77bb3567e6068a7f0b1fdf7326c969dc1323eaaa0bfade2d40/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f34343031383135303037366631653430663138363663623532346435323565353f733d393626643d6d6d26723d67)](https://camo.githubusercontent.com/5e0cbb5e5f680a77bb3567e6068a7f0b1fdf7326c969dc1323eaaa0bfade2d40/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f34343031383135303037366631653430663138363663623532346435323565353f733d393626643d6d6d26723d67) **Abhi** commented on **2019-04-12 12:37:50**

Hi,

Thank you for good article,

I am getting error on the line

```
var cacheProfiles = this.configuration.GetSection<Dictionary<string, CacheProfile>>();
```

GetSection not supporting..

.netcore 2.1 framework

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

12 May 2020

[![Al Stevens](https://camo.githubusercontent.com/c918ba8e7337b7c49e08f2ddb97846155fcd9598aaef780b13515dc87345cb2a/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f63366530373537303839626336653834326663633361656264306439316433313f733d393626643d6d6d26723d67)](https://camo.githubusercontent.com/c918ba8e7337b7c49e08f2ddb97846155fcd9598aaef780b13515dc87345cb2a/68747470733a2f2f7777772e67726176617461722e636f6d2f6176617461722f63366530373537303839626336653834326663633361656264306439316433313f733d393626643d6d6d26723d67) **Al Stevens** commented on **2019-12-16 17:49:31**

> Hi,
>
> Thank you for good article,
>
> I am getting error on the line
>
> ```
> var cacheProfiles = this.configuration.GetSection<Dictionary<string, CacheProfile>>();
> ```
>
> GetSection not supporting..
>
> .netcore 2.1 framework

Abhi,

I hit the same thing too, you need to change it to the following:

```
var cacheProfiles = this.configuration.GetSection("CacheProfiles").Get<Dictionary<string, CacheProfile>>();
```

Hope that helps.

[![xhafan](https://avatars.githubusercontent.com/u/13038902?v=4)](https://github.com/xhafan)

### [xhafan](https://github.com/xhafan)

7 September 2020

Thanks the article helped me

[![terryaney](https://avatars.githubusercontent.com/u/6237247?v=4)](https://github.com/terryaney)

### [terryaney](https://github.com/terryaney)

4 February 2021

I'm trying to do the same thing you described in the 'Last-Modified & If-Modified-Since' section. However, after the initial call to the controller, Chrome (and I tested FF too) doesn't even call back to my controller. I simply returns like this:

[![image](https://user-images.githubusercontent.com/6237247/106906603-f67ce180-66c2-11eb-9ee0-c7766f3e5229.png)](https://user-images.githubusercontent.com/6237247/106906603-f67ce180-66c2-11eb-9ee0-c7766f3e5229.png)

It says *(disk cache)*. I haven't done any 'configuration setup' in `Startup`. I simply implemented code for checking if-modified-since against our database timestamp. Do I need code in `Startup` to make this work?

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

4 February 2021

[@terryaney](https://github.com/terryaney) If you've implemented the headers you mention correctly and the header is telling the browser that a resource has not been modified since it last made a request to the resource then the browser will cache that resource. This is why you get a 'disk cache' in the browsers network tab. Is that not what you want?

[![terryaney](https://avatars.githubusercontent.com/u/6237247?v=4)](https://github.com/terryaney)

### [terryaney](https://github.com/terryaney)

4 February 2021

I'm just saying that it doesn't even post back to my action so that I can check 'if-modified-since' against my database. The browser just caches it itself. So when I update the resource in our database, the new version is never returned because it doesn't hit my code.

```
protected async Task<IActionResult> CachedOrModifiedAsync( CacheDownloadInfo cacheDownloadInfo, IDbConnectionFactory dbConnectionFactory )
{
	var lastModifiedDate = cacheDownloadInfo.LastModifiedDate.Value.ToUniversalTime();

	// https://rehansaeed.com/asp-net-core-caching-in-practice/#last-modified--if-modified-since
	// https://www.geekytidbits.com/efficient-caching-dynamic-resources-asp-net-304-not-modified/
	var requestHeaders = HttpContext.Request.GetTypedHeaders();
	if ( !requestHeaders.IfModifiedSince.HasValue )
	{
		// Set Last Modified time
		HttpContext.Response.GetTypedHeaders().LastModified = lastModifiedDate;
	}
	else
	{
		// HTTP does not provide milliseconds, so remove it from the comparison
		if ( lastModifiedDate.AddMilliseconds( -lastModifiedDate.Millisecond ) == requestHeaders.IfModifiedSince.Value )
		{
			return NotModified();
		}
	}

	var bds = new SqlBinaryDataStream<Guid>( dbConnectionFactory.GetDataLockerConnectionString(), "Cache", "UID", "Content", cacheDownloadInfo.Token.Value );
	return new FileStreamResult( await bds.ContentIsCompressedAsync ? new GZipStream( bds, CompressionMode.Decompress ) : bds, cacheDownloadInfo.ContentType );
}
```

[![RehanSaeed](https://avatars.githubusercontent.com/u/2270364?v=4)](https://github.com/RehanSaeed)

### [RehanSaeed](https://github.com/RehanSaeed)

Owner

4 February 2021

Perhaps consider creating a small sample app and posting the question on StackOverflow to get some eyes on it (I'll also take a look).

[![terryaney](https://avatars.githubusercontent.com/u/6237247?v=4)](https://github.com/terryaney)

### [terryaney](https://github.com/terryaney)

4 February 2021

I didn't make a 'sample app'. I just pasted my controller and caching code. https://stackoverflow.com/questions/66052852/asp-net-core-web-api-checking-if-modified-since-header-not-working-because-brows Will see if anyone has ideas.

[![terryaney](https://avatars.githubusercontent.com/u/6237247?v=4)](https://github.com/terryaney)

### [terryaney](https://github.com/terryaney)

5 February 2021 (edited 5 February 2021)

FYI, I updated my stack overflow answer [here](https://stackoverflow.com/a/66067418/166231). But to make this work with Swagger (maybe other clients as well), I had to put the following:

```
responseHeaders.CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue
{
	Public = true,
	MustRevalidate = true,
	MaxAge = new TimeSpan( 0, 0, 0 ),
};
responseHeaders.Expires = DateTime.UtcNow;
```

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://rehansaeed.com/#website","@type":"WebSite","description":"Software Developer at Microsoft, YouTuber, Open Source Contributor and Blogger","inLanguage":"en-GB","name":"Muhammad Rehan Saeed","url":"https://rehansaeed.com/","author":{"@id":"https://rehansaeed.com/#identity"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://rehansaeed.com/?search={search_term_string}"},"query-input":"required name=search_term_string"}],"publisher":{"@id":"https://rehansaeed.com/#publisher"}},{"@id":"https://rehansaeed.com/asp-net-core-caching-in-practice/#webpage","@type":"WebPage","dateModified":"2017-10-20T00:00:00+00:00","datePublished":"2017-10-20T00:00:00+00:00","description":"How to use and implement Cache-Control, Cache-Control Immutable, E-Tag, Last-Modified and If-Modified-Since Caching HTTP headers in ASP.NET Core.","name":"ASP.NET Core Caching in Practice","url":"https://rehansaeed.com/asp-net-core-caching-in-practice/","breadcrumb":{"@id":"https://rehansaeed.com/asp-net-core-caching-in-practice/#breadcrumb"},"isPartOf":{"@id":"https://rehansaeed.com/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://rehansaeed.com/asp-net-core-caching-in-practice/"]}]},{"@id":"https://rehansaeed.com/#identity","@type":"Person","name":"Muhammad Rehan Saeed","url":"https://rehansaeed.com/about/","image":{"@id":"https://rehansaeed.com/#/schema/image/1"},"sameAs":["https://github.com/RehanSaeed","https://www.linkedin.com/in/muhammad-rehan-saeed","https://stackoverflow.com/users/1212017/muhammad-rehan-saeed","https://www.youtube.com/c/MuhammadRehanSaeed","https://www.twitch.tv/rehansaeeduk"]},{"@id":"https://rehansaeed.com/#publisher","@type":"Organization","logo":"https://rehansaeed.com/images/schema/Publisher-600x60.png","name":"Muhammad Rehan Saeed","url":"https://rehansaeed.com"},{"@id":"https://rehansaeed.com/asp-net-core-caching-in-practice/#article","@type":"Article","dateModified":"2017-10-20T00:00:00+00:00","datePublished":"2017-10-20T00:00:00+00:00","description":"How to use and implement Cache-Control, Cache-Control Immutable, E-Tag, Last-Modified and If-Modified-Since Caching HTTP headers in ASP.NET Core.","headline":"ASP.NET Core Caching in Practice","inLanguage":"en-GB","url":"https://rehansaeed.com/asp-net-core-caching-in-practice/","author":{"@id":"https://rehansaeed.com/#identity"},"image":[{"@type":"ImageObject","url":"https://rehansaeed.com/images/hero/ASP.NET-Core-Caching-in-Practice-1366x768.png","caption":"ASP.NET Core-Caching-in-Practice","width":"1366","height":"768"}],"isPartOf":{"@id":"https://rehansaeed.com/asp-net-core-caching-in-practice/#webpage"},"keywords":[".NET",".NET Core","ASP.NET Core","ASP.NET MVC","C#","Cache-Control","Caching","E-Tag","If-Modified-Since"],"mainEntityOfPage":{"@id":"https://rehansaeed.com/asp-net-core-caching-in-practice/#webpage"},"publisher":{"@id":"https://rehansaeed.com/#publisher"}},{"@id":"https://rehansaeed.com/asp-net-core-caching-in-practice/#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://rehansaeed.com/","position":1},{"@type":"ListItem","name":"ASP.NET Core Caching in Practice","item":"https://rehansaeed.com/asp-net-core-caching-in-practice/","position":2}]},{"@id":"https://rehansaeed.com/#/schema/image/1","@type":"ImageObject","contentUrl":"https://rehansaeed.com/images/author/Muhammad-Rehan-Saeed/Logo-512x512.png","inLanguage":"en-GB","url":"https://rehansaeed.com/images/author/Muhammad-Rehan-Saeed/Logo-512x512.png"}]}
```

## Sitemap

See the full [sitemap](https://rehansaeed.com/sitemap.md) for all pages.
