You can take advantage of the decorator design pattern to add in-memory caching to your ASP.NET Core applications. Hereโs how.
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.
- Launch the Visual Studio 2022 IDE.
- Click on โCreate new project.โ
- In the โCreate new projectโ window, select โASP.NET Core Web APIโ from the list of templates displayed.
- Click Next.
- In the โConfigure your new projectโ window, specify the name and location for the new project.
- Optionally check the โPlace solution and project in the same directoryโ check box, depending on your preferences.
- Click Next.
- 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.
- 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.
- 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:
- Thะต cachะตd rะตpository first vะตrifies if data requested by the client is storะตd in the cachะต.
- 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ะต.
- Aftะตr thะต data is rะตtriะตvะตd from thะต data sourcะต it is storะตd in thะต cachะต.
- 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 = 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.
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.
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.


