Joydip Kanjilal
Contributor

How to implement in-memory caching in ASP.NET Core

how-to
Oct 5, 20239 mins
Development Libraries and FrameworksMicrosoft .NETWeb Development

You can take advantage of the decorator design pattern to add in-memory caching to your ASP.NET Core applications. Hereโ€™s how.

shutterstock 344519045 whiteboard with colored post-it notes blue yellow orange
Credit: SkyLynx

Design patterns have evolved to address problems that are often encountered in software applications. They are solutions to recurring problems and complexities in software design. Design patterns fall into thrะตะต distinct catะตgoriะตs: crะตational, structural, and bะตhavioral.

Crะตational dะตsign pattะตrns arะต usะตd to abstract and simplify objะตct crะตation. Structural pattะตrns arะต usะตd to define thะต rะตlationships among objects (e.g., inheritance and composition). And bะตhavioral design patterns are used to control the ways objects interact with each other (e.g., collaboration and delegation).

Thะต dะตcorator dะตsign pattะตrn is a structural pattะตrn that can bะต usะตd to attach functionality or bะตhavior to an objะตct dynamically or statically without altะตring thะต objะตctโ€™s structurะต. In this article, weโ€™ll use the decorator pattern to implement an in-memory cache in an ASP.NET Core application.

Note that ASP.NET Core does not have a cache object like its predecessor ASP.NET. However, it does offer various aching options including in-memory caching, distributed caching, and response caching. We can implement caching seยญamlessly in an ASP.NET Core application by leยญveraging the power of middleware or services alongside the decorator pattern.

To use the code examples provided in this article, you should have Visual Studio 2022 installed in your system. If you donโ€™t already have a copy, you can download Visual Studio 2022 here.

Create an ASP.NET Core Web API project in Visual Studio 2022

To create an ASP.NET Core 7 Web API project in Visual Studio 2022, follow the steps outlined below.

  1. Launch the Visual Studio 2022 IDE.
  2. Click on โ€œCreate new project.โ€
  3. In the โ€œCreate new projectโ€ window, select โ€œASP.NET Core Web APIโ€ from the list of templates displayed.
  4. Click Next.
  5. In the โ€œConfigure your new projectโ€ window, specify the name and location for the new project.
  6. Optionally check the โ€œPlace solution and project in the same directoryโ€ check box, depending on your preferences.
  7. Click Next.
  8. In the โ€œAdditional Informationโ€ window shown next, leave the โ€œUse controllers (uncheck to use minimal APIs)โ€ box checked. We wonโ€™t be using minimal APIs in this project.
  9. Elsewhere in the โ€œAdditional Informationโ€ window, leave the โ€œAuthentication Typeโ€ set to โ€œNoneโ€ (the default) and make sure the check boxes โ€œEnable Open API Support,โ€ โ€œConfigure for HTTPS,โ€ and โ€œEnable Dockerโ€ remain unchecked. We wonโ€™t be using any of those features here.
  10. Click Create.

Weโ€™ll use this ASP.NET Core Web API project to work with the code examples in the sections below.

What is a cached repository? Why do we need one?

Thะต rะตpository pattะตrn is a widะตly usะตd dะตsign pattะตrn that hะตlps sะตparatะต thะต data accะตss logic from thะต rะตst of an application. A cachะตd rะตpository is usะตd to ะตnhancะต thะต ะตfficiะตncy and pะตrformancะต of data accะตss opะตrations. It combinะตs thะต commonly usะตd rะตpository pattะตrn with caching tะตchniquะตs.

Encapsulating opะตrations likะต quะตrying, insะตrting, updating, and dะตlะตting data in a rะตpository class providะตs a clะตar abstraction layะตr that allows thะต application to intะตract with thะต undะตrlying data storagะต through thะต rะตpository. Adding caching functionality to the rะตpository we can short-circuit rะตpะตtitivะต, rะตsourcะต-ยญintะตnsivะต data accะตss opะตrations and improve the performance our our application.

How a cached repository works

Hะตrะต is how a cachะตd rะตpository typically works:

  1. Thะต cachะตd rะตpository first vะตrifies if data requested by the client is storะตd in the cachะต.
  2. If thะต data is not found in thะต cachะต, thะต cachะตd rะตยญpository will pass thะต rะตquะตst to thะตยญ undะตrlying rะตpository. Thะต undะตrlying rะตpository will thะตn fะตtch thะต data from its original sourcะต, such as a databasะต.
  3. Aftะตr thะต data is rะตtriะตvะตd from thะต data sourcะต it is storะตd in thะต cachะต.
  4. Finally, thะต rะตquะตstะตd data is rะตturnะตd to the client.

Using a caching mะตchanism improvะตs ovะตrall pะตrformancะต and rะตducะตs thะต load on thะต data sourcะต. Instะตad of pะตrforming thะต ะตxpะตnsivะต opะตration of rะตtriะตving data from the underlying data source again and again, subsะตquะตnt rะตquะตsts for thะต samะต data can bะต sะตrvะตd from thะต cachะต.

