StackExchange.Redis.Extensions.System.Text.Json 12.5.0

StackExchange.Redis.Extensions

StackExchange.Redis.Extensions is a library that extends StackExchange.Redis, making it easier to work with Redis in .NET applications. It wraps the base library with serialization, connection pooling, and higher-level APIs so you can store and retrieve complex objects without writing boilerplate code.

CI CodeQL NuGet Tests .NET

AI-Ready: This library provides an llms.txt file for AI coding assistants and a Claude Code plugin for configuration, scaffolding, and troubleshooting.

claude plugin add imperugo/StackExchange.Redis.Extensions

Then use /redis-configure, /redis-scaffold, or /redis-diagnose in Claude Code.

Features

  • Store and retrieve complex .NET objects with automatic serialization
  • Multiple serialization providers (System.Text.Json, Newtonsoft, Protobuf, MsgPack, MemoryPack, and more)
  • Connection pooling with LeastLoaded and RoundRobin strategies
  • Pub/Sub messaging with typed handlers
  • Hash operations with per-field expiry (Redis 7.4+)
  • GeoSpatial indexes (GEOADD, GEOSEARCH, GEODIST, etc.)
  • Redis Streams with consumer group support
  • Set, List, and Sorted Set operations with Set Combine (union, intersect, diff)
  • Key tagging and search
  • Key management (rename, type, dump/restore)
  • Atomic counters (StringIncrement/StringDecrement)
  • Transparent compression (GZip, Brotli, LZ4, Snappy, Zstandard)
  • IDistributedCache adapter (Microsoft-compatible Hash schema)
  • ASP.NET Core Health Check for connection pool monitoring
  • Azure Managed Identity support
  • ASP.NET Core integration with DI and .NET 8+ Keyed Services
  • Multiple named Redis instances with [FromKeyedServices] support
  • OpenTelemetry integration
  • .NET Standard 2.1, .NET 8, .NET 9, .NET 10

Architecture

graph TB
    App[Your Application] --> DI[ASP.NET Core DI]
    DI --> Factory[IRedisClientFactory]
    Factory --> Client1[IRedisClient - Default]
    Factory --> Client2[IRedisClient - Named]
    Client1 --> DB[IRedisDatabase]
    DB --> Pool[Connection Pool Manager]
    Pool --> C1[Connection 1]
    Pool --> C2[Connection 2]
    Pool --> C3[Connection N...]
    DB --> Ser[ISerializer]
    Ser --> Comp[CompressedSerializer?]
    Comp --> Inner[System.Text.Json / Newtonsoft / ...]
    C1 --> Redis[(Redis Server)]
    C2 --> Redis
    C3 --> Redis

Quick Start

1. Install packages

dotnet add package StackExchange.Redis.Extensions.Core
dotnet add package StackExchange.Redis.Extensions.System.Text.Json
dotnet add package StackExchange.Redis.Extensions.AspNetCore

2. Configure in appsettings.json

{
  "Redis": {
    "Password": "",
    "AllowAdmin": true,
    "Ssl": false,
    "ConnectTimeout": 5000,
    "SyncTimeout": 5000,
    "Database": 0,
    "Hosts": [
      { "Host": "localhost", "Port": 6379 }
    ],
    "PoolSize": 5,
    "IsDefault": true
  }
}

3. Register in DI

var redisConfig = builder.Configuration.GetSection("Redis").Get<RedisConfiguration>();
builder.Services.AddStackExchangeRedisExtensions<SystemTextJsonSerializer>(redisConfig);

4. Use it

public class MyService(IRedisDatabase redis)
{
    public async Task Example()
    {
        // Store an object
        await redis.AddAsync("user:1", new User { Name = "Ugo", Age = 38 });

        // Retrieve it
        var user = await redis.GetAsync<User>("user:1");

        // Store with expiry
        await redis.AddAsync("session:abc", sessionData, TimeSpan.FromMinutes(30));

        // Bulk operations
        var items = new[]
        {
            Tuple.Create("key1", "value1"),
            Tuple.Create("key2", "value2"),
        };
        await redis.AddAllAsync(items, TimeSpan.FromHours(1));

        // Search keys
        var keys = await redis.SearchKeysAsync("user:*");
    }
}

NuGet Packages

Core

Package Description NuGet
Core Core library with abstractions and implementations NuGet
AspNetCore ASP.NET Core DI integration and middleware NuGet

Serializers (pick one)

Package Description NuGet
System.Text.Json Recommended for most scenarios NuGet
Newtonsoft JSON.NET for legacy compatibility NuGet
MemoryPack High-performance binary (net7.0+) NuGet
MsgPack MessagePack binary format NuGet
Protobuf Protocol Buffers NuGet
Utf8Json UTF-8 native JSON NuGet

