Redis Setup With .NET Aspire — Connection Strings, Health Checks, and Configuration
How to set up Redis as the L2 cache backing in a Clean Architecture .NET project: StackExchange.Redis configuration, .NET Aspire integration, health checks, connection resilience, and production configuration patterns.
Redis in the Architecture
Redis is an infrastructure concern — it lives in the Infrastructure layer behind the HybridCache abstraction. The Application layer never knows Redis exists.
HybridCache API ← Application layer uses this
│
└── L1: IMemoryCache (in-process)
└── L2: IDistributedCache ← StackExchange.Redis implements this
│
└── Redis server (local Docker or managed cloud)Packages
<!-- Infrastructure.csproj -->
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.*" />
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.*" />Registration in Infrastructure
// Infrastructure/DependencyInjection.cs
public static IServiceCollection AddInfrastructure(
this IServiceCollection services,
IConfiguration configuration)
{
var redisConnection = configuration.GetConnectionString("Redis");
if (!string.IsNullOrWhiteSpace(redisConnection))
{
// L2: Redis distributed cache
services.AddStackExchangeRedisCache(options =>
{
options.Configuration = redisConnection;
options.InstanceName = "SystemForge:"; // key prefix — prevents collisions
options.ConnectRetry = 3;
options.ConnectTimeout = 5000; // ms
options.ReconnectRetryPolicy = new LinearRetry(1000);
});
}
else
{
// Fallback to in-memory only (development without Redis)
services.AddDistributedMemoryCache();
}
// HybridCache uses whatever IDistributedCache is registered
services.AddHybridCache(options =>
{
options.DefaultEntryOptions = new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(5),
LocalCacheExpiration = TimeSpan.FromSeconds(30),
};
});
return services;
}Configuration
// appsettings.json (development)
{
"ConnectionStrings": {
"Database": "Server=localhost;Database=SystemForge;Trusted_Connection=true;",
"Redis": "localhost:6379"
}
}
// appsettings.Production.json (production — values from Key Vault or environment)
{
"ConnectionStrings": {
"Redis": "" // injected from environment variable or Key Vault
}
}# Environment variable override (production)
ConnectionStrings__Redis=systemforge.redis.cache.windows.net:6380,password=...,ssl=True.NET Aspire Integration
With .NET Aspire, Redis is declared as a resource in the AppHost, and the connection string is injected automatically:
// AppHost/Program.cs
var builder = DistributedApplication.CreateBuilder(args);
var redis = builder.AddRedis("redis")
.WithRedisCommander(); // optional: Redis Commander UI on localhost
var database = builder.AddSqlServer("sql")
.AddDatabase("SystemForge");
builder.AddProject<Projects.SystemForge_Api>("api")
.WithReference(database)
.WithReference(redis); // injects ConnectionStrings:redis automatically
builder.Build().Run();// ServiceDefaults/Extensions.cs — part of the Aspire defaults
public static IHostApplicationBuilder AddServiceDefaults(
this IHostApplicationBuilder builder)
{
builder.AddRedisDistributedCache("redis"); // reads from Aspire-injected connection string
// ...
return builder;
}Health Checks
// Infrastructure/DependencyInjection.cs
services.AddHealthChecks()
.AddSqlServer(
connectionString: configuration.GetConnectionString("Database")!,
name: "sql-server",
tags: ["db", "ready"])
.AddRedis(
redisConnectionString: configuration.GetConnectionString("Redis")!,
name: "redis",
tags: ["cache", "ready"]);
// Api/Program.cs
app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = _ => false });
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready"),
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
});Connection Resilience
// Infrastructure/DependencyInjection.cs — more robust Redis setup
services.AddStackExchangeRedisCache(options =>
{
options.ConfigurationOptions = new ConfigurationOptions
{
EndPoints = { configuration.GetConnectionString("Redis")! },
AbortOnConnectFail = false, // do not crash the app if Redis is unavailable
ConnectRetry = 3,
ConnectTimeout = 5000,
SyncTimeout = 2000,
ReconnectRetryPolicy = new ExponentialRetry(
deltaBackOffMilliseconds: 1000,
maxDeltaBackOffMilliseconds: 10_000),
Ssl = !builder.Environment.IsDevelopment(),
};
});Production issue I've seen: An application used
AbortOnConnectFail = true(the default). When the managed Redis instance had a 30-second restart during a planned maintenance, the application also crashed because it could not establish the initial connection. SettingAbortOnConnectFail = falseallows the application to start without Redis and reconnect when it becomes available — degraded cache performance, but not a full outage.
Graceful Degradation When Redis Is Unavailable
// HybridCache falls back to L1 (in-memory) when L2 (Redis) is unavailable
// No code change required — HybridCache handles this automatically
// However: when Redis is down, L1 has no cross-instance invalidation
// This means different instances may serve different versions of cached data
// Add a custom health check to expose this degraded state:
public sealed class RedisCacheHealthCheck : IHealthCheck
{
private readonly HybridCache _cache;
public RedisCacheHealthCheck(HybridCache cache) => _cache = cache;
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken ct)
{
try
{
// Simple set/get to verify Redis is writable
await _cache.SetAsync("health-check", "ok", cancellationToken: ct);
return HealthCheckResult.Healthy("Cache is operational.");
}
catch (Exception ex)
{
return HealthCheckResult.Degraded(
"Cache L2 (Redis) is unavailable. Operating on L1 only.",
ex);
}
}
}PRO TIP — Key Namespacing
When multiple applications share a Redis instance (common in cost-optimized environments), use an
InstanceNameprefix to prevent key collisions."SystemForge:"as the instance name means every key becomesSystemForge:patient:abc123. Without it, two apps both caching a key called"patient:abc123"will overwrite each other's data silently.
Key Takeaway
Redis is a detail. The Application layer uses
HybridCache. Infrastructure wiresStackExchange.RedisbehindIDistributedCache. .NET Aspire injects the connection string automatically in development. In production, the connection string comes from Key Vault or environment variables. Health checks expose Redis availability. Graceful degradation viaAbortOnConnectFail = falseprevents a Redis restart from crashing the entire application.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.