RedisCacheService 1.0.8

There is a newer version of this package available.
See the version list below for details.
dotnet add package RedisCacheService --version 1.0.8
                    
NuGet\Install-Package RedisCacheService -Version 1.0.8
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RedisCacheService" Version="1.0.8" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RedisCacheService" Version="1.0.8" />
                    
Directory.Packages.props
<PackageReference Include="RedisCacheService" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RedisCacheService --version 1.0.8
                    
#r "nuget: RedisCacheService, 1.0.8"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RedisCacheService@1.0.8
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RedisCacheService&version=1.0.8
                    
Install as a Cake Addin
#tool nuget:?package=RedisCacheService&version=1.0.8
                    
Install as a Cake Tool

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 = 0 for a new scan
  • ✅ Use result.Cursor for the next iteration
  • ✅ When cursor == 0, there are no more pages
  • result.HasMore is a convenience property (equivalent to cursor != 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 SSCAN command with actual cursors (not page numbers)
  • Efficient O(1) iteration, safe for concurrent modifications
  • Cursor 0 means 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:

  1. Check Configuration

    // Verify your configuration is loaded correctly
    var redisConfig = Configuration.GetSection("Redis");
    var server = redisConfig["Server"];
    var port = redisConfig["Port"];
    
  2. Check Connection Status

    if (!_redis.IsConnected)
    {
        _logger.LogWarning("Redis is not connected");
        // Handle gracefully
    }
    
  3. Check Logs The library logs connection attempts, failures, and retries. Check your application logs for details.

Performance Issues

  1. Enable Compression - Automatically enabled for values > 1KB
  2. Use Appropriate Expiration - Don't cache forever
  3. Use Pagination - For large datasets
  4. 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

📞 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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