Compressors (optional)

Package Algorithm Best for NuGet
Compression.LZ4 LZ4 Lowest latency NuGet
Compression.Snappier Snappy Low latency NuGet
Compression.ZstdSharp Zstandard Best ratio/speed NuGet
Compression.GZip GZip Wide compatibility NuGet
Compression.Brotli Brotli Best ratio for text NuGet

Usage Examples

Hash Operations

// Set a hash field
await redis.HashSetAsync("user:1", "name", "Ugo");
await redis.HashSetAsync("user:1", "email", "ugo@example.com");

// Get a hash field
var name = await redis.HashGetAsync<string>("user:1", "name");

// Set with per-field expiry (Redis 7.4+)
await redis.HashSetWithExpiryAsync("user:1", "session", sessionData, TimeSpan.FromMinutes(30));

// Query field TTL
var ttl = await redis.HashFieldGetTimeToLiveAsync("user:1", new[] { "session" });

GeoSpatial

// Add locations
await redis.GeoAddAsync("restaurants", new[]
{
    new GeoEntry(13.361389, 38.115556, "Pizzeria Da Michele"),
    new GeoEntry(15.087269, 37.502669, "Trattoria del Corso"),
    new GeoEntry(12.496366, 41.902782, "Da Enzo al 29"),
});

// Distance between two places
var km = await redis.GeoDistanceAsync("restaurants",
    "Pizzeria Da Michele", "Trattoria del Corso", GeoUnit.Kilometers);

// Search within 200km of a point
var nearby = await redis.GeoSearchAsync("restaurants", 13.361389, 38.115556,
    new GeoSearchCircle(200, GeoUnit.Kilometers),
    count: 10, order: Order.Ascending);

Redis Streams

// Publish typed events
await redis.StreamAddAsync("orders", "payload", new Order { Id = 1, Total = 99.99m });

// Consumer group workflow
await redis.StreamCreateConsumerGroupAsync("orders", "processors");

var entries = await redis.StreamReadGroupAsync("orders", "processors", "worker-1");
foreach (var entry in entries)
{
    // Process the message
    await redis.StreamAcknowledgeAsync("orders", "processors", entry.Id!);
}

Pub/Sub

// Subscribe to typed messages
await redis.SubscribeAsync<OrderEvent>("orders:new", async order =>
{
    Console.WriteLine($"New order: {order.Id}");
});

// Publish
await redis.PublishAsync("orders:new", new OrderEvent { Id = 42 });

Atomic Counters

// Increment/decrement with long (default step = 1)
var views = await redis.StringIncrementAsync("page:views");
var stock = await redis.StringDecrementAsync("product:123:stock");

// Custom step
await redis.StringIncrementAsync("stats:bytes", 1024);

// Double precision
await redis.StringIncrementAsync("account:balance", 49.99);

Set Operations

// Combine sets: union, intersect, difference
var allTags = await redis.SetCombineAsync<string>(SetOperation.Union, "user:1:tags", "user:2:tags");
var commonTags = await redis.SetCombineAsync<string>(SetOperation.Intersect, "user:1:tags", "user:2:tags");

// Store result in a new key
await redis.SetCombineAndStoreAsync(SetOperation.Union, "all:tags", new[] { "set:a", "set:b", "set:c" });

Key Management

// Rename a key
await redis.KeyRenameAsync("old-key", "new-key");

// Check key type
var type = await redis.KeyTypeAsync("my-key"); // RedisType.String, Set, Hash, ...

// Dump and restore (migrate between databases)
var dump = await redis.KeyDumpAsync("my-key");
await redis.KeyRestoreAsync("my-key-copy", dump, TimeSpan.FromHours(1));

IDistributedCache

// Register the IDistributedCache adapter (call after AddStackExchangeRedisExtensions)
builder.Services.AddStackExchangeRedisExtensions<SystemTextJsonSerializer>(redisConfig);
builder.Services.AddRedisDistributedCache();

// Use standard IDistributedCache anywhere
public class MyService(IDistributedCache cache)
{
    public async Task CacheData()
    {
        await cache.SetAsync("session:abc", data, new DistributedCacheEntryOptions
        {
            SlidingExpiration = TimeSpan.FromMinutes(20),
            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(4),
        });

        var cached = await cache.GetAsync("session:abc");
    }
}

Note: Uses a Hash-based schema (data/absexp/sldexp) compatible with Microsoft.Extensions.Caching.StackExchangeRedis, enabling zero-downtime migration between providers.

