Learnixo
Back to blog
AI Systemsintermediate

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.

Asma Hafeez KhanMay 16, 20264 min read
Clean Architecture.NETRedisAspireCachingConfiguration
Share:𝕏

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

XML
<!-- Infrastructure.csproj -->
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.*" />
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.*" />

Registration in Infrastructure

C#
// 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

JSON
// 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
  }
}
Bash
# 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:

C#
// 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();
C#
// 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

C#
// 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

C#
// 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. Setting AbortOnConnectFail = false allows 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

C#
// 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 InstanceName prefix to prevent key collisions. "SystemForge:" as the instance name means every key becomes SystemForge: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 wires StackExchange.Redis behind IDistributedCache. .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 via AbortOnConnectFail = false prevents a Redis restart from crashing the entire application.

Enjoyed this article?

Explore the AI Systems learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.