Step-by-Step Guide to Implementing Redis Caching in ASP.NET Core

Step-by-Step Guide to Implementing Redis Caching in ASP.NET Core

Boost Performance and Scalability with In-Memory Caching Techniques

Caching is a key technique for boosting the performance and scalability of web applications. In this article, we’ll set up caching in an ASP.NET Core Web API using Redis, a powerful in-memory data store.

Prerequisites

  1. ASP.NET Core Web API project: Ensure you have an existing ASP.NET Core Web API project.

  2. Redis: For local development, you can use Docker to set up Redis.

docker run --name redis-cache -d -p 6379:6379 redis

Windows

Redis does not officially support Windows, but you can use the community-supported builds.

  1. Download Redis for Windows:

  2. Install Redis:

    • Extract the downloaded .zip file.

    • Navigate to the extracted folder and run redis-server.exe to start the Redis server.


Linux

  1. Update Package Manager:

     sudo apt update
    
  2. Install Redis:

     sudo apt install redis-server
    
  3. Start Redis:

     sudo systemctl start redis
    
  4. Enable Redis to Start on Boot:

     sudo systemctl enable redis
    
  5. Test Redis Installation:

     redis-cli ping
    
  6. If Redis in running, you will see:

     PONG
    

macOS

  1. Install Using Homebrew:

     brew install redis
    
  2. Start Redis:

     brew services start redis
    
  3. Test Redis Installation:

     redis-cli ping
    
  4. After succession, you should see:

     PONG
    

  1. NuGet Packages: Install the following NuGet packages in your project:

    • StackExchange.Redis

    • Microsoft.Extensions.Caching.StackExchangeRedis

    dotnet add package StackExchange.Redis
    dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis

Step 1: Configure Redis in ASP.NET Core

In the Program.cs file, configure Redis as the caching provider:

var builder = WebApplication.CreateBuilder(args);

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

// Configure Redis caching
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
    options.InstanceName = "SampleApp_";
});

var app = builder.Build();

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

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();

Add the Redis connection string to the appsettings.json file:

{
  "ConnectionStrings": {
    "Redis": "localhost:6379"
  }
}

Step 2: Create a Caching Service

Create a service to manage caching logic. Add a new class RedisCacheService:

using Microsoft.Extensions.Caching.Distributed;
using System.Text.Json;

namespace CachingProject.Services
{
    public class RedisCacheService
    {
        private readonly IDistributedCache _cache;

        public RedisCacheService(IDistributedCache cache)
        {
            _cache = cache;
        }

        public async Task SetCacheAsync<T>(string key, T value, TimeSpan expirationTime)
        {
            var options = new DistributedCacheEntryOptions
            {
                AbsoluteExpirationRelativeToNow = expirationTime
            };

            var jsonData = JsonSerializer.Serialize(value);
            await _cache.SetStringAsync(key, jsonData, options);
        }

        public async Task<T?> GetCacheAsync<T>(string key)
        {
            var jsonData = await _cache.GetStringAsync(key);
            return jsonData is null ? default : JsonSerializer.Deserialize<T?>(jsonData);
        }

        public async Task RemoveCacheAsync(string key)
        {
            await _cache.RemoveAsync(key);
        }
    }
}

Step 3: Register the Caching Service

In Program.cs, register the RedisCacheService:

builder.Services.AddSingleton<RedisCacheService>();

Step 4: Use Redis Caching

You can use this service as per your architecture style here we are showing Redis Caching in a Controller, For example, in a EmployeesController:

using System.Text.Json;
using CachingProject.Data;
using CachingProject.Dto;
using CachingProject.Model;
using CachingProject.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace CachingProject.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class EmployeesController : ControllerBase
    {
        private readonly AppDbContext _context;
        private readonly RedisCacheService _cacheService;

        public EmployeesController(AppDbContext context, RedisCacheService cacheService)
        {
            _context = context;
            this._cacheService = cacheService;
        }

        // Add Employee API
        [HttpPost]
        public async Task<IActionResult> AddEmployee([FromBody] AddEmployeeDto addEmployeeDto)
        {
            if (addEmployeeDto == null)
            {
                return BadRequest("Employee data is required.");
            }

            var employee = new Employee
            {
                FirstName = addEmployeeDto.FirstName,
                LastName = addEmployeeDto.LastName,
                Email = addEmployeeDto.Email,
                Role = addEmployeeDto.Role,
                DateOfJoining = addEmployeeDto.DateOfJoining
            };

            _context.Employees.Add(employee);
            await _context.SaveChangesAsync();

            return Ok(new { Message = "Employee added successfully", EmployeeId = employee.Id });
        }

        // Get Employees API
        [HttpGet]
        public async Task<IActionResult> GetEmployees()
        {
            var cacheKey = "employees";
            // Check if data exists in cache
            var cachedProduct = await _cacheService.GetCacheAsync<List<EmployeeDto>>(cacheKey);
            if (cachedProduct != null)
            {
                return Ok(new { Source = "cache", cachedProduct });
            }
            // if not in cache then fetch data from a database
            var employees = await _context.Employees
                .Select(e => new EmployeeDto
                {
                    Id = e.Id,
                    FirstName = e.FirstName,
                    LastName = e.LastName,
                    Email = e.Email,
                    Role = e.Role,
                    DateOfJoining = e.DateOfJoining
                })
                .ToListAsync();
            // Save data to cache for required timespan
            await _cacheService.SetCacheAsync(cacheKey, employees, TimeSpan.FromMinutes(5));
            return Ok(new { Source ="Database",employees});
        }
    }

}

Step 5: Testing the Caching Implementation

  1. Run the application and access GetEmployees endpoint, use break point for better understanding and go line by line.

  2. The first time, data will be retrieved from the database and then it will be saved to the cache.

  3. Subsequent requests will retrieve data from the Redis cache for the specified time duration.

Step 6: Verify Implementation

To verify, run the application and see the response from endpoint:

Since this is the first request, the data will be retrieved from the database.

Now, let's try again. This time, the data should be fetched from the cache, so fingers crossed.🤞.

You can use Redis CLI to verify the data stored in Redis:

  • If only string values are stored in Redis, you can use GET SampleApp_employees. Here, SampleApp_ is the instance name we set up in Program.cs, and "employees" is the key we used when setting or retrieving cached data. In the controller, we defined it as var cacheKey = "employees";.

  • If you see the error (error) WRONGTYPE Operation against a key holding the wrong kind of value, it means you are trying to retrieve a key as a string (GET), but the key holds a different type of value, like a list, hash, or set. In our case, the key SampleApp_employees does not hold a string but a complex data type because we store a list of objects under this key.

    Steps to Debug and Fix:

    1. Check the Key Type: Use the TYPE command in Redis CLI to determine the data type of the key:

       TYPE SampleApp_employees
      

      Possible results:

      • string: The key holds a string value.

      • list, hash, set, etc.: The key holds a different type of value.

Note: in our case key will be of hash type.

  1. Inspect the Key Value: If the key is a hash, list, or another type, we need to use the appropriate command to view its contents. For example:

    • For a hash:

        HGETALL SampleApp_employees
      
    • For a list:

        LRANGE SampleApp_employees 0 -1
      

      Since our key holds a hash type value, we will use: HGETALL SampleApp_employees. This should display the cached data in JSON format.

Conclusion:

By integrating Redis caching into an ASP.NET Core Web API, we can greatly enhance the performance and scalability of our application. Redis's speed and flexibility make it an excellent choice for caching solutions in modern web applications.