Implementing response caching in ASP.NET Core

ASP.NET Core implements a declarative way to manage caching directives in the responses of our web service. Furthermore, it provides an attribute that can be used for caching purposes. The attribute implementation is also compatible with the HTTP 1.1 caching specification, therefore, it becomes easy to implement these caching standards using ASP.NET Core's out-of-the-box implementations, and we don't have to worry about the details of each request. We can specify the caching behavior using the [ResponseCache] attribute that's exposed by ASP.NET Core:

namespace Catalog.API.Controllers
{
[Route("api/items")]
[ApiController]
[Authorize]
public class ItemController : ControllerBase
{
private readonly IItemService _itemService;
private readonly IEndpointInstance _messageEndpoint;

public ItemController(IItemService itemService,
IEndpointInstance messageEndpoint)
{
_itemService = itemService;
_messageEndpoint = messageEndpoint;
}

...

[HttpGet("{id:guid}")]
[ItemExists]
[ResponseCache(Duration = 100, VaryByQueryKeys = new []{"*"})]
public async Task<IActionResult> GetById(Guid id)
{
var result = await _itemService.GetItemAsync(new
GetItemRequest { Id = id });
return Ok(result);
}
...
}
}

For example, in this case, the code defines a caching directive on the GetById action method of the ItemController class. The ResponseCache attribute sets a Duration field and a VaryByQueryKeys field: the first one corresponds to the max-age instruction while the second one reflects the Vary HTTP header.

So far, we have only added the Cache-Control directive to the server responses. Therefore, we are not implementing any caching. The caching directives can be used by a third-party system or application, such as the client of our service, to cache information.

In addition to the ResponseCache attribute, it is necessary to put caching middleware in front of the web service. ResponseCachingMiddleware is the default middleware provided by ASP.NET Core. It is compliant with the HTTP 1.1 caching specification (https://tools.ietf.org/html/rfc7234). Therefore, if we think about the ResponseCachingMiddleware type, it is possible to change the previous schema in the following way:

The ResponseCachingMiddleware class can be initialized using the AddResponseCaching extension method provided by the Microsoft.Extensions.DependencyInjection package. In addition, we can add the ResponseCachingMiddleware middleware in the middleware pipeline by executing the UseResponseCaching extension method in the Configure method of the Startup class. The ResponseCachingMiddleware type checks whether a response is cacheable and stores responses and serves answers from the cache. We can add ResponseCachingMiddleware to the service pipeline by editing the Startup class in the Catalog.API project:

public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
services
...
.AddResponseCaching();
}

public void Configure(IApplicationBuilder app,
IHostingEnvironment env)
{
...
app.UseHsts();
app.UseMiddleware<ResponseTimeMiddlewareAsync>();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseResponseCaching();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}

The preceding code adds the caching middleware, but it is not enough to initialize a caching mechanism. Therefore, if we try to call the route decorated with the ResponseCache attribute using the curl command, we will receive the following response:

curl --verbose -X GET http://localhost:5000/api/items/08164f57-1e80-4d2a-739a-08d6731ac140
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 5000 (#0)
> GET /api/items/08164f57-1e80-4d2a-739a-08d6731ac140 HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Sat, 05 Jan 2019 16:55:21 GMT
< Content-Type: application/json; charset=utf-8
< Server: Kestrel
< Cache-Control: public,max-age=100
< Transfer-Encoding: chunked
< X-Response-Time-ms: 21
<
* Connection #0 to host localhost left intact

As you can see, Cache-Control tells us that this information can be shared in the cache and that max-age is 100 seconds. Therefore, if we call the same route after N (with N<100) seconds, we can also see the Age header, which contains the time (in seconds) that the object has been in the cache for.

Moreover, if we call the same route after N seconds (with N >= 100), we will hit the server and cache a new response in the memory. It is also necessary to note that we can cancel the cache middleware by appending a query string parameter to the calling URL. This is because we specified the Vary header using the following field:

 VaryByQueryKeys = new []{"*"}
It may be the case that defining the ResponseCache attribute uses the VaryByQueryKeys field. In that case, the attribute will not be able to detect the query string variation. Furthermore, the routes that are covered by the ResponseCache attribute will retrieve the following exception: 

"ClassName":"System.InvalidOperationException","Message":"'VaryByQueryKeys' requires the response cache middleware."

An important thing to note about the ResponseCachingMiddleware class is that it uses the IMemoryCache interface to store the content of the response. Hence, if you examine the definition of the class on GitHub (https://github.com/aspnet/AspNetCore/.../ResponseCachingMiddleware.cs), you will see the following constructor:

...
public ResponseCachingMiddleware( RequestDelegate next, IOptions<ResponseCachingOptions> options, ILoggerFactory loggerFactory, ObjectPoolProvider poolProvider) : this( next, options, loggerFactory, new ResponseCachingPolicyProvider(), new MemoryResponseCache(new MemoryCache(new
MemoryCacheOptions { SizeLimit = options.Value.SizeLimit })),
new ResponseCachingKeyProvider(poolProvider, options)) { }

internal ResponseCachingMiddleware(
RequestDelegate next,
IOptions<ResponseCachingOptions> options,
ILoggerFactory loggerFactory,
IResponseCachingPolicyProvider policyProvider,
IResponseCache cache,
IResponseCachingKeyProvider keyProvider)
{
....

The preceding method signature defines the constructor of the ResposeCachingMiddleware class. The constructor uses the this keyword to refer to another internal constructor overload that's used to initialize the properties of the class. As you can see, the IResponseCache interface is initialized using the MemoryCache type by default, which extends the IMemoryCache interface.

The IMemoryCache interface represents the cache stored in the webserver. Additionally, it is possible to use the IMemoryCache interface as a standalone component by adding the AddMemoryCache() extension method to the initialization of the services in the Startup class:

        public void ConfigureServices(IServiceCollection services)
{
services
...
.AddMemoryCache();
}

This approach allows you to use the IMemoryCache interface through the dependency injection engine, which means you can call the GetOrCreate and GetOrCreateAsync methods to set the cache values in the memory of the webserver. Although the in-memory cache provides a really good abstraction for us to cache data, it does not work in a distributed approach. Therefore, if you want to store and share caches between different web servers, ASP.NET Core provides the tools you'll need to implement a distributed cache. In the next section, we will learn a bit more about how to implement a distributed cache in ASP.NET Core. 

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.84.29