SHARE:

Building a conditional caching middleware instead of an attribute in ASP.NET Core 3.1

Introduction

It was not that long ago that I had to manage the cache to get informations that rarely change. My goal was to avoid making HTTP hits to the server, so I had to eliminate the distributed cache and the memory cache. So I only had as solution the HTTP response cache by attributes because client side caching. But something bothered me, I wanted to have the possibility of benefiting from a greater flexibility in parameters management, for example by using dependency injection to inject parameters rather than using caching profiles. So I had the idea of building a caching middleware, conditional on the route used, in order to have the same behavior as an action or controller attribute. In this article I wanted to explain to you how I did it.

Caching scenarii

My scenari were like this:

  • Expose an endpoint that takes optionally a query string parameter (a method that retrieves all my configuration + the possibility to filter it)
  • Expose an endpoint that returns a configuration for a specified route filter.

If we take a look of all possible urls to manage, we have

  • /configuration/all endpoint
  • /configuration/all?siteId={siteId}
  • /configuration/{siteId}

The third scenario is different from the first two, because the route parameter, there are as many urls as possible siteId while others are the same url but only the query parameter changes.

Building the caching middleware

Let’s start by configuring option values, in my demo I will need two options:

  • Define caching duration
  • Define caching location (in my case client side)

Here we go:

The value “Location” is set to “Client” because I want to cache it client only, then “Cache-Control” header will be set to “Private”, “Public” if I choose “Any” if I want to set it Client side or in any Proxy.

The caching middleware has to be able to manage query string parameters (siteId) so we need to implement IResponseCachingFeature. This feature is activated when you configure your application with services.AddResponseCaching() and app.UseResponseCaching() in your Startup.cs.

Here is the implementation of the middleware:

Note that how its easy to access to the configuration from options we defined juste before (ConfigurationCachingOptions).

Our caching middleware is ready to be used, but we need to define on what endpoint it has to be activated, because for now it’s activated on any endpoint in within the app and manage siteId parameter in every endpoints within the app as well.

According to Microsoft documentation here are conditions of caching:

  • The request must result in a server response with a 200 (OK) status code.
  • The request method must be GET or HEAD.
  • In Startup.Configure, Response Caching Middleware must be placed before middleware that require caching. For more information, see ASP.NET Core Middleware.
  • The Authorization header must not be present.
  • Cache-Control header parameters must be valid, and the response must be marked public and not marked private.
  • The Pragma: no-cache header must not be present if the Cache-Control header isn’t present, as the Cache-Control header overrides the Pragma header when present.
  • The Set-Cookie header must not be present.
  • Vary header parameters must be valid and not equal to *.
  • The Content-Length header value (if set) must match the size of the response body.
  • The IHttpSendFileFeature isn’t used.
  • The response must not be stale as specified by the Expires header and the max-age and s-maxage cache directives.
  • Response buffering must be successful. The size of the response must be smaller than the configured or default SizeLimit. The body size of the response must be smaller than the configured or default MaximumBodySize.
  • The response must be cacheable according to the RFC 7234 specifications. For example, the no-store directive must not exist in request or response header fields. See Section 3: Storing Responses in Caches of RFC 7234 for details.

Activate the caching middleware by condition

To use our middleware with a condition, we need to use the method .UseWhen() and our condition is the route name: configuration

Now we are able to activate our middleware on every endpoint that match the request path configuration.

These urls will now be cached independently from each others:

If we take a look of all possible urls to manage, we have

  • /configuration/all
  • /configuration/all?siteId=1
  • /configuration/all?siteId=2
  • /configuration/1
  • /configuration/2

/configuration/1 and /configuration/2 are two differents Uri, so any conflict will appear, but all Uri that starts with /configuration/all may be in conflict if the parameter siteId is not well managed with IResponseCachingFeature and VaryByQueryKeys options:

var responseCachingFeature = context.Features.Get<IResponseCachingFeature>();
if (responseCachingFeature != null)
{   
   responseCachingFeature.VaryByQueryKeys = new[] { "siteId" }; 
}

Demo:

Conclusion

This demo showed you how to build a conditional Response caching middleware. If you prefer using attributes, it’s definitely not a bad option!

If you want to learn more about Response caching in a middleware you can go here Response caching in ASP.NET Core and here Response Caching Middleware in ASP.NET Core.

Hope you liked this article 😉 and if you have any comment to make, please make it I will be glad to read it :).

Written by

anthonygiretti

Anthony is a specialist in Web technologies (14 years of experience), in particular Microsoft .NET and learns the Cloud Azure platform. He has received twice the Microsoft MVP award and he is also certified Microsoft MCSD and Azure Fundamentals.