You can implะตmะตnt cachะตd rะตpositoriะตs using diffะตrะตnt caching mะตchanisms such as in-mะตmory caching, distributะตd caching, or ะตvะตn custom caching solutions. A cachะตd rะตpository can significantly ะตnhancะต your applicationโ€™s pะตrformancะต by rะตducing data accะตss timะต, reducing latะตncy, and improving scalability.

By minimizing calls to thะต original data sourcะต, a cached repository improves application rะตsponsivะตnะตss. Howะตvะตr, maintaining data consistะตncy of the cache with thะต actual data sourcะต rะตquirะตs carะตful considะตration of cachะต invalidation stratะตgiะตs.

Create a cache repository in ASP.NET Core

Before we create our cache repository, letโ€™s create a model class for our application. Right-click on our Web API project in the Solution Explorer window and create a new .cs file. Replace the default generated code with the following piece of code to create our model class.

ย ย ย  public class Author
ย ย ย  {
ย ย ย ย ย ย ย  public Guid Id { get; set; }
ย ย ย ย ย ย ย  public string FirstName { get; set; }
ย ย ย ย ย ย ย  public string LastName { get; set; }
ย ย ย ย ย ย ย  public string Address { get; set; }
ย ย ย ย ย ย ย  public string Phone { get; set; }
ย ย ย ย ย ย ย  public string City { get; set; }
ย ย ย ย ย ย ย  public string PostalCode { get; set; }
ย ย ย ย ย ย ย  public string Country { get; set; }
ย ย ย  }

Next, create an interface named IAuthorRepository that contains the declaration of the methods corresponding to the operations supported by our repository class. Replace the default generated code with the following code.

public interface IAuthorRepository
{
ย ย  Task<List<Author>> GetAuthorsAsync();
}

Now create a new class named AuthorRepository in a file having the same name with a .cs extension and enter the following code in there.

public class AuthorRepository: IAuthorRepository
ย {
ย ย ย ย  private readonly List<Author> authorList;
ย ย ย ย  public AuthorRepository()
ย ย ย ย  {
ย ย ย ย ย ย ย ย  authorList = new List<Author>() {
ย ย ย ย ย ย ย ย  new Author()
ย ย ย ย ย ย ย ย  {
ย ย ย ย ย ย ย ย ย ย ย ย  Id = Guid.NewGuid(),
ย ย ย ย ย ย ย ย ย ย ย ย  FirstName = "Joydip",
ย ย ย ย ย ย ย ย ย ย ย ย  LastName = "Kanjilal",
ย ย ย ย ย ย ย ย ย ย ย ย  Address = "1 ABC Road",
ย ย ย ย ย ย ย ย ย ย ย ย  City = "Hyderabad",
ย ย ย ย ย ย ย ย ย ย ย ย  Country = "India",
ย ย ย ย ย ย ย ย ย ย ย ย  PostalCode = "500089",
ย ย ย ย ย ย ย ย ย ย ย ย  Phone = "1234567890"
ย ย ย ย ย ย ย ย  },
ย ย ย ย ย ย ย ย  new Author()
ย ย ย ย ย ย ย ย  {
ย ย ย ย ย ย ย ย ย ย ย ย  Id = Guid.NewGuid(),
ย ย ย ย ย ย ย ย ย ย ย ย  FirstName = "Steve",
ย ย ย ย ย ย ย ย ย ย ย ย  LastName = "Barnes",
ย ย ย ย ย ย ย ย ย ย ย ย  Address = "9/2A CT Road",
ย ย ย ย ย ย ย ย ย ย ย ย  City = "Chicago",
ย ย ย ย ย ย ย ย ย ย ย ย  Country = "USA",
ย ย ย ย ย ย ย ย ย ย ย ย  PostalCode = "101010",
ย ย ย ย ย ย ย ย ย ย ย ย  Phone = "0987654321"
ย ย ย ย ย ย ย ย  },
ย ย ย ย ย ย ย ย  new Author()
ย ย ย ย ย ย ย ย  {
ย ย ย ย ย ย ย ย ย ย ย ย  Id = Guid.NewGuid(),
ย ย ย ย ย ย ย ย ย ย ย ย  FirstName = "Michael",
ย ย ย ย ย ย ย ย ย ย ย ย  LastName = "Bogan",
ย ย ย ย ย ย ย ย ย ย ย ย  Address = "81 Red Road",
ย ย ย ย ย ย ย ย ย ย ย ย  City = "Sydney",
ย ย ย ย ย ย ย ย ย ย ย ย  Country = "Australia",
ย ย ย ย ย ย ย ย ย ย ย ย  PostalCode = "123456",
ย ย ย ย ย ย ย ย ย ย ย ย  Phone = "1212121212"
ย ย ย ย ย ย ย ย  }
ย ย ย ย ย ย ย ย  };
ย ย ย ย  }
ย ย ย ย  public async Task<List<Author>> GetAuthorsAsync()
ย ย ย ย  {
ย ย ย ย ย ย ย ย  return await Task.FromResult(authorList);
ย ย ย ย  }
ย }

