RedisCacheService 1.0.6
See the version list below for details.
dotnet add package RedisCacheService --version 1.0.6
NuGet\Install-Package RedisCacheService -Version 1.0.6
<PackageReference Include="RedisCacheService" Version="1.0.6" />
<PackageVersion Include="RedisCacheService" Version="1.0.6" />
<PackageReference Include="RedisCacheService" />
paket add RedisCacheService --version 1.0.6
#r "nuget: RedisCacheService, 1.0.6"
#:package RedisCacheService@1.0.6
#addin nuget:?package=RedisCacheService&version=1.0.6
#tool nuget:?package=RedisCacheService&version=1.0.6
RedisCacheService
A production-ready, feature-rich Redis caching library for .NET 9.0 that simplifies Redis integration with automatic retry logic, connection resilience, compression, and comprehensive logging.
🚀 Features
- ✅ Easy Integration - One-line setup with dependency injection
- ✅ Built-in Resilience - Automatic retry logic with exponential backoff
- ✅ Performance Optimized - Automatic compression for large values
- ✅ Full Async Support - All operations support async/await
- ✅ Enterprise Security - SSL/TLS support with certificate authentication
- ✅ Comprehensive Logging - Built-in structured logging
- ✅ Type-Safe Operations - Strongly-typed object caching
- ✅ Multiple Data Types - Strings, Objects, Lists, and Sets (Groups)
- ✅ GetOrSet Pattern - Cache-aside pattern built-in
- ✅ Pagination Support - Efficient cursor-based pagination for large datasets
- ✅ Atomic Operations - Transaction-based expiry setting prevents race conditions
📦 Installation
Package Manager
Install-Package RedisCacheService
.NET CLI
dotnet add package RedisCacheService
PackageReference
<PackageReference Include="RedisCacheService" Version="1.0.0" />
⚙️ Configuration
1. Add Configuration to appsettings.json
{
"Redis": {
"Server": "localhost",
"Port": 6379,
"Password": "",
"AbortOnConnectFail": false,
"ConnectTimeout": 5000,
"SyncTimeout": 5000,
"KeepAlive": 30,
"RetryCount": 3,
"RetryDelayMilliseconds": 5000,
"UseSsl": false,
"SslHost": "",
"CertificatePath": "",
"CertificatePassword": ""
}
}
2. Register Services in Startup/Program.cs
For .NET 6+ (Minimal API)
using RedisCacheService;
var builder = WebApplication.CreateBuilder(args);
// Add Redis Cache Service
builder.Services.AddCache(builder.Configuration);
var app = builder.Build();
For .NET Core 3.1 / .NET 5
using RedisCacheService;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Add Redis Cache Service
services.AddCache(Configuration);
}
}
3. Custom Configuration Section Name
If your configuration section has a different name:
services.AddCache(configuration, configSection: "MyRedisSettings");
🎯 Quick Start
Basic Usage
using RedisCacheService.Services;
public class MyService
{
private readonly RedisService _redis;
public MyService(RedisService redis)
{
_redis = redis;
}
// Option 1: Manual cache check
public async Task<string> GetCachedDataAsync(string key)
{
// Get cached string
var cached = await _redis.StringGetAsync(key);
if (cached != null)
return cached;
// If not cached, fetch from source
var data = await FetchFromSourceAsync();
// Cache it for 1 hour
await _redis.StringSetAsync(key, data, TimeSpan.FromHours(1));
return data;
}
// Option 2: Use GetOrSet pattern (recommended - simpler)
public async Task<string> GetCachedDataAsync(string key)
{
return await _redis.GetOrSetObjectAsync(
key,
async () => await FetchFromSourceAsync(),
TimeSpan.FromHours(1)
);
}
}
📚 Usage Examples
String Operations
Set and Get Strings
// Set a string value with expiration
bool success = await _redis.StringSetAsync(
"user:123:name",
"John Doe",
TimeSpan.FromMinutes(30)
);
// Get a string value
string? name = await _redis.StringGetAsync("user:123:name");
// Synchronous versions
_redis.StringSet("key", "value", TimeSpan.FromHours(1));
string? value = _redis.StringGet("key");
Object Caching
Cache Objects with Automatic Serialization
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
// Save an object to cache
var user = new User { Id = 123, Name = "John", Email = "john@example.com" };
bool saved = await _redis.ListSaveObjectsAsync("user:123", new List<User> { user });
// Get a single object
User? cachedUser = await _redis.GetSingleObjectAsync<User>("user:123");
// Get a list of objects
List<User> users = await _redis.ListGetObjectsAsync<User>("users:all");
GetOrSet Pattern (Cache-Aside)
This pattern automatically checks cache first, and if not found, executes your function and caches the result:
// Get from cache, or fetch from database and cache it
User user = await _redis.GetOrSetObjectAsync(
"user:123",
async () => await _dbContext.Users.FindAsync(123),
TimeSpan.FromHours(1)
);
// Synchronous version
User user = _redis.GetOrSetObject(
"user:123",
() => _dbContext.Users.Find(123),
TimeSpan.FromHours(1)
);
List Operations
Add to Lists
// Add a string to a list
long length = await _redis.ListAddAsync(
"recent:users",
"user123",
TimeSpan.FromDays(7)
);
// Add an object to a list (automatically serialized)
var activity = new Activity { UserId = 123, Action = "Login" };
long length = await _redis.ListAddAsync("activities", activity, TimeSpan.FromHours(24));
// Add to the top of the list
await _redis.ListInsertTopAsync("recent:users", "user456");
Get List of Objects
// Get list with fallback to database
List<User> users = await _redis.ListGetObjectsAsync<User>(
"users:active",
async () => await _dbContext.Users.Where(u => u.IsActive).ToListAsync(),
TimeSpan.FromMinutes(30)
);
Group Operations (Redis Sets)
Groups are unordered collections of unique string values, perfect for tags, roles, and unique collections.
Add to Groups
// Add a single value to a group
bool added = await _redis.GroupAddAsync("article:123:tags", "technology");
// Add multiple values at once (more efficient than multiple single adds)
string[] tags = { "technology", "programming", "csharp" };
long addedCount = await _redis.GroupAddMultipleAsync("article:123:tags", tags);
// Add with expiration (atomic operation - uses Redis transactions)
// The expiry is set atomically with the add operation to prevent race conditions
await _redis.GroupAddAsync("temp:users", "user123", TimeSpan.FromHours(1));
// Add multiple values with expiration
await _redis.GroupAddMultipleAsync("session:users", userIds, TimeSpan.FromMinutes(30));
Note: Expiry operations are atomic - the group expiry is set atomically with the add operation using Redis transactions, ensuring no race conditions.
Check Membership
// Check if a value exists in a group
bool hasTag = await _redis.GroupContainsAsync("article:123:tags", "technology");
if (hasTag)
{
Console.WriteLine("Article has technology tag");
}
Get All Members
// Get all members as strings (for small groups - loads everything into memory)
List<string> tags = await _redis.GroupGetAllAsStringAsync("article:123:tags");
// Get all members as integers (for small groups)
List<int> userIds = await _redis.GroupGetAllAsIntAsync("active:users");
// Get count without loading all members (efficient)
long count = await _redis.GroupCountAsync("article:123:tags");
// For large groups, use pagination instead (see Pagination section below)
Pagination for Large Groups (Cursor-Based)
The library uses Redis SCAN cursors for efficient pagination. This is the recommended approach for large groups to avoid loading all members into memory.
// Iterate through large groups efficiently using cursor-based pagination
long cursor = 0;
do
{
var result = await _redis.GroupGetAllAsStringAsync(
"large:group",
cursor, // Start with 0, then use result.Cursor for next iteration
pageSize: 100,
pattern: null // Optional: filter by pattern (e.g., "user:*")
);
foreach (string member in result.Members)
{
// Process member
Console.WriteLine(member);
}
// Use the cursor from result for next iteration
cursor = result.Cursor; // When cursor is 0, there are no more pages
} while (result.HasMore); // Or check: result.Cursor != 0
// Alternative: Using GroupGetAll (returns RedisValue[])
long cursor = 0;
do
{
var result = await _redis.GroupGetAllAsync("large:group", cursor, pageSize: 100);
foreach (var member in result.Members)
{
Console.WriteLine(member.ToString());
}
cursor = result.Cursor;
} while (result.HasMore);
Key Points:
- ✅ Uses actual Redis SCAN cursors (not page numbers) - efficient and safe
- ✅ Start with
cursor = 0for a new scan - ✅ Use
result.Cursorfor the next iteration - ✅ When
cursor == 0, there are no more pages - ✅
result.HasMoreis a convenience property (equivalent tocursor != 0) - ✅ Supports pattern matching for filtering members
Set Operations
// Union - Get all unique members from multiple groups
string[] groups = { "article:1:tags", "article:2:tags", "article:3:tags" };
RedisValue[] allTags = await _redis.GroupUnionAsync(groups);
// Intersection - Get members that exist in ALL groups
RedisValue[] commonTags = await _redis.GroupIntersectionAsync(groups);
// Difference - Get members in first group but not in others
string[] others = { "article:2:tags", "article:3:tags" };
RedisValue[] uniqueTags = await _redis.GroupDifferenceAsync("article:1:tags", others);
// Move a member from one group to another
bool moved = await _redis.GroupMoveAsync("source:group", "dest:group", "value");
// Get random members
RedisValue[] randomTags = await _redis.GroupRandomMemberAsync("article:123:tags", count: 5);
Remove from Groups
// Remove a single value
bool removed = await _redis.GroupRemoveAsync("article:123:tags", "old-tag");
// Remove multiple values
string[] tagsToRemove = { "tag1", "tag2", "tag3" };
long removedCount = await _redis.GroupRemoveMultipleAsync("article:123:tags", tagsToRemove);
// Clear entire group
bool cleared = await _redis.GroupClearAsync("temp:group");
Connection Status
// Check if Redis is connected
if (_redis.IsConnected)
{
// Perform Redis operations
}
// Get direct database access (advanced)
IDatabase db = _redis.GetDatabase(0); // 0 is default database
🔧 Advanced Configuration
SSL/TLS Configuration
For secure connections (e.g., Azure Redis Cache):
{
"Redis": {
"Server": "your-redis.redis.cache.windows.net",
"Port": 6380,
"Password": "your-password",
"UseSsl": true,
"SslHost": "your-redis.redis.cache.windows.net",
"CertificatePath": "path/to/certificate.pfx",
"CertificatePassword": "cert-password"
}
}
Connection Resilience
The library automatically handles:
- Connection failures with retry logic
- Exponential backoff on retries
- Graceful degradation when Redis is unavailable
- Automatic reconnection
Configure retry behavior:
{
"Redis": {
"RetryCount": 5,
"RetryDelayMilliseconds": 1000
}
}
Compression
Large values are automatically compressed when enabled:
// Compression is enabled by default for values > 1KB
// You can configure this in the CacheOptions (if exposed in future versions)
🎨 Best Practices
1. Use Meaningful Key Names
// ✅ Good
await _redis.StringSetAsync("user:123:profile", data);
await _redis.StringSetAsync("product:456:details", data);
// ❌ Bad
await _redis.StringSetAsync("u123", data);
await _redis.StringSetAsync("p456", data);
2. Set Appropriate Expiration Times
// User data - 1 hour
await _redis.StringSetAsync("user:123", data, TimeSpan.FromHours(1));
// Session data - 30 minutes
await _redis.StringSetAsync("session:abc", data, TimeSpan.FromMinutes(30));
// Static reference data - 24 hours
await _redis.StringSetAsync("countries:list", data, TimeSpan.FromHours(24));
3. Use GetOrSet for Database Queries
// ✅ Good - Automatically handles cache miss
var user = await _redis.GetOrSetObjectAsync(
$"user:{userId}",
async () => await _dbContext.Users.FindAsync(userId),
TimeSpan.FromHours(1)
);
// ❌ Less efficient - Manual cache check
var cached = await _redis.GetSingleObjectAsync<User>($"user:{userId}");
if (cached == null)
{
cached = await _dbContext.Users.FindAsync(userId);
await _redis.ListSaveObjectsAsync($"user:{userId}", new List<User> { cached });
}
4. Handle Null Values
// Always check for null
string? value = await _redis.StringGetAsync("key");
if (value != null)
{
// Use value
}
// For objects
User? user = await _redis.GetSingleObjectAsync<User>("user:123");
if (user != null)
{
// Use user
}
5. Use Cursor-Based Pagination for Large Datasets
// ✅ Good - Cursor-based pagination for large groups (uses Redis SCAN)
long cursor = 0;
do
{
var result = await _redis.GroupGetAllAsStringAsync("large:group", cursor, pageSize: 100);
// Process result.Members
cursor = result.Cursor; // Use actual Redis cursor for next iteration
} while (result.HasMore); // When cursor is 0, no more pages
// ✅ Also Good - For small groups (< 1000 members), loading all is acceptable
List<string> all = await _redis.GroupGetAllAsStringAsync("small:group");
// ❌ Bad - Loading very large groups into memory
List<string> all = await _redis.GroupGetAllAsStringAsync("very:large:group"); // Could cause memory issues
When to use pagination:
- Groups with > 1000 members
- When memory usage is a concern
- When processing members in batches
- When you need pattern matching (filtering)
How cursor-based pagination works:
- Uses Redis
SSCANcommand with actual cursors (not page numbers) - Efficient O(1) iteration, safe for concurrent modifications
- Cursor
0means no more pages - Supports pattern matching:
GroupGetAllAsync("users", 0, 100, "user:*")
6. Use Cancellation Tokens
public async Task ProcessDataAsync(CancellationToken cancellationToken)
{
var data = await _redis.StringGetAsync("key", cancellationToken);
// Process data
}
🐛 Troubleshooting
Redis Connection Issues
If Redis is not connecting:
Check Configuration
// Verify your configuration is loaded correctly var redisConfig = Configuration.GetSection("Redis"); var server = redisConfig["Server"]; var port = redisConfig["Port"];Check Connection Status
if (!_redis.IsConnected) { _logger.LogWarning("Redis is not connected"); // Handle gracefully }Check Logs The library logs connection attempts, failures, and retries. Check your application logs for details.
Performance Issues
- Enable Compression - Automatically enabled for values > 1KB
- Use Appropriate Expiration - Don't cache forever
- Use Pagination - For large datasets
- Monitor Key Count - Too many keys can impact performance
Common Errors
"Redis connection error"
- Check if Redis server is running
- Verify connection string and credentials
- Check firewall/network settings
"Key length exceeds maximum"
- Default max key length is 512 bytes
- Use shorter key names or configure max length
"Value size exceeds maximum cache size"
- Default max value size is 1MB
- Consider splitting large data or increasing limit
📖 API Reference
String Operations
| Method | Description |
|---|---|
StringGet(string key) |
Get string value synchronously |
StringGetAsync(string key) |
Get string value asynchronously |
StringSet(string key, string value, TimeSpan? expiry) |
Set string value synchronously |
StringSetAsync(string key, string value, TimeSpan? expiry) |
Set string value asynchronously |
Object Operations
| Method | Description |
|---|---|
GetSingleObject<T>(string key) |
Get a single object |
GetSingleObjectAsync<T>(string key) |
Get a single object asynchronously |
GetOrSetObject<T>(string key, Func<T> getData, TimeSpan? expiry) |
Get or set object with fallback |
GetOrSetObjectAsync<T>(string key, Func<Task<T>> getData, TimeSpan? expiry) |
Get or set object asynchronously |
ListSaveObjects<T>(string key, List<T> data, TimeSpan? expiry) |
Save list of objects |
ListGetObjects<T>(string key, Func<List<T>>? getData, TimeSpan? expiry) |
Get list of objects with fallback |
List Operations
| Method | Description |
|---|---|
ListAdd(string key, string value, TimeSpan? expiry) |
Add to list |
ListAddAsync<T>(string key, T value, TimeSpan? expiry) |
Add object to list |
ListInsertTop(string key, string value, TimeSpan? expiry) |
Insert at top of list |
Group Operations (Sets)
| Method | Description |
|---|---|
GroupAdd(string groupName, string value, TimeSpan? expiry) |
Add value to group (atomic expiry) |
GroupAddAsync(string groupName, string value, TimeSpan? expiry) |
Add value to group asynchronously (atomic expiry) |
GroupAddMultiple(string groupName, string[] values, TimeSpan? expiry) |
Add multiple values (atomic expiry) |
GroupAddMultipleAsync(string groupName, string[] values, TimeSpan? expiry) |
Add multiple values asynchronously (atomic expiry) |
GroupRemove(string groupName, string value) |
Remove value from group |
GroupContains(string groupName, string value) |
Check if value exists |
GroupCount(string groupName) |
Get member count |
GroupGetAllAsString(string groupName) |
Get all members as strings (for small groups) |
GroupGetAllAsString(string groupName, long cursor, int pageSize, string? pattern) |
Get paginated members with cursor |
GroupGetAllAsStringAsync(string groupName, long cursor, int pageSize, string? pattern) |
Get paginated members asynchronously |
GroupGetAll(string groupName, long cursor, int pageSize, string? pattern) |
Get paginated members as RedisValue[] |
GroupGetAllAsync(string groupName, long cursor, int pageSize, string? pattern) |
Get paginated members asynchronously |
GroupUnion(string[] groupNames) |
Union of multiple groups |
GroupIntersection(string[] groupNames) |
Intersection of groups |
GroupDifference(string firstGroup, string[] otherGroups) |
Difference between groups |
GroupMove(string sourceGroup, string destGroup, string value) |
Move member between groups |
GroupClear(string groupName) |
Clear entire group |
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License
[Specify your license here]
🙏 Acknowledgments
- Built on StackExchange.Redis
- Target Framework: .NET 9.0
📞 Support
For issues, questions, or contributions, please contact us:
- Email: info@waelelazizy.com
- Phone: +973 33030730
- GitHub Issues: Open an issue (if applicable)
Made with ❤️ for the .NET community
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. 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. |
-
net9.0
- Microsoft.Extensions.Configuration.Abstractions (>= 9.0.9)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.9)
- StackExchange.Redis (>= 2.9.17)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.11 | 51 | 3/1/2026 |
| 10.0.10 | 62 | 2/15/2026 |
| 10.0.8 | 654 | 12/2/2025 |
| 10.0.7 | 653 | 12/2/2025 |
| 10.0.6 | 643 | 12/2/2025 |
| 10.0.5 | 164 | 11/23/2025 |
| 10.0.4 | 128 | 11/23/2025 |
| 10.0.3 | 143 | 11/23/2025 |
| 10.0.2 | 142 | 11/23/2025 |
| 10.0.1 | 134 | 11/23/2025 |
| 1.0.11 | 131 | 11/23/2025 |
| 1.0.10 | 132 | 11/23/2025 |
| 1.0.9 | 142 | 11/23/2025 |
| 1.0.8 | 140 | 11/23/2025 |
| 1.0.7 | 112 | 11/23/2025 |
| 1.0.6 | 101 | 11/23/2025 |
| 1.0.5 | 188 | 11/22/2025 |
| 1.0.4 | 371 | 11/20/2025 |
| 1.0.3 | 365 | 11/20/2025 |
| 1.0.2 | 375 | 11/20/2025 |