Health Check

// Register the health check
builder.Services.AddHealthChecks()
    .AddRedisExtensionsHealthCheck();

// Returns:
// - Healthy: all pool connections active + PING OK
// - Degraded: some connections invalid but Redis responding
// - Unhealthy: all connections invalid or PING fails

Keyed DI Services (.NET 8+)

// Register with named configurations
builder.Services.AddStackExchangeRedisExtensions<SystemTextJsonSerializer>(new[]
{
    new RedisConfiguration { Name = "cache", IsDefault = true, /* ... */ },
    new RedisConfiguration { Name = "session", /* ... */ },
});

// Inject by name using [FromKeyedServices]
public class MyService(
    [FromKeyedServices("cache")] IRedisDatabase cacheDb,
    [FromKeyedServices("session")] IRedisDatabase sessionDb)
{
    // Each resolves to its own Redis instance with isolated connection pool
}

Compression

// Enable transparent compression with any serializer
services.AddStackExchangeRedisExtensions<SystemTextJsonSerializer>(config);
services.AddRedisCompression<LZ4Compressor>();  // That's it!

// All operations automatically compress/decompress
await redis.AddAsync("large-data", myLargeObject);  // stored compressed
var obj = await redis.GetAsync<MyObject>("large-data");  // decompressed automatically

Azure Managed Identity

var config = new RedisConfiguration { /* ... */ };
config.ConfigurationOptionsAsyncHandler = async opts =>
{
    await opts.ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential());
    return opts;
};

Connection Pooling

graph LR
    subgraph Pool["Connection Pool (PoolSize=5)"]
        C1["Connection 1<br/>Outstanding: 3"]
        C2["Connection 2<br/>Outstanding: 0"]
        C3["Connection 3<br/>Outstanding: 7"]
        C4["Connection 4<br/>Outstanding: 1"]
        C5["Connection 5<br/>Outstanding: 2"]
    end

    Op["GetConnection()"] -->|LeastLoaded| C2
    Op -->|RoundRobin| C4

    style C2 fill:#90EE90
    style C4 fill:#87CEEB

The pool automatically skips disconnected connections and falls back gracefully when all connections are down, letting StackExchange.Redis's internal reconnection logic recover.

Strategy Behavior
LeastLoaded (default) Picks the connected connection with fewest outstanding commands
RoundRobin Random selection among connected connections

Serialization Behavior

All values stored in Redis go through the configured ISerializer. This means:

  • A string value "hello" is stored as "\"hello\"" (JSON-encoded)
  • Use IRedisDatabase.Database for raw Redis operations without serialization
  • All serializers follow the same convention: null input produces an empty byte array

Configuration Reference

Property Default Description
Hosts Required Redis server endpoints
Password null Redis password
Database 0 Database index
Ssl false Enable TLS
PoolSize 5 Number of connections in the pool
ConnectionSelectionStrategy LeastLoaded Pool selection strategy
SyncTimeout 5000 Sync operation timeout (ms)
ConnectTimeout 5000 Connection timeout (ms)
KeyPrefix "" Prefix for all keys and channels
AllowAdmin false Enable admin commands
ClientName null Connection client name
KeepAlive -1 Heartbeat interval (seconds). -1 = SE.Redis default, 0 = disabled
ServiceName null Sentinel service name
MaxValueLength 0 Max serialized value size (0 = unlimited)
WorkCount CPU*2 I/O threads per SocketManager
ConnectRetry null Connection retry count
CertificateValidation null TLS certificate validation callback
CertificateSelection null TLS client certificate selection callback
ConfigurationOptionsAsyncHandler null Async callback for custom ConfigurationOptions setup (e.g. Azure)

Documentation

Full documentation is available in the doc/ folder:

Getting Started

Configuration

Serializers

Features

Advanced

Contributing

Thanks to all the people who already contributed!

Please read CONTRIBUTING.md before submitting a pull request. PRs target the master branch only.

License

StackExchange.Redis.Extensions is Copyright © Ugo Lattanzi and other contributors under the MIT license.

No packages depend on StackExchange.Redis.Extensions.System.Text.Json.

