Using Cache in Methods in ASP.NET Core with OutputCache

It’s very easy to use caching in ASP.NET Core with OutputCache. We just have to enable it. Then we place an attribute in the actions or endpoints where we want to use it. That’s it. But sometimes we will want to use it within a method. We will see that in this post.

Note: For this post we will use ASP.NET Core 9, however, the content of this post should work for .NET 7+.

We will start by creating an ASP.NET Core Web API project.

Then, we will enable OutputCache by going to the program.cs class and placing the following code:

using OutputCacheMethod;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();

builder.Services.AddOutputCache(options =>
{
    options.DefaultExpirationTimeSpan = TimeSpan.FromSeconds(15);
});

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseHttpsRedirection();

app.UseOutputCache();

app.UseAuthorization();

app.MapControllers();

app.Run();

Then we can use it, for example, in the WeatherForecastController class like this:

[HttpGet(Name = "GetWeatherForecast")]
[OutputCache]
public async Task<IEnumerable<WeatherForecast>> Get()
{
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();
}

This has the effect that the first time we make the HTTP request, the action will be executed. Then, in subsequent HTTP requests, while the cache is in effect, the action itself will not be executed. The data from the cache will simply be returned. However, what if I want to add code that is executed whenever we make a request to the route, regardless of whether we have data in the cache or not? There are certainly several ways to do this. One of them is to move the OutputCache from an attribute to be able to be used within the method.

To do this, the first thing we need to do is inject the IOutputCacheStore service into the class:

private readonly IOutputCacheStore outputCacheStore;
 
public WeatherForecastController(ILogger<WeatherForecastController> logger,
    IOutputCacheStore outputCacheStore)
{
    _logger = logger;
    this.outputCacheStore = outputCacheStore;
}

Next, we need two helper functions. One to convert from object to byte array, and another to convert from byte array to object. This is because the cache works by storing data in byte array format. So, we will create the helper functions (in my case I will do it in the same WeatherForecastController class):

private T ConvertFromBytesToObject<T>(byte[] bytes)
{
    var json = Encoding.UTF8.GetString(bytes);
    return JsonSerializer.Deserialize<T>(json)!;
}

private byte[] ConvertFromObjectToBytes(object obj)
{
    var json = JsonSerializer.Serialize(obj);
    return Encoding.UTF8.GetBytes(json);
}

With this in place, we can modify the Get method to use the cache internally. The first thing we do is build a key, which we will use to cache the data. Then, from that key, we will get the cached data. If we find such data, we convert it from a byte array to an IEnumerable<WeatherForecast>, and return this. If the data is not found in the cache, we build the WeatherForecast array (which we can assume comes from a database). Then, we convert it to a byte array. Next, we cache it. Finally, we return it. In the end, the method looks like this:

[HttpGet(Name = "GetWeatherForecast")]
public async Task<IEnumerable<WeatherForecast>> Get()
{
    _logger.LogInformation("Executing GetWeatherForecast");

    var key = "weatherforecasts-get-all";

    var dataFromCacheStore = await outputCacheStore.GetAsync(key, default);

    if (dataFromCacheStore is not null)
    {
        var dataDeserialized = ConvertFromBytesToObject<IEnumerable<WeatherForecast>>(dataFromCacheStore);
        return dataDeserialized;
    }


    await Task.Delay(3000);
    var value = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();

    var valueToStoreInCache = ConvertFromObjectToBytes(value);

    await outputCacheStore.SetAsync(key, valueToStoreInCache, tags: null,
        validFor: TimeSpan.FromSeconds(15), default);

    return value;
}

And that’s it, with this we have managed to use OutputCache within a method.

Courses

If you want to learn more about .NET or other technologies, please check out my Udemy courses today (discount coupons included): https://www.felipe-gavilan.com/cursos?idioma=eng

One comment

Leave a comment