blog
Ottorino Bruni  

How to Implement Redis Cache in .NET using Minimal APIs and C#

Introduction

In a previous article, I wrote about How to Implement In-Memory Caching in .NET Console App Using C#, where I introduced the basic concept of caching and explained the difference between first-level (in-memory) and second-level (distributed) caching. In-memory caching is a powerful tool to boost application performance by storing frequently accessed data in memory, reducing the need for expensive database queries.

However, while in-memory caching works well for single-server applications, it becomes less effective in distributed environments where multiple instances of the application are running. In such cases, a distributed cache, like Redis, is a better solution. Redis allows data to be cached centrally, making it accessible across multiple servers and application instances, ensuring consistency and scalability.

In this article, I’ll build on the in-memory caching example and show you how to implement Redis caching in a .NET using Minimal APIs and C#. By the end, you’ll have a working example that seamlessly transitions from in-memory caching to a distributed Redis cache, enabling your application to handle larger loads and scale efficiently.


What is Redis?

Redis (Remote Dictionary Server) is a high-performance, distributed, in-memory key-value database, cache, and message broker. Originally developed in 2009 by Salvatore Sanfilippo, Redis was designed to offer extremely low-latency reads and writes by keeping all data in memory. This makes Redis an ideal choice for use cases like caching, session storage, real-time analytics, and leaderboards, where speed is critical.

Unlike traditional databases that rely on disk storage, Redis operates primarily in-memory, allowing it to handle large volumes of requests with minimal latency. While Redis is commonly used as a cache, it can also provide optional durability, meaning it can persist data to disk to recover after a crash or server failure.

Why Use Redis?

  • Low Latency:
    Because Redis stores data in-memory, access times are extremely fast, making it suitable for applications that require real-time data processing or frequent access to the same data.
  • Versatile Data Structures:
    Redis supports various data structures, including strings, lists, sets, sorted sets, maps, bitmaps, and more. This flexibility allows it to be used in a wide range of scenarios, such as caching, messaging, pub/sub, and distributed locking.
  • Popularity and Scalability:
    Redis is one of the most popular NoSQL databases and is widely used by companies like Twitter, Airbnb, Amazon, and OpenAI. Its ability to handle millions of requests per second makes it scalable for high-demand environments.

Redis Origins and Development

Redis started as an open-source project in 2009, developed by Salvatore Sanfilippo. Over the years, it grew into a major player in the NoSQL space. In 2015, a core team sponsored by Redis Labs (now simply “Redis”) began to manage the project. Redis has evolved significantly, offering a wide range of features while remaining true to its goal of delivering fast and reliable data storage.

In 2021, Redis Labs officially dropped the “Labs” from its name to reflect the company’s focus on the core Redis project. Redis remains dual-licensed under the Redis Source Available License v2 and the Server Side Public License v1 to protect its intellectual property.

Redis Alternatives

While Redis is a top choice for in-memory caching, several alternatives offer distinct benefits. Memcached is known for its simplicity and speed, but it lacks Redis’s advanced data structures and persistence. Apache Cassandra is a disk-based NoSQL database built for large-scale, fault-tolerant systems with high availability, though it’s slower and more complex than Redis. Hazelcast supports caching and distributed computing but is heavier and more complex for basic use cases. Finally, Amazon ElastiCache provides fully managed Redis or Memcached instances, making setup easy but tying users to AWS infrastructure, potentially at a higher cost.

Redis: Strengths and Limitations

Redis shines in performance, versatility, and scalability, making it a go-to solution for distributed caching and real-time applications. However, it may not always be the perfect fit for every use case. Here’s a quick summary of Redis’s strengths and limitations:

  • Strengths:
    • Ultra-fast in-memory data storage with low-latency reads and writes.
    • Rich data structure support (strings, lists, sets, sorted sets, hashes, and more).
    • Supports persistence, ensuring data recovery in case of failure.
    • Widely adopted, with strong community support and extensive use in major organizations.
  • Limitations:
    • Memory-bound: Redis stores everything in memory, so it may not be ideal for applications with very large datasets that can’t fit into memory.
    • Single-threaded core: Redis operates on a single-threaded model, which can limit performance on multi-core systems for very high throughput scenarios.

Example: How to Use Redis with Minimal API and .NET

Disclaimer: This example is intended for educational purposes only. While it demonstrates key concepts, there are more efficient ways to write and optimize this code in real-world applications. Consider this a starting point for learning, and always aim to follow best practices and refine your implementation.

GitHub Repository

All the code is available in this GitHub repository and use the redis branch . This repository will help you easily apply the new modifications to the previously created code thanks to the use of tags and branches.

Prerequisites

Before starting, make sure you have the following installed:

C# Extension for VSCode: Install the C# extension for VSCode to enable C# support.

.NET SDK: Download and install the .NET SDK if you haven’t already.

Visual Studio Code (VSCode): Install Visual Studio Code for a lightweight code editor.

Docker Client: Install Docker Desktop.

To modify the existing in-memory caching solution to use Redis in a .NET Web API, we’ll need to replace the IMemoryCache implementation with IDistributedCache for Redis caching. I will guide you through the process of updating your code.

You can run Redis locally using Docker with the following command:

docker run --name my-redis -p 6379:6379 -d redis
Run Redis locally using Docker

