Caching via IMemoryCache in ASP.NET

Output caching is the process by which you store responses in the server’s memory or in a relatively fast external in-memory store (such as Redis) for a set period of time and responding to requests from that cache while alleviating strains on databases or other upstream systems where generating the output for that request can be a relatively costly operation. Consider a weather forecast application as an example. There may be several databases, other systems and processes involved to generate the forecast for a given zip code. But once that hard processing work is completed for a given zip code, the webserver can save those responses for say an hour (or another desirable time period threshold) in a low-latency in-memory mechanism and serve any other users wanting the same weather forecast for that same zip code without redoing all the hard work again. The practice of caching can significantly improve the performance of your applications.

Unfortunately, the OutputCache mechanism that we had in .NET Full Framework has still not been implemented in the cross-platform version of .NET (Core, 5, 6, etc.) as of the time of this writing (June 2022). However, there are talks of bringing back that feature in one form or another in the next major release of ASP.NET. But what about in the meantime? Thankfully, you don’t have to write a caching system from scratch. There is a piece of middleware in the framework that you can use today to give you some basic caching functionality for your ASP.NET applications – Microsoft.Extensions.Caching.Memory/IMemoryCache.

Hands-On Guide

You can wire up IMemoryCache in you Program.cs (or Startup.cs in older templates) like so:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

// Enable caching
builder.Services.AddMemoryCache();

var app = builder.Build();

app.MapControllers();

app.Run();

With that in place, now you can call on IMemoryCache in any of your Controllers or in any of your other classes through the magic of Dependency Injection (DI):

private readonly IMemoryCache memoryCache;

public HelloWorldController(IMemoryCache memoryCache)
{
	this.memoryCache = memoryCache;
}

IMemoryCache allows you to store data – strings, integers, other primitive types or objects of any kind, lists, arrays, just about anything – as key/value pairs. You provide a name/id along with a payload and ASP.NET will store in-memory and then you can retrieve that chunk of data by asking for it with the same key that you used to store it.

Consider my example below. This HelloWorld controller has a get endpoint that takes a name and returns a greeting with that name. This endpoint calls out to a fake GetGreetingFromDatabase method to generate that greeting message. This method simulates an expensive operation. Each time a request comes in, the endpoint checks the cache to see if a greeting was already generated for this name and if so, retrieves it from the cache; otherwise, it calls the database to have that greeting generated.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;

namespace ExploreCaching.Controllers
{
    [ApiController]
    [Route("greeting")]
    public class HelloWorldController
    {
        private readonly IMemoryCache memoryCache;

        public HelloWorldController(IMemoryCache memoryCache)
        {
            this.memoryCache = memoryCache;
        }

        [Route("{name}")]
        public string Get(string name)
        {
            if (memoryCache.TryGetValue<string>(name, out var greeting))
            {
                Console.WriteLine($"{name} is already in cache... fetching from there.");
                return greeting;
            }
            else
            {
                Console.WriteLine($"{name} is NOT in cache. Getting greeting from the database.");

                greeting = GetGreetingFromDatabase(name);

                memoryCache.Set(name, greeting);
                return greeting;
            }
        }

        private string GetGreetingFromDatabase(string name)
        {
            // This method is a pretend database interaction
            // or any other resource intensive operation
            Thread.Sleep(5000);
            return $"Hello, {name}! I was created at {DateTime.Now.ToString("HH:mm:ss")}.";
        }
    }
}

Run the application, visit the endpoint in the browser with a name parameter and you’ll see it return the greeting. Take a look at the console output. It shows that a request for “Tom” came in and it did not have a greeting for Tom in the cache and so it generates it from the database and returns it.

web browser showing the greeting endpoint return a greeting for Tom

Next, take a look at the Console output. You’ll see that the system didn’t have a greeting for Tom in cache and thus it asks the database to generate one.

Console output for the web app showing that the requested item was not in cache and is generated from the database.

Now experiment with different names and inspect the output. Every time the system encounters a new name, it is generated from the database and then on subsequent requests for the same name, it is then fetched from the cache.

Console output showing repeat requests being fetched from cache.

Closing

Congratulations! You have successfully implemented server-side in-memory caching in ASP.NET. If you want to play with today’s exercise on your own, please check out my GitHub repo for this project:

GitHub Repository: tvaidyan / IMemoryCache-In-ASP.NET

Leave a Comment

Your email address will not be published. Required fields are marked *