We encountered a problem where we were sending response cache headers to the client for non-successful requests. To us, this was unexpected. So, let's take a look at what we did wrong, and how we fixed this issue.
The easiest way to add a cache header to the response is to use the
ResponseCache attribute, which can be added to a class or to specific methods. This has one big caveat, the response will be cached by the browser regardless of the response code.
For example in the code below, if a condition is not met, the
GET request returns a
400 BadRequest, which also will be cached by the client. When the client retried the request, the request wouldn't reach the server, and the client would immediately use the cached response.
On the other hand, when the API throws an uncaught exception, the cache headers won't be added to the response.
But when the default exception behavior is overridden, like we did in Validate incoming requests, this can be problematic because this means that all non-successful responses are cached when the endpoint uses the
This is not the desired behavior and leads to unexpected results.
To take full control of which and how an endpoint needs to be cached, another option is to implement a caching middleware.
In the example below, the
AddCacheControlHeaders method is used to validate incoming requests, and it's adding the appropriate cache headers on the response when the response has the
200 OK status code.
This approach has one major disadvantage. It doesn't make it very clear which requests are cached and which aren't. Because this de-couples the caching logic from the API, this can cause some regression when an endpoint is renamed. It can also make the code complicated if there are more complex conditions and different caching strategies for different endpoints.
In the next example, we add the cache header to the
/WeatherForecast request only when the response is
👍 Good: Hybrid link
Now that we know the strengths and weaknesses of each approach, let's take a look at a hybrid approach. This blends the two approaches together to come up with a simple and maintainable solution.
We use the
ResponseCache attribute because it's an easy and expressive way to add a cache header to the response.
Then, to prevent that non-200's response codes are cached by the client, we use the cache middleware to remove the cache headers for when the response status code is not
The example below, replaces the existing cache header with a no-cache header when the response status code is not
I was wrong thinking that .NET's caching middleware
ResponseCachingMiddleware removed the cache headers for invalid requests.
This is important because otherwise the invalid response ends up in the cache of the client.
To prevent that invalid requests are cached, you must manually remove the cache headers from the response. The easiest way to do this, is to write your own middleware.
I appreciate it if you would support me if have you enjoyed this post and found it useful, thank you in advance.