Caching MVC responses

How to cache the response from an MVC controller to increase service performance

Home DailyDrop

Daily Knowledge Drop

ASP.NET Core has out of the box configurable response caching functionality which can be leveraged to improve the performance of a service using controllers.

The functionality described in this post is only for caching responses from an MVC controller - to cache responses from a minimal API, have a look at this post which details output caching on minimal APIs.


Code

Setup

The default template Weather API project is used for the below example, with the Use Controllers options checked.


Startup

First step, enabling the caching functionality in the service - this is done in the program.cs, and involves adding two lines of code:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
// setup caching with the DI container
builder.Services.AddResponseCaching();

var app = builder.Build();

// setup caching in the pipeline
app.UseResponseCaching();
app.UseAuthorization();
app.MapControllers();

app.Run();

This follows a fairly standard pattern when it comes to adding functionality to an ASP.NET Core application:

  • The AddResponseCaching method is called to register the required services with the dependency injection container
  • The UseResponseCaching method is called to insert the caching logic into the middleware pipeline

Next step, enabling caching on a controller.


Controller

Now that we have the base caching functionality configured in the service, the next step is to actual enable caching for a specific endpoint/controller. This is done by adding an attribute to the relevent controller method. In the WeatherForecastController.cs class:

[HttpGet]
[ResponseCache(Duration = 30, Location = ResponseCacheLocation.Any)]
public IEnumerable<WeatherForecast> Get()
{
    Console.WriteLine($"{nameof(Get)} method in {nameof(WeatherForecastController)} called");

    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();
}

The ResponseCache attribute is added to the Get method (the method called when the endpoint is called). In this example, the cache is set to expire every 30 seconds.

And thats it! We now have basic caching functionality working on the service.


Output

If we run the service and browse to the /weatherforecast endpoint, the weather payload will be returned, and logging at the console, the following will be output:

Get method in WeatherForecastController called

Refreshing the endpoint within 30 seconds (the duration of the cache), will yield the same payload, and cause no output to the console - the results are returned from the cache, and the controller is never called.


Vary

Not explicitly show in the above example, but it is also possible to vary the cache response by a specific key.

If the attribute parameters were changed to include the VaryByHeader parameter:

[HttpGet]
[ResponseCache(Duration = 30, Location = ResponseCacheLocation.Any, VaryByHeader = "User-Agent")]
public IEnumerable<WeatherForecast> Get()
{
    Console.WriteLine($"{nameof(Get)} method in {nameof(WeatherForecastController)} called");

    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();
}

Then a different cache is created for each distinct User-Agent header value sent on a request, each cache independent of one another. This way, the cache of one called will operate independently from the cache of a different caller. It is also possible to vary the cache by a specific query string key.


Notes

When simple caching is required, the built-in functionality is an easy to implement, low effort option to enhance the performance of a service.


References

ASP.NET Core Response Caching

Daily Drop 231: 10-01-2023

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net mvc controller cache