RedisFlexCache 1.0.6
dotnet add package RedisFlexCache --version 1.0.6
NuGet\Install-Package RedisFlexCache -Version 1.0.6
<PackageReference Include="RedisFlexCache" Version="1.0.6" />
<PackageVersion Include="RedisFlexCache" Version="1.0.6" />
<PackageReference Include="RedisFlexCache" />
paket add RedisFlexCache --version 1.0.6
#r "nuget: RedisFlexCache, 1.0.6"
#:package RedisFlexCache@1.0.6
#addin nuget:?package=RedisFlexCache&version=1.0.6
#tool nuget:?package=RedisFlexCache&version=1.0.6
RedisFlexCache
A flexible and easy-to-use Redis caching library for .NET applications with support for dependency injection, configuration patterns, and advanced caching strategies.
Features
- 🚀 High Performance: Built on top of StackExchange.Redis for optimal performance
- 🔧 Easy Configuration: Simple setup with .NET configuration patterns
- 💉 Dependency Injection: Full support for .NET DI container
- 🔄 Async/Sync Support: Both asynchronous and synchronous operations
- 🗜️ Compression: Optional GZip compression for large cached objects
- ⚙️ High-Performance Serialization: MessagePack serialization with compression support
- 🔑 Key Management: Pattern-based key operations and prefix support
- 📊 Connection Management: Robust Redis connection handling with retry logic
- 🛡️ Error Handling: Comprehensive error handling and logging
- ⏱️ TTL Support: Time-to-live management and expiration control
Installation
Install-Package RedisFlexCache
Quick Start
1. Basic Setup
using RedisFlexCache.Extensions;
// In Program.cs or Startup.cs
builder.Services.AddRedisFlexCache("localhost:6379");
2. Using the Cache Service
public class ProductService
{
private readonly ICacheService _cache;
public ProductService(ICacheService cache)
{
_cache = cache;
}
public async Task<Product> GetProductAsync(int id)
{
var cacheKey = $"product:{id}";
// Try to get from cache first
var cachedProduct = await _cache.GetAsync<Product>(cacheKey);
if (cachedProduct != null)
{
return cachedProduct;
}
// If not in cache, get from database
var product = await GetProductFromDatabaseAsync(id);
// Cache for 30 minutes
await _cache.SetAsync(cacheKey, product, TimeSpan.FromMinutes(30));
return product;
}
public async Task<Product> GetProductWithFactoryAsync(int id)
{
var cacheKey = $"product:{id}";
// Get or set pattern - automatically handles cache miss
return await _cache.GetAsync(cacheKey,
async () => await GetProductFromDatabaseAsync(id),
TimeSpan.FromMinutes(30));
}
}
Configuration
Using appsettings.json
{
"RedisCache": {
"Connection": "localhost:6379",
"KeyPrefix": "myapp",
"DefaultExpiration": "00:30:00",
"EnableCompression": true,
"EnableHashKey": false,
"ConnectionTimeout": 5000,
"CommandTimeout": 5000,
"ConnectionCount": 1,
"MaxKeyLength": 100,
"IsDbCachedActive": true,
"Username": "",
"Password": "",
"ResolverStrategy": "Conservative"
}
}
// Register with configuration
builder.Services.AddRedisFlexCache(builder.Configuration);
Programmatic Configuration
builder.Services.AddRedisFlexCache(options =>
{
options.Connection = "localhost:6379";
options.KeyPrefix = "myapp";
options.DefaultExpiration = TimeSpan.FromMinutes(30);
options.EnableCompression = true;
options.EnableHashKey = false;
options.ConnectionTimeout = 5000;
options.CommandTimeout = 5000;
options.ConnectionCount = 1;
options.MaxKeyLength = 100;
options.IsDbCachedActive = true;
options.Username = "";
options.Password = "";
options.ResolverStrategy = MessagePackResolverStrategy.Conservative;
});
Redis Cluster Support
RedisFlexCache provides built-in support for Redis clusters with automatic failover and load balancing. You can configure cluster connections using semicolon-separated connection strings.
Cluster Configuration
Using Connection String Format
// Multiple Redis nodes with semicolon separation
builder.Services.AddRedisFlexCache("127.0.0.1:6379;192.168.1.100:6379;192.168.1.101:6379");
Using appsettings.json
{
"RedisCache": {
"Connection": "127.0.0.1:6379;192.168.1.100:6379;192.168.1.101:6379",
"KeyPrefix": "myapp",
"DefaultExpiration": "00:30:00",
"EnableCompression": true,
"ConnectionTimeout": 5000,
"CommandTimeout": 5000,
"ResolverStrategy": "Conservative"
}
}
Programmatic Configuration
builder.Services.AddRedisFlexCache(options =>
{
options.Connection = "127.0.0.1:6379;192.168.1.100:6379;192.168.1.101:6379";
options.KeyPrefix = "myapp";
options.DefaultExpiration = TimeSpan.FromMinutes(30);
options.EnableCompression = true;
options.ConnectionTimeout = 10000; // Increase timeout for cluster
options.CommandTimeout = 10000;
options.ResolverStrategy = MessagePackResolverStrategy.Conservative;
});
Cluster Connection String Format
The cluster connection string uses semicolon (;) as the delimiter between Redis nodes:
host1:port1;host2:port2;host3:port3;...
Examples:
// Local cluster setup
"127.0.0.1:6379;127.0.0.1:6380;127.0.0.1:6381"
// Production cluster with different servers
"redis-node1.company.com:6379;redis-node2.company.com:6379;redis-node3.company.com:6379"
// Mixed IP and hostname configuration
"192.168.1.10:6379;redis-backup.local:6379;10.0.0.50:6379"
// Cluster with authentication
"user:password@redis1.example.com:6379;user:password@redis2.example.com:6379"
Cluster Features
Automatic Failover
- Node Detection: Automatically detects and connects to available cluster nodes
- Health Monitoring: Continuously monitors node health and availability
- Seamless Failover: Automatically switches to healthy nodes when failures occur
- Recovery: Automatically reconnects to recovered nodes
Load Balancing
- Distributed Operations: Distributes cache operations across cluster nodes
- Optimal Routing: Routes commands to the appropriate node based on key hashing
- Connection Pooling: Maintains efficient connection pools to all cluster nodes
Cluster Topology
- Dynamic Discovery: Automatically discovers cluster topology and slot assignments
- Slot Mapping: Handles Redis cluster slot mapping transparently
- Resharding Support: Adapts to cluster resharding operations
Best Practices for Cluster Configuration
1. Connection Timeouts
builder.Services.AddRedisFlexCache(options =>
{
options.Connection = "node1:6379;node2:6379;node3:6379";
options.ConnectionTimeout = 10000; // Increase for cluster latency
options.CommandTimeout = 10000; // Increase for cluster operations
});
2. Minimum Node Configuration
// Recommended: At least 3 nodes for proper cluster operation
var clusterNodes = "redis-master1:6379;redis-master2:6379;redis-master3:6379";
builder.Services.AddRedisFlexCache(clusterNodes);
3. High Availability Setup
// Include both master and replica nodes for maximum availability
var haCluster = "master1:6379;master2:6379;master3:6379;replica1:6379;replica2:6379;replica3:6379";
builder.Services.AddRedisFlexCache(haCluster);
4. Network Considerations
builder.Services.AddRedisFlexCache(options =>
{
options.Connection = "10.0.1.10:6379;10.0.1.11:6379;10.0.1.12:6379";
options.ConnectionTimeout = 15000; // Account for network latency
options.CommandTimeout = 15000;
options.ConnectionCount = 2; // Multiple connections per node
});
Cluster Monitoring and Diagnostics
Connection Status
public class CacheHealthService
{
private readonly ICacheProvider _cacheProvider;
private readonly ILogger<CacheHealthService> _logger;
public async Task<bool> CheckClusterHealthAsync()
{
try
{
// Test cluster connectivity
var testKey = "health-check-" + Guid.NewGuid();
await _cacheProvider.SetAsync(testKey, "test", TimeSpan.FromSeconds(10));
var result = await _cacheProvider.GetAsync<string>(testKey);
await _cacheProvider.RemoveAsync(testKey);
return result == "test";
}
catch (Exception ex)
{
_logger.LogError(ex, "Cluster health check failed");
return false;
}
}
}
Logging Cluster Events
// Enable detailed logging for cluster operations
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Information);
// RedisFlexCache will log:
// - Cluster node connections/disconnections
// - Failover events
// - Slot mapping changes
// - Performance metrics
Cluster vs Single Node Comparison
| Feature | Single Node | Redis Cluster |
|---|---|---|
| Setup Complexity | Simple | Moderate |
| High Availability | ❌ Single point of failure | ✅ Automatic failover |
| Scalability | ❌ Limited by single server | ✅ Horizontal scaling |
| Performance | ✅ Lower latency | ⚠️ Network overhead |
| Data Distribution | ❌ All data on one node | ✅ Distributed across nodes |
| Maintenance | ⚠️ Downtime required | ✅ Rolling updates possible |
| Cost | ✅ Lower infrastructure cost | ⚠️ Higher infrastructure cost |
Migration from Single Node to Cluster
// Before: Single node
builder.Services.AddRedisFlexCache("localhost:6379");
// After: Cluster configuration
builder.Services.AddRedisFlexCache("redis-node1:6379;redis-node2:6379;redis-node3:6379");
// No code changes required in your application!
// RedisFlexCache handles cluster operations transparently
Migration Steps:
- Set up Redis cluster infrastructure
- Update connection string to include all cluster nodes
- Test cluster connectivity
- Deploy application with new configuration
- Monitor cluster performance and health
Troubleshooting Cluster Issues
Common Issues and Solutions
Connection Timeouts
// Increase timeouts for cluster latency
options.ConnectionTimeout = 15000;
options.CommandTimeout = 15000;
Node Discovery Problems
// Ensure all cluster nodes are listed
options.Connection = "node1:6379;node2:6379;node3:6379;node4:6379;node5:6379;node6:6379";
Network Connectivity
// Use IP addresses if DNS resolution is problematic
options.Connection = "192.168.1.10:6379;192.168.1.11:6379;192.168.1.12:6379";
Slot Migration Errors
// Enable retry logic for slot migration scenarios
options.CommandTimeout = 20000; // Allow time for slot migrations
Advanced Usage
Key Operations
// Remove cache entry
await _cache.RemoveAsync("product:123");
// Check if key exists
var exists = await _cache.ExistsAsync("product:123");
// Get remaining time to live
var ttl = await _cache.GetTimeToLiveAsync("product:123");
// Refresh expiration
var refreshed = await _cache.RefreshAsync("product:123", TimeSpan.FromHours(1));
Batch Operations
// Cache multiple products
var products = await GetProductsFromDatabaseAsync();
var tasks = products.Select(p =>
_cache.SetAsync($"product:{p.Id}", p, TimeSpan.FromMinutes(30))
);
await Task.WhenAll(tasks);
// Get multiple products
var productIds = new[] { 1, 2, 3, 4, 5 };
var cachedProducts = new List<Product>();
foreach (var id in productIds)
{
var product = await _cache.GetAsync<Product>($"product:{id}");
if (product != null)
{
cachedProducts.Add(product);
}
}
Custom Cache Implementations
public class CustomCacheService : RedisCacheService
{
public CustomCacheService(IOptions<RedisCacheOptions> options, ILogger<CustomCacheService> logger)
: base(options, logger)
{
}
// Add custom methods or override existing ones
public async Task<T> GetWithFallbackAsync<T>(string primaryKey, string fallbackKey)
{
var primary = await GetAsync<T>(primaryKey);
if (primary != null) return primary;
return await GetAsync<T>(fallbackKey);
}
}
// Register custom implementation
builder.Services.AddRedisFlexCache<CustomCacheService>(options =>
{
options.Connection = "localhost:6379";
});
Service Lifetime Options
// Singleton (default - recommended for most scenarios)
builder.Services.AddRedisFlexCache(options => { /* config */ });
builder.Services.AddRedisFlexCache(options => { /* config */ }, ServiceLifetime.Singleton);
// Scoped (per request in web applications)
builder.Services.AddRedisFlexCache(options => { /* config */ }, ServiceLifetime.Scoped);
// Transient (new instance every time)
builder.Services.AddRedisFlexCache(options => { /* config */ }, ServiceLifetime.Transient);
// With validation and custom lifetime
builder.Services.AddRedisFlexCacheWithValidation(options => { /* config */ }, validateOnStart: true, ServiceLifetime.Singleton);
// Legacy methods (still supported for backward compatibility)
builder.Services.AddRedisFlexCacheScoped(options => { /* config */ });
builder.Services.AddRedisFlexCacheTransient(options => { /* config */ });
Choosing the Right Service Lifetime
Singleton (Recommended) 🏆
- ✅ Best performance - single shared instance
- ✅ Thread-safe Redis connections
- ✅ Optimal memory usage
- ✅ Recommended for most scenarios
Scoped
- ⚠️ New instance per HTTP request/scope
- ⚠️ Higher memory usage
- ⚠️ Use only if you need request-specific cache behavior
Transient
- ❌ New instance every time (not recommended)
- ❌ Poor performance and memory usage
- ❌ Potential connection exhaustion
- ❌ Avoid unless absolutely necessary
// Recommended approach
builder.Services.AddRedisFlexCache(options =>
{
options.Connection = "localhost:6379";
options.KeyPrefix = "myapp";
}); // Defaults to Singleton
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
Connection |
string |
"localhost:6379" |
Redis connection string |
KeyPrefix |
string |
null |
Prefix for all cache keys |
DefaultExpiration |
TimeSpan |
30 minutes |
Default expiration time |
EnableCompression |
bool |
false |
Enable compression for cached values |
EnableHashKey |
bool |
false |
Enable SHA256 hashing for cache keys |
ConnectionTimeout |
int |
5000 |
Connection timeout (ms) |
CommandTimeout |
int |
5000 |
Command timeout (ms) |
ConnectionCount |
int |
1 |
Number of Redis connections |
MaxKeyLength |
int |
100 |
Maximum cache key length |
IsDbCachedActive |
bool |
false |
Enable/disable caching |
Username |
string |
"" |
Redis username for authentication |
Password |
string |
"" |
Redis password for authentication |
ResolverStrategy |
MessagePackResolverStrategy |
Conservative |
MessagePack resolver strategy (Conservative/Standard) |
Thread Safety
RedisFlexCache is fully thread-safe and designed for concurrent access:
- ✅ Connection Pool: Uses multiple Redis connections with thread-safe connection pooling
- ✅ Stateless Operations: All cache operations are stateless and can be called concurrently
- ✅ Singleton Registration: Default singleton registration ensures thread-safe shared instances
- ✅ StackExchange.Redis: Built on top of the thread-safe StackExchange.Redis library
- ✅ Concurrent Access: Multiple threads can safely read/write to the cache simultaneously
// Safe to use from multiple threads
var tasks = Enumerable.Range(1, 100).Select(async i =>
{
await _cache.SetAsync($"key:{i}", $"value:{i}");
return await _cache.GetAsync<string>($"key:{i}");
});
var results = await Task.WhenAll(tasks);
Error Handling
The library includes comprehensive error handling:
try
{
var result = await _cache.GetAsync<Product>("product:123");
// Handle result
}
catch (Exception ex)
{
// Cache operations are designed to be non-blocking
// Errors are logged but don't throw exceptions in most cases
// You can still catch specific Redis exceptions if needed
}
Logging
The library uses Microsoft.Extensions.Logging for comprehensive logging:
// Configure logging in Program.cs
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Debug); // To see cache debug logs
Log levels:
- Debug: Cache hits/misses, key operations
- Information: Connection events, configuration
- Warning: Failed operations, retries
- Error: Connection failures, serialization errors
Performance Tips
- Use Async Methods: Always prefer async methods for better scalability
- Enable Compression: For large objects, enable compression to reduce memory usage
- Set Appropriate TTL: Use reasonable expiration times to balance performance and data freshness
- Use Key Prefixes: Organize your cache keys with prefixes for easier management
- Batch Operations: When possible, batch multiple cache operations
- Monitor Connection: Keep an eye on Redis connection health in production
- Optimize Serialization: For maximum performance, use MessagePack attributes on your models
High-Performance Serialization
For optimal caching performance, decorate your models with MessagePack attributes:
using MessagePack;
[MessagePackObject]
public class Product
{
[Key(0)]
public int Id { get; set; }
[Key(1)]
public string Name { get; set; }
[Key(2)]
public decimal Price { get; set; }
[Key(3)]
public DateTime CreatedAt { get; set; }
[Key(4)]
public List<string> Tags { get; set; }
}
[MessagePackObject]
public class User
{
[Key(0)]
public int UserId { get; set; }
[Key(1)]
public string Email { get; set; }
[Key(2)]
public string FirstName { get; set; }
[Key(3)]
public string LastName { get; set; }
[Key(4)]
public UserProfile Profile { get; set; }
}
[MessagePackObject]
public class UserProfile
{
[Key(0)]
public string Bio { get; set; }
[Key(1)]
public DateTime LastLoginAt { get; set; }
[Key(2)]
public Dictionary<string, object> Settings { get; set; }
}
Benefits of MessagePack attributes:
- Faster Serialization: Up to 10x faster than JSON serialization
- Smaller Size: Significantly reduced memory footprint
- Type Safety: Compile-time validation of serialization contracts
- Version Tolerance: Better handling of model evolution
Best Practices:
- Always use
[MessagePackObject]on classes you plan to cache - Assign sequential
[Key(n)]attributes starting from 0 - Keep key numbers consistent across versions for backward compatibility
- Use meaningful key numbers that won't conflict with future properties
Configuration Examples
For Background Jobs (Hangfire, etc.)
{
"RedisCache": {
"Connection": "localhost:6379",
"KeyPrefix": "backgroundjobs",
"DefaultExpiration": "01:00:00",
"ResolverStrategy": "Conservative"
}
}
For Web Applications (Maximum Compatibility)
{
"RedisCache": {
"Connection": "localhost:6379",
"KeyPrefix": "webapp",
"DefaultExpiration": "00:30:00",
"EnableCompression": true,
"ResolverStrategy": "Standard"
}
}
For High-Performance Scenarios
{
"RedisCache": {
"Connection": "localhost:6379",
"KeyPrefix": "highperf",
"DefaultExpiration": "00:15:00",
"EnableCompression": false,
"ResolverStrategy": "Conservative",
"ConnectionCount": 3,
"ConnectionTimeout": 3000,
"CommandTimeout": 3000
}
}
Troubleshooting
MessagePack Serialization Issues in Background Jobs
If you encounter MessagePack serialization errors in background job processors like Hangfire:
MessagePack.MessagePackSerializationException: Failed to deserialize [TypeName] value.
---> System.TypeInitializationException: The type initializer for 'FormatterCache`1' threw an exception.
---> System.IO.FileNotFoundException: System.Reflection.Emit, Version=8.0.0.0...
Solution: Use the Conservative resolver strategy (this is the default).
Configuration via appsettings.json:
{
"RedisCache": {
"Connection": "localhost:6379",
"ResolverStrategy": "Conservative"
}
}
Configuration via code:
services.Configure<RedisCacheOptions>(options =>
{
options.Connection = "your-redis-connection";
options.ResolverStrategy = MessagePackResolverStrategy.Conservative; // Default value
});
Why this happens: Background job processors like Hangfire run in restricted environments where dynamic code generation (System.Reflection.Emit) may not be available. The Conservative strategy avoids this by using reflection-based resolvers instead of dynamic IL generation.
Resolver Strategies:
- Conservative (Default): Safe for all environments, including background jobs
- Standard: Maximum compatibility but may fail in restricted environments
Valid ResolverStrategy values in appsettings.json:
"Conservative"- Recommended for background jobs and restricted environments"Standard"- Maximum compatibility for normal web applications
Requirements
- .NET 8.0 or later
- Redis 3.0 or later
Dependencies
- StackExchange.Redis (>= 2.7.20)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.8)
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.8)
- Microsoft.Extensions.Options (>= 9.0.8)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.8)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.8)
- MessagePack (>= 3.1.4)
License
This project is licensed under the MIT License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Support
If you encounter any issues or have questions, please file an issue on the GitHub repository.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net6.0
- MessagePack (>= 3.1.4)
- Microsoft.Extensions.Configuration.Abstractions (>= 6.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Options (>= 6.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 6.0.0)
- StackExchange.Redis (>= 2.7.20)
-
net7.0
- MessagePack (>= 3.1.4)
- Microsoft.Extensions.Configuration.Abstractions (>= 6.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Options (>= 6.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 6.0.0)
- StackExchange.Redis (>= 2.7.20)
-
net8.0
- MessagePack (>= 3.1.4)
- Microsoft.Extensions.Configuration.Abstractions (>= 6.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Options (>= 6.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 6.0.0)
- StackExchange.Redis (>= 2.7.20)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v1.0.6: Fixed MessagePack serialization issues in restricted environments like Hangfire background jobs. Added configurable MessagePackResolverStrategy with Conservative (default) and Standard options. Conservative strategy avoids dynamic code generation and works reliably in background job processors. Enhanced error handling with fallback deserialization. Improved compatibility with .NET security restrictions.