v12.5.0:

  • Added IDistributedCache adapter with Hash-based storage compatible with Microsoft.Extensions.Caching.StackExchangeRedis (#632)
  • Added ASP.NET Core Health Check for Redis connection pool monitoring (#633)
  • Added StringIncrementAsync / StringDecrementAsync with long and double overloads (#634)
  • Added SetCombineAsync / SetCombineAndStoreAsync for union, intersect, and difference operations (#635)
  • Added Key Management: KeyRenameAsync, KeyTypeAsync, KeyDumpAsync, KeyRestoreAsync (#636)
  • Added .NET 8+ Keyed DI Services for named Redis clients via [FromKeyedServices] (#654)

v12.2.0:

  • Fixed SearchKeysAsync ignoring database number — SCAN now correctly targets the specified database instead of always scanning DB0 (#651)
  • Fixed RemoveByTagAsync not deleting the tag Set key itself, causing a slow memory leak of empty tag Sets in Redis (#650)
  • Upgraded Snappier to 1.3.x to fix high severity vulnerability (GHSA-pggp-6c3x-2xmx)

v12.1.0:

  • Added VectorSet API for AI/ML similarity search (Redis 8.0+): VADD, VSIM, VREM, VCONTAINS, VCARD, VDIM, VGETATTR, VSETATTR, VINFO, VRANDMEMBER, VLINKS
  • Added llms.txt for AI coding assistant documentation indexing
  • Added Claude Code plugin with configure, scaffold, and diagnose skills
  • Added complete API reference tables to all feature documentation
  • Added SECURITY.md with GitHub Private Vulnerability Reporting
  • Added CodeQL Advanced security analysis workflow
  • Added CI workflow for automated testing on push/PR

v12.0.0:

  • Added .NET 10 target framework
  • Added GeoSpatial API (GEOADD, GEOSEARCH, GEODIST, GEOPOS, GEOHASH)
  • Added Redis Streams API (XADD, XREAD, XREADGROUP, XACK, consumer groups)
  • Added Hash field expiry (HEXPIRE, HSETEX, HPTTL, HPERSIST) for Redis 7.4+
  • Added transparent compression support with pluggable ICompressor
  • Added compression packages: LZ4, Snappy, Zstandard, GZip, Brotli
  • Added Azure Managed Identity support via ConfigurationOptionsAsyncHandler
  • Added CertificateSelection, ClientName, KeepAlive configuration properties
  • Fixed SyncTimeout default from 1000ms to 5000ms
  • Fixed Sentinel CommandMap blocking data commands (EVAL, GET, SET)
  • Fixed AddAllAsync TTL race condition (now atomic per key)
  • Fixed pool resilience: GetConnection skips disconnected connections
  • Fixed PubSub handler silently swallowing exceptions
  • Upgraded to StackExchange.Redis 2.12.14
  • Upgraded to ConnectAsync (SE.Redis best practice)
  • Replaced Moq with NSubstitute

Version Downloads Last updated
12.5.0 1 07/01/2026
12.2.0 19 05/23/2026
12.1.0 19 04/13/2026
12.0.0 20 04/13/2026
11.0.0 24 12/16/2025
10.2.0 22 12/20/2025
10.1.0 23 12/16/2025
10.0.2 26 12/16/2025
10.0.1 22 12/20/2025
10.0.0 24 12/16/2025
9.1.0 20 12/30/2025
9.0.0 23 12/20/2025
8.0.5 23 12/20/2025
8.0.4 21 12/16/2025
8.0.3 25 12/16/2025
8.0.2 22 12/16/2025
8.0.1 22 12/16/2025
8.0.0 24 12/16/2025
7.2.1 20 12/30/2025
7.1.1 18 12/30/2025
7.0.1 23 12/16/2025
7.0.0 23 12/20/2025
7.0.0-pre 29 12/30/2025
6.4.5 24 12/20/2025
6.4.3 25 12/16/2025
6.4.2 26 12/16/2025
6.4.1 28 12/16/2025
6.4.0 22 12/30/2025
6.3.6 25 12/16/2025
6.3.5 24 12/20/2025
6.3.4 24 12/13/2025
6.3.3 22 12/20/2025
6.3.2 23 12/16/2025
6.3.1 23 12/30/2025
6.3.0 20 12/20/2025
6.2.2 23 12/16/2025
6.2.1 26 12/22/2025
6.2.0 23 12/16/2025
6.1.7 23 12/16/2025
6.1.6 26 12/16/2025
6.1.5 31 12/16/2025
6.1.1 24 12/20/2025
6.1.0 21 12/13/2025
6.0.11 22 12/16/2025
6.0.10-pre 25 12/16/2025
6.0.9-pre 22 12/21/2025
6.0.8-pre 28 12/21/2025
6.0.7-pre 18 12/21/2025
6.0.6-pre 20 12/16/2025
6.0.5-pre 21 12/15/2025
6.0.4-pre 23 12/21/2025
6.0.3-pre 16 12/21/2025
6.0.2-pre 21 12/13/2025
5.5.0 24 12/16/2025