Redis comes with a CLI tool (redis-cli) that you can use to interact with it. Let’s test if it’s working fine:

docker exec -it my-redis redis-cli

You can then run commands like:

ping                      # Test if Redis is running
set mykey "Hello World"   # Set a key
get mykey                 # Retrieve the value
Redis CLI

Install the required Redis package:

To use Redis in .NET, you need to install the Redis cache package:

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

Modify UserService to use IDistributedCache

We need to change the caching logic to work with Redis. IDistributedCache uses byte[] for cache storage, so we’ll need to serialize and deserialize the User objects.

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

public interface IUserService
{
    Task<List<User>> GetUsers();
}

public class UserService : IUserService
{
    private readonly IDistributedCache _distributedCache;
    private const string cacheKey = "users";

    public UserService(IDistributedCache distributedCache)
    {
        this._distributedCache = distributedCache;
    }
    
    public async Task<List<User>> GetUsers()
    {
        var cachedUsers = await _distributedCache.GetStringAsync(cacheKey);

        if (cachedUsers != null)
        {
            return JsonSerializer.Deserialize<List<User>>(cachedUsers);
        }

        var users = await GetValuesFromDbAsync();

        var serializedUsers = JsonSerializer.Serialize(users);

        var cacheOptions = new DistributedCacheEntryOptions()
            .SetAbsoluteExpiration(TimeSpan.FromSeconds(60));

        await _distributedCache.SetStringAsync(cacheKey, serializedUsers, cacheOptions);

        return users;
    }

    private async Task<List<User>> GetValuesFromDbAsync()
    {
        List<User> users = new List<User>
        {
            new User { Id = 1, Name = "Alice" },
            new User { Id = 2, Name = "Bob" },
            new User { Id = 3, Name = "Otto" },
        };

        // Simulating a long-running database query.
        await Task.Delay(2000); 
        return users;
    }
}

Register Redis as the Cache Provider in Program.cs

Now, we need to update the DI container to use IDistributedCache with Redis in your Program.cs file.


var builder = WebApplication.CreateBuilder(args);

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";  // Redis connection string (from Docker)
    options.InstanceName = "SampleInstance";   // Optional: name of Redis instance
});
builder.Services.AddScoped<IUserService, UserService>();  // Register UserService

// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

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

app.UseHttpsRedirection();

app.MapGet("/users", async (IUserService userService) =>
{
    var users = await userService.GetUsers();
    return Results.Ok(users);
})
.WithName("GetUsers")
.WithOpenApi();

app.Run();
Example: How to Use Redis with Minimal API and .NET

Once we run the updated code, it will store the result of the GetUsers method in Redis the first time it is called. On subsequent requests (within the cache expiration time of 60 seconds), it will retrieve the data from Redis instead of fetching it again from the “database” (simulated by the GetValuesFromDbAsync method).

Run Example: How to Use Redis with Minimal API and .NET

Conclusion

Implementing a caching system in your applications offers significant advantages, especially when dealing with expensive or frequent data retrieval operations. By reducing the need to repeatedly access a database or perform time-consuming computations, caching can drastically improve application performance and responsiveness. In-memory caching works well for single-server setups, but as applications grow and scale across multiple servers, a more robust solution like Redis becomes essential.

Key Benefits of Caching with Redis:

  • Enhanced Performance and Reduced Latency: Redis’s ability to store data in memory allows it to serve requests much faster than traditional databases, making it ideal for real-time applications, such as session management, gaming leaderboards, or dynamic content delivery.
  • Scalability in Distributed Environments: Redis is perfectly suited for distributed environments where multiple instances of your application are running. Unlike in-memory caches that are bound to a single server, Redis allows multiple application instances to access a centralized cache. This ensures consistency across all instances and reduces the risk of cache inconsistencies or stale data.
  • Versatility with Rich Data Structures: Redis provides more than just basic key-value storage. With support for complex data structures like strings, lists, sets, sorted sets, and hashes, Redis opens up a wide range of use cases, from caching API responses to handling queues or even real-time analytics.
  • Persistence and Durability: While Redis is primarily an in-memory database, it offers optional persistence, allowing you to save data to disk and recover it after a crash or reboot. This adds a layer of reliability, ensuring that important cached data isn’t lost if your server goes down.

Integrating Redis into a .NET application is simple, especially with Docker, which allows you to run Redis locally using a single command. The transition from in-memory caching (IMemoryCache) to distributed caching with Redis (IDistributedCache) requires minimal changes to your code, making it an efficient upgrade for distributed environments.

Redis also scales seamlessly to the cloud. Managed services like Amazon ElastiCache or Azure Cache for Redis handle the infrastructure, allowing you to focus on development. With high throughput and low latency, Redis is ideal for cloud-based, high-demand applications such as e-commerce or real-time services.

Final Thoughts:

As we’ve seen in this article, Redis is not just an in-memory cache, but a versatile tool that can significantly improve the performance and scalability of your .NET applications. Whether you’re working on a small local project or building a globally distributed system in the cloud, Redis can help you handle larger loads efficiently while maintaining low latency and high reliability.

If you think your friends or network would find this article useful, please consider sharing it with them. Your support is greatly appreciated.

Thanks for reading!

Leave A Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.