How to Add Distributed Caching to ASP.NET Core with Azure Managed Redis


If an ASP.NET Core app repeatedly reads the same data from a database, a distributed cache can take some of that work off the database and reduce response times. Azure Managed Redis provides a shared cache that multiple app instances can use, unlike an in-memory cache that belongs to just one running process.

Here’s how to add it with .NET’s IDistributedCache abstraction, and what to consider so the cache remains a performance aid rather than a second source of truth.

Why use a distributed cache?

An in-memory cache is quick and useful, but each app instance has its own copy. If a request lands on another instance, that instance may not have the same cached value. Entries also disappear when an instance restarts. A shared Redis cache lets instances read and write to the same cache.

Caching is most helpful for data that is read often and changes less frequently than it is requested: product details, public settings, or reference data are common examples. It is usually a poor fit for data that must always be fresh, such as a current account balance or a one-time authorization decision.

Connect the app to Azure Managed Redis

Create an Azure Managed Redis resource and configure network access so the application can reach it. For production, consider a private endpoint and appropriate virtual network access rather than exposing the cache broadly. The exact connection details depend on your resource and authentication setup.

Add the Redis cache provider package, Microsoft.Extensions.Caching.StackExchangeRedis, to the .NET project. Keep the endpoint and credentials outside source control. For example, the app can read a connection string from configuration, with the production value supplied through an environment-specific setting or a secrets service.

var redisConnection = builder.Configuration.GetConnectionString("Redis");

if (string.IsNullOrWhiteSpace(redisConnection))
{
    throw new InvalidOperationException("The Redis connection string is missing.");
}

// Register the Redis-backed implementation of IDistributedCache.
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = redisConnection;
    options.InstanceName = "catalog:";
});

The instance name prefixes keys created through this cache registration. It can help keep entries separate when applications or environments share a Redis instance, but it is not a replacement for designing clear cache keys. In Azure, use TLS and protect credentials with a suitable secret-management approach. If you choose Microsoft Entra authentication, make sure your Redis client and token-refresh setup support that configuration.

Use cache-aside for frequently read data

A common pattern is cache-aside: the app checks Redis first, reads from the database on a miss, and then stores the result for a limited time. Here is a simplified example using EF Core and IDistributedCache:

public async Task<Product?> GetProductAsync(int id, CancellationToken cancellationToken)
{
    var cacheKey = $"product:{id}";

    // Return the cached value when it exists.
    var cachedJson = await _cache.GetStringAsync(cacheKey, cancellationToken);
    if (cachedJson is not null)
    {
        return JsonSerializer.Deserialize<Product>(cachedJson);
    }

    // Load a cache miss from the authoritative data store.
    var product = await _db.Products
        .AsNoTracking()
        .FirstOrDefaultAsync(product => product.Id == id, cancellationToken);

    if (product is null)
    {
        return null;
    }

    // Give the entry a finite lifetime so old data eventually expires.
    var cacheOptions = new DistributedCacheEntryOptions
    {
        AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
    };

    var json = JsonSerializer.Serialize(product);
    await _cache.SetStringAsync(cacheKey, json, cacheOptions, cancellationToken);

    return product;
}

This example assumes the service has IDistributedCache and the EF Core context injected, and that the required namespaces are included. In a real application, choose a cache lifetime based on how quickly the underlying data can change. Five minutes is only an example, not a universal setting.

Plan for updates and cache misses

A cached value can become stale after a database update. When the application changes a product, it can remove the corresponding key with RemoveAsync, or refresh the cache with the new value. If several code paths update the same data, make sure each one follows the same invalidation rule.

Cache keys should be predictable and include the values that affect the result. For example, a listing may need a key that includes the page number, sort order, and relevant filter. Keep key naming consistent; otherwise, it becomes difficult to identify which entries an update should invalidate.

  • Set expiration: Use a finite lifetime so abandoned entries do not live indefinitely.
  • Cache selectively: Avoid storing large results or data that is rarely requested without measuring the benefit.
  • Consider concurrent misses: A popular key expiring can cause many requests to query the database at once. For high-traffic data, consider request coalescing or a short refresh strategy.
  • Keep the database authoritative: Redis can be unavailable or lose entries. The application should be able to rebuild cached data from its primary store.

Test the behavior, not just the connection

Check what happens on both a cache hit and a cache miss, and verify that an update makes the old value unavailable. Also test the app when Redis cannot be reached. Whether requests should fail or fall back to the database depends on the application’s reliability and load requirements; unrestricted fallback can overwhelm the database during a cache outage.

Finally, measure before and after adding the cache. A shared Redis layer adds network calls and operational work, so it is most valuable when it reduces a demonstrated bottleneck. Used for carefully chosen, repeatable reads, it gives ASP.NET Core instances a common place to reuse data without tying cached entries to any one server.

0 Comments Report