Minimal APIs – In Memory Caching

By | 20/03/2024

In this post, we will see how to use In-Memory Caching to improve the performances of our Minimal APIs.
But, why should we use caching?
“Caching temporarily stores copies of data so future requests for that data can be served faster. The idea is to reduce the number of expensive calls, such as database queries, making the overall experience quicker for the user and less resource-intensive for the server.”
“MemoryCache” is a built-in .NET feature that lets us store objects in memory.
It’s perfect for data that doesn’t change often but is requested frequently. By implementing “MemoryCache”, we can significantly reduce the load on your database and improve response times for your users.
For this post, we will use the same project used in all Minimal APIs’ post.


First of all, we modify the method GettAllDogs in the file DogCommands.cs:

public async Task LoadDefaultValuesDB()
{
    List<Dog> lstDogs = new List<Dog>();
    lstDogs.Add(new Dog { Id = 1, Name = "Max", Breed = "Labrador Retriever", Color = "Yellow" });
    lstDogs.Add(new Dog { Id = 2, Name = "Bella", Breed = "German Shepherd", Color = "Black" });
    lstDogs.Add(new Dog { Id = 3, Name = "Charlie", Breed = "Beagle", Color = "Tri-color" });
    lstDogs.Add(new Dog { Id = 4, Name = "Lucy", Breed = "Poodle", Color = "White" });
    lstDogs.Add(new Dog { Id = 5, Name = "Cooper", Breed = "Bulldog", Color = "Brindle" });
    lstDogs.Add( new Dog { Id = 6, Name = "Daisy", Breed = "Boxer", Color = "Fawn" });
    lstDogs.Add( new Dog { Id = 7, Name = "Bailey", Breed = "Rottweiler", Color = "Black and Tan" });
    lstDogs.Add( new Dog { Id = 8, Name = "Lola", Breed = "Siberian Husky", Color = "Grey" });
    lstDogs.Add( new Dog { Id = 9, Name = "Oliver", Breed = "Dachshund", Color = "Red" });
    lstDogs.Add( new Dog { Id = 10, Name = "Sadie", Breed = "Golden Retriever", Color = "Golden" });

    foreach (var item in lstDogs)
    {
        await _dataContext.Dogs.AddAsync(item);
    }

    await _dataContext.SaveChangesAsync();
}
    
public async Task<List<Dog>> GetAllDogs()
{
    _logger.LogInformation("BLL - Retrieving all dogs");
    var lstDogs = await _dataContext.Dogs.AsNoTracking().ToListAsync();
    if (!lstDogs.Any())
    {
        await LoadDefaultValuesDB();
    }

    return await _dataContext.Dogs.AsNoTracking().ToListAsync();
}


Then, in Program.cs, we add the MemoryCache in our Service:

using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using MinimalAPI;
using MinimalAPI.Commands;
using MinimalAPI.Extensions;
using MinimalAPI.Model;
using Serilog;

var builder = WebApplication.CreateBuilder(args);

// Configure Serilog
var logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .CreateLogger();

Log.Logger = logger;

builder.Host.UseSerilog();

// definition of DataContext
builder.Services.AddDbContext<DataContext>(opt => opt.UseInMemoryDatabase("DbDog"));
// definition of Dependency Injection
builder.Services.AddScoped<IDogCommands, DogCommands>();

// Add services to the container.
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthorization();

// Add services to the container.
builder.Services.AddMemoryCache();
...
...
...


Finally, we modify the Get endpoint to implement the “In Memory Caching”:

app.MapGet("/dog", async (IDogCommands commands, ILogger<Program> loggerInput, IMemoryCache cache) =>
{
    const string cacheKey = "dogsList";
    List<Dog> dogs = null;
    
    // Log the beginning of the request to get all dogs
    loggerInput.LogInformation("Requesting all dogs");

    // Try to retrieve the cached list of dogs
    if (!cache.TryGetValue(cacheKey, out dogs))
    {
        loggerInput.LogInformation("Cache miss. Fetching dogs from database...");
        
        // Execute the GetAllDogs command to retrieve all dogs
        dogs = await commands.GetAllDogs();

        // Check if the result is null or empty
        if (dogs == null || !dogs.Any())
        {
            // Log a warning indicating that no dogs were found
            loggerInput.LogWarning("No dogs found");

            // Return a NotFound result to indicate that no dogs were found
            return Results.NotFound();
        }
        
        // Set cache with a relative expiration time of 5 minutes
        var cacheEntryOptions = new MemoryCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromMinutes(5));
        cache.Set(cacheKey, dogs, cacheEntryOptions);

        loggerInput.LogInformation($"Fetched {dogs.Count} dogs from the database and cached.");
    }
    else
    {
        loggerInput.LogInformation($"Retrieved {dogs.Count} dogs from cache.");
    }
    // Return an Ok result with the list of retrieved dogs
    return Results.Ok(dogs);
}).RequireAuthorization();


We are done and now, if we run the APIs, the following will be the result:

The first time, data is not in the cache


The second time, data is in the cache



Leave a Reply

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