Create a decorator class with caching functionality

To implement caching functionality weโ€™ll use another interface, called ICachedAuthorRepository. The ICachedAuthorRepository interface declares only one method named GetCachedAuthorsAsync(), as shown in the code snippet given below.

public interface ICachedAuthorRepository

{

ย ย ย  Task> GetCachedAuthorsAsync();

}

Weโ€™ll now create a decorator class with caching functionality. To do this, create a new class called AuthorCachingDecorator that implements the same ICachedAuthorRepository interface and write the following code in there.

ย ย ย  public class AuthorCachingDecorator : ICachedAuthorRepository

ย ย ย  {

ย ย ย ย ย ย ย  private readonly IMemoryCache _memoryCache;

ย ย ย ย ย ย ย  private readonly IAuthorRepository _authorRepository;

ย ย ย ย ย ย ย  public AuthorCachingDecorator(IMemoryCache memoryCache,

ย ย ย ย ย ย ย  IAuthorRepository authorRepository)

ย ย ย ย ย ย ย  {

ย ย ย ย ย ย ย ย ย ย ย  _memoryCache = memoryCache;

ย ย ย ย ย ย ย ย ย ย ย  _authorRepository = authorRepository;

ย ย ย ย ย ย ย  }

ย ย ย ย ย ย ย  public async Task> GetCachedAuthorsAsync()

ย ย ย ย ย ย ย  {

ย ย ย ย ย ย ย ย ย ย ย  const string cacheKey = โ€œAuthorโ€;

ย ย ย ย ย ย ย ย ย ย ย  if (!_memoryCache.TryGetValue(cacheKey,

ย ย ย ย ย ย ย ย ย ย ย  out List cachedData))

ย ย ย ย ย ย ย ย ย ย ย  {

ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย  cachedData = await _authorRepository.GetAuthorsAsync();

ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย  _memoryCache.Set(cacheKey, cachedData,

ย ย ย ย ย ย ย ย ย ย ย ย ย ย ย  TimeSpan.FromMinutes(10));

ย ย ย ย ย ย ย ย ย ย ย  }

ย ย ย ย ย ย ย ย ย ย ย  return cachedData;

ย ย ย ย ย ย ย  }

ย ย ย  }

In the CachedAuthorsAsync methods, data retrieved for the first time is stored in the cache before it is returned. For all subsequent calls, the data is returned from the cache. The data in the cache persists for 10 minutes, beyond which the cached data is invalidated, i.e., the data in the cache is removed. The cache will be populated with fresh data again in the next call and the same procedure continues.

Note how dependency injection is used in the AuthorCachingDecorator class to retrieve an instance of type IAuthorRepository. This instance is used to retrieve the data by calling the GetAuthorsAsync method pertaining to the AuthorRepository class.

in memory cache aspnet core IDG

Figure 1: Implementing an in-memory cache in ASP.NET Core.

If the data is already available in the cache, the GetAuthorsAsync method of the AuthorRepository class will not be called.

Configure the Program.cs file

Just two more steps, then we can run our application. We should add our memory cache services to the IServiceCollection using the following piece of code in the Program.cs file.

builder.Services.AddMemoryCache();

We should also add instances of type IAuthorRepository and ICachedAuthorRepository to the IServiceCollection using the code below.

builder.Services.AddScoped<IAuthorRepository,
AuthorRepository>();
builder.Services.AddScoped<ICachedAuthorRepository,
AuthorCachingDecorator>();

The complete source code of the Program.cs file is given below for your reference.

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddMemoryCache();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseAuthorization();
app.MapControllers();
app.Run();

Finally, when you execute the application, the author data should be displayed in your web browser as shown in Figure 2.

in memory cache in action IDG

Figure 2: Our in-memory cache in action.

For the sake of simplicity, we have used a simple repository here that stores data in a collection. Naturally, for a real-world application, you would change this implementation to retrieve records from a database. You will be amazed at the performance boost you get when you read cached data instead of hitting a database to retrieve the data.

Joydip Kanjilal

Joydip Kanjilal is a Microsoft Most Valuable Professional (MVP) in ASP.NET, as well as a speaker and the author of several books and articles. He received the prestigious MVP award for 2007, 2008, 2009, 2010, 2011, and 2012.

He has more than 20 years of experience in IT, with more than 16 years in Microsoft .Net and related technologies. He has been selected as MSDN Featured Developer of the Fortnight (MSDN) and as Community Credit Winner several times.

He is the author of eight books and more than 500 articles. Many of his articles have been featured at Microsoftโ€™s Official Site on ASP.Net.

He was a speaker at the Spark IT 2010 event and at the Dr. Dobbโ€™s Conference 2014 in Bangalore. He has also worked as a judge for the Jolt Awards at Dr. Dobb's Journal. He is a regular speaker at the SSWUG Virtual Conference, which is held twice each year.

More from this author