SkyWebFramework.Caching
1.0.0
dotnet add package SkyWebFramework.Caching --version 1.0.0
NuGet\Install-Package SkyWebFramework.Caching -Version 1.0.0
<PackageReference Include="SkyWebFramework.Caching" Version="1.0.0" />
<PackageVersion Include="SkyWebFramework.Caching" Version="1.0.0" />
<PackageReference Include="SkyWebFramework.Caching" />
paket add SkyWebFramework.Caching --version 1.0.0
#r "nuget: SkyWebFramework.Caching, 1.0.0"
#:package SkyWebFramework.Caching@1.0.0
#addin nuget:?package=SkyWebFramework.Caching&version=1.0.0
#tool nuget:?package=SkyWebFramework.Caching&version=1.0.0
🚀 SkyWebFramework.Caching
A powerful, high-performance caching library for .NET that provides a unified API for memory, distributed, and multi-level caching with advanced features like automatic compression, metrics tracking, and scoped contexts.
✨ Features
🎯 Core Capabilities
- Unified API - Single
IEasyCacheinterface for all cache types - Memory Caching - Fast in-process caching with IMemoryCache
- Distributed Caching - Support for Redis, SQL Server, and other distributed caches
- Multi-Level Caching - Automatic L1 (Memory) + L2 (Distributed) with cache promotion
- Thread-Safe Operations - Lock-free reads with semaphore-protected writes
🔥 Advanced Features
- Scoped Contexts - Logical cache partitioning with automatic key prefixing
- Compression - Automatic GZIP compression for distributed caches
- Dual Serialization - Choose between System.Text.Json or Newtonsoft.Json
- Built-in Metrics - Track cache hits, misses, and errors in real-time
- GetOrSet Pattern - Atomic cache-aside pattern with factory functions
- Flexible Expiration - Absolute, sliding, and relative expiration policies
- Pattern Matching - Search and enumerate cache keys with wildcard patterns
🛡️ Production-Ready
- Exception Handling - Configurable exception suppression
- Logging Integration - Built-in logging with Microsoft.Extensions.Logging
- Performance Optimized - Minimal allocations and lock contention
- Async/Await - Fully asynchronous API
📦 Installation
dotnet add package SkyWebFramework.Caching
Or via NuGet Package Manager:
Install-Package SkyWebFramework.Caching
🚀 Quick Start
1. Memory Cache (L1)
using SkyWebFramework.Caching;
// Register in Startup/Program.cs
builder.Services.AddMemoryCache();
builder.Services.AddEasyMemoryCache(options =>
{
options.DefaultTtl = TimeSpan.FromMinutes(30);
options.KeyPrefix = "myapp:";
});
// Use in your code
public class ProductService
{
private readonly IEasyCache _cache;
public ProductService(IEasyCache cache)
{
_cache = cache;
}
public async Task<Product> GetProductAsync(int id)
{
return await _cache.GetOrSetAsync(
$"product:{id}",
async () => await _database.GetProductAsync(id),
new CacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
}
);
}
}
2. Distributed Cache (L2 - Redis)
// Register Redis
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
});
builder.Services.AddEasyDistributedCache(options =>
{
options.DefaultTtl = TimeSpan.FromHours(1);
options.EnableCompression = true; // Compress cached data
options.SerializerType = JsonSerializerType.SystemTextJson;
});
// Use distributed cache
public class UserService
{
private readonly IDistributedEasyCache _cache;
public UserService(IDistributedEasyCache cache)
{
_cache = cache;
}
public async Task<User> GetUserAsync(string userId)
{
// Cache across multiple servers
return await _cache.GetOrSetAsync(
$"user:{userId}",
async () => await _database.GetUserAsync(userId)
);
}
}
3. Multi-Level Cache (L1 + L2)
// Register both memory and distributed caches
builder.Services.AddMemoryCache();
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
});
builder.Services.AddEasyMultiLevelCache(options =>
{
options.DefaultTtl = TimeSpan.FromMinutes(30);
options.EnableCompression = true;
});
// Automatic cache promotion: L2 → L1
public class OrderService
{
private readonly IEasyCache _cache;
public OrderService(IEasyCache cache)
{
_cache = cache;
}
public async Task<Order> GetOrderAsync(int orderId)
{
// 1. Check L1 (memory) - fastest
// 2. Check L2 (Redis) - if found, promote to L1
// 3. Load from database - cache in both L1 and L2
return await _cache.GetOrSetAsync(
$"order:{orderId}",
async () => await _database.GetOrderAsync(orderId)
);
}
}
🎯 Core API
Basic Operations
// Get value
var user = await cache.GetAsync<User>("user:123");
// Set value with expiration
await cache.SetAsync("user:123", user, new CacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15)
});
// Remove
await cache.RemoveAsync("user:123");
// Check existence
bool exists = await cache.ExistsAsync("user:123");
// Get or set (cache-aside pattern)
var product = await cache.GetOrSetAsync(
"product:456",
async () => await LoadProductFromDatabase(456)
);
Cache Entry Options
var options = new CacheEntryOptions
{
// Expire after fixed time
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
// Expire if not accessed within time
SlidingExpiration = TimeSpan.FromMinutes(10),
// Absolute expiration timestamp
AbsoluteExpiration = DateTimeOffset.UtcNow.AddHours(2),
// Memory cache priority
Priority = CacheItemPriority.High,
// Size for memory cache limits
Size = 1024
};
await cache.SetAsync("key", value, options);
🔥 Advanced Features
Scoped Cache Contexts
Create logical cache partitions without manual key management:
public class MultiTenantService
{
private readonly IEasyCache _cache;
public MultiTenantService(IEasyCache cache)
{
_cache = cache;
}
public async Task<Data> GetTenantDataAsync(string tenantId, string dataId)
{
// Create tenant-specific cache scope
var tenantCache = _cache.WithScope($"tenant:{tenantId}");
// Keys are automatically prefixed with "tenant:{tenantId}:"
return await tenantCache.GetOrSetAsync(
dataId, // Actually stored as "tenant:{tenantId}:{dataId}"
async () => await LoadData(tenantId, dataId)
);
}
public async Task ClearTenantCacheAsync(string tenantId)
{
var tenantCache = _cache.WithScope($"tenant:{tenantId}");
await tenantCache.ClearAsync();
}
}
Nested Scopes
var userCache = cache.WithScope("users");
var adminCache = userCache.WithScope("admins");
// Key: "easycache:users:admins:123"
await adminCache.SetAsync("123", adminUser);
Cache Metrics
Track cache performance in real-time:
public class CacheMonitor
{
private readonly IEasyCache _cache;
public CacheMonitor(IEasyCache cache)
{
_cache = cache;
}
public CacheStats GetStats()
{
var metrics = _cache.Metrics;
return new CacheStats
{
Hits = metrics.Hits,
Misses = metrics.Misses,
Errors = metrics.Errors,
HitRatio = (double)metrics.Hits / (metrics.Hits + metrics.Misses)
};
}
}
Key Pattern Matching
// Get all user keys
var userKeys = await cache.GetKeysAsync("user:*");
// Get keys with specific pattern
var adminKeys = await cache.GetKeysAsync("user:admin:*");
// Contains pattern
var searchKeys = await cache.GetKeysAsync("*search*");
// Get all keys
var allKeys = await cache.GetKeysAsync("*");
Compression (Distributed Only)
builder.Services.AddEasyDistributedCache(options =>
{
options.EnableCompression = true; // Enable GZIP compression
});
// Large objects are automatically compressed
await cache.SetAsync("large-report", reportData);
// Saves bandwidth and Redis memory
Custom Serialization
// Use System.Text.Json (default, faster)
builder.Services.AddEasyDistributedCache(options =>
{
options.SerializerType = JsonSerializerType.SystemTextJson;
options.JsonOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
WriteIndented = false
};
});
// Or use Newtonsoft.Json (more features)
builder.Services.AddEasyDistributedCache(options =>
{
options.SerializerType = JsonSerializerType.NewtonsoftJson;
});
🛠️ Extension Methods
GetOrSet Variations
// Simple version with TimeSpan
var data = await cache.GetOrSetAsync(
"key",
async () => await LoadData(),
TimeSpan.FromMinutes(10)
);
// With default value
var data = await cache.GetOrSetAsync(
"key",
async () => await LoadData(),
defaultValue: new Data(),
expiration: TimeSpan.FromMinutes(10)
);
Set with Expiration
await cache.SetWithExpirationAsync("key", value, TimeSpan.FromMinutes(5));
Add (Set if not exists)
bool added = await cache.AddAsync("key", value, TimeSpan.FromMinutes(10));
if (added)
{
Console.WriteLine("Value was added");
}
else
{
Console.WriteLine("Key already exists");
}
Get and Remove (Pop)
var value = await cache.GetAndRemoveAsync<User>("temp-user");
// Value is retrieved and immediately removed
Bulk Operations
// Get multiple keys
var keys = new[] { "user:1", "user:2", "user:3" };
var users = await cache.GetMultipleAsync<User>(keys);
foreach (var (key, user) in users)
{
Console.WriteLine($"{key}: {user?.Name}");
}
// Set multiple keys
var values = new Dictionary<string, User>
{
["user:1"] = user1,
["user:2"] = user2,
["user:3"] = user3
};
await cache.SetMultipleAsync(values, new CacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15)
});
🏗️ Cache Manager
Manage multiple named caches:
// Register cache manager
builder.Services.AddEasyCacheManager();
// Register multiple named caches
var manager = serviceProvider.GetRequiredService<EasyCacheManager>();
manager.RegisterCache("products", productCache);
manager.RegisterCache("users", userCache);
manager.RegisterCache("sessions", sessionCache);
// Use named caches
var productCache = manager.GetCache("products");
await productCache.SetAsync("product:123", product);
// List all cache names
var cacheNames = manager.GetCacheNames();
// Clear all caches
await manager.ClearAllAsync();
📊 Configuration Options
Global Cache Options
public record CacheOptions
{
// Default time-to-live
public TimeSpan DefaultTtl { get; init; } = TimeSpan.FromMinutes(30);
// Enable compression (distributed only)
public bool EnableCompression { get; init; } = false;
// JSON serialization options
public JsonSerializerOptions? JsonOptions { get; init; }
// Key prefix for all cache entries
public string KeyPrefix { get; init; } = "easycache:";
// Serializer type (SystemTextJson or NewtonsoftJson)
public JsonSerializerType SerializerType { get; init; } = JsonSerializerType.SystemTextJson;
// Suppress cache exceptions (log but don't throw)
public bool SuppressExceptions { get; init; } = true;
}
Usage Example
builder.Services.AddEasyDistributedCache(options =>
{
options.DefaultTtl = TimeSpan.FromHours(2);
options.EnableCompression = true;
options.KeyPrefix = "myapp:v1:";
options.SuppressExceptions = false; // Throw exceptions in dev
options.SerializerType = JsonSerializerType.SystemTextJson;
options.JsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
});
🎨 Common Patterns
1. Cache-Aside Pattern
public async Task<Product> GetProductAsync(int id)
{
return await _cache.GetOrSetAsync(
$"product:{id}",
async () =>
{
var product = await _db.Products.FindAsync(id);
return product ?? throw new NotFoundException();
}
);
}
2. Refresh Ahead Pattern
public async Task<Data> GetWithRefreshAsync(string key)
{
var cached = await _cache.GetAsync<CachedData>(key);
if (cached != null)
{
// If data is older than 80% of TTL, refresh in background
var age = DateTime.UtcNow - cached.CachedAt;
if (age > TimeSpan.FromMinutes(8)) // 80% of 10 min
{
_ = Task.Run(async () =>
{
var fresh = await LoadFromSource();
await _cache.SetAsync(key, fresh);
});
}
return cached.Data;
}
// Cache miss - load and cache
var data = await LoadFromSource();
await _cache.SetAsync(key, new CachedData
{
Data = data,
CachedAt = DateTime.UtcNow
});
return data;
}
3. Write-Through Cache
public async Task UpdateProductAsync(Product product)
{
// Update database
await _db.Products.UpdateAsync(product);
await _db.SaveChangesAsync();
// Update cache
await _cache.SetAsync($"product:{product.Id}", product);
}
4. Write-Behind (Write-Back) Cache
public async Task UpdateProductAsync(Product product)
{
// Update cache immediately
await _cache.SetAsync($"product:{product.Id}", product);
// Update database in background
_ = Task.Run(async () =>
{
await _db.Products.UpdateAsync(product);
await _db.SaveChangesAsync();
});
}
5. Cache Invalidation on Update
public async Task UpdateUserAsync(User user)
{
await _db.Users.UpdateAsync(user);
await _db.SaveChangesAsync();
// Invalidate related cache entries
await _cache.RemoveAsync($"user:{user.Id}");
await _cache.RemoveAsync($"user:email:{user.Email}");
}
6. Multi-Tenant Caching
public class MultiTenantCacheService
{
private readonly IEasyCache _cache;
public async Task<T> GetTenantDataAsync<T>(string tenantId, string key)
{
var tenantCache = _cache.WithScope($"tenant:{tenantId}");
return await tenantCache.GetAsync<T>(key);
}
public async Task ClearTenantAsync(string tenantId)
{
var tenantCache = _cache.WithScope($"tenant:{tenantId}");
await tenantCache.ClearAsync();
}
}
🔧 Redis Setup
Using StackExchange.Redis
// Install: dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
options.InstanceName = "MyApp:";
});
builder.Services.AddEasyDistributedCache(options =>
{
options.EnableCompression = true;
options.DefaultTtl = TimeSpan.FromMinutes(30);
});
Using Azure Redis Cache
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = Configuration.GetConnectionString("AzureRedis");
options.InstanceName = "Production:";
});
Using SQL Server Cache
// Install: dotnet add package Microsoft.Extensions.Caching.SqlServer
builder.Services.AddDistributedSqlServerCache(options =>
{
options.ConnectionString = Configuration.GetConnectionString("CacheDb");
options.SchemaName = "dbo";
options.TableName = "CacheEntries";
});
builder.Services.AddEasyDistributedCache();
📋 Best Practices
1. Use Appropriate Cache Types
// Memory cache: Fast, but lost on restart
// - Session data
// - Frequently accessed read-only data
// - Small datasets
// Distributed cache: Shared across servers, persistent
// - User profiles
// - Product catalogs
// - API responses
// Multi-level: Best of both worlds
// - Critical data needing both speed and durability
2. Set Reasonable TTLs
// Short TTL for frequently changing data
await cache.SetAsync("stock-price", price, new CacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30)
});
// Long TTL for static data
await cache.SetAsync("country-list", countries, new CacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1)
});
// Sliding expiration for session data
await cache.SetAsync("user-session", session, new CacheEntryOptions
{
SlidingExpiration = TimeSpan.FromMinutes(20)
});
3. Use Key Prefixes
builder.Services.AddEasyMemoryCache(options =>
{
options.KeyPrefix = $"{appName}:v{apiVersion}:";
});
// Keys: "myapp:v2:user:123", "myapp:v2:product:456"
// Easy to identify and version your cache entries
4. Monitor Cache Performance
public class CacheHealthCheck : IHealthCheck
{
private readonly IEasyCache _cache;
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
var metrics = _cache.Metrics;
var hitRatio = (double)metrics.Hits / (metrics.Hits + metrics.Misses);
if (hitRatio < 0.5)
{
return HealthCheckResult.Degraded(
$"Low cache hit ratio: {hitRatio:P}");
}
return HealthCheckResult.Healthy(
$"Cache hit ratio: {hitRatio:P}");
}
}
5. Handle Cache Failures Gracefully
public async Task<Product> GetProductAsync(int id)
{
try
{
return await _cache.GetOrSetAsync(
$"product:{id}",
async () => await _db.GetProductAsync(id)
);
}
catch (Exception ex)
{
_logger.LogError(ex, "Cache error, falling back to database");
return await _db.GetProductAsync(id);
}
}
6. Use Compression for Large Objects
builder.Services.AddEasyDistributedCache(options =>
{
options.EnableCompression = true; // Compress objects > 1KB
});
// Good for:
// - Large JSON documents
// - Report data
// - Search results
🔍 Troubleshooting
Cache Not Working
// 1. Check if cache service is registered
var cache = serviceProvider.GetService<IEasyCache>();
if (cache == null)
{
Console.WriteLine("Cache not registered!");
}
// 2. Check if distributed cache is configured
var distributedCache = serviceProvider.GetService<IDistributedCache>();
if (distributedCache == null)
{
Console.WriteLine("Add Redis or SQL Server cache!");
}
// 3. Enable logging to see cache operations
builder.Services.AddLogging(config =>
{
config.AddConsole();
config.SetMinimumLevel(LogLevel.Debug);
});
Redis Connection Issues
// Test Redis connection
try
{
await cache.SetAsync("test", "value");
var result = await cache.GetAsync<string>("test");
Console.WriteLine(result == "value" ? "✓ Redis working" : "✗ Redis failed");
}
catch (Exception ex)
{
Console.WriteLine($"✗ Redis error: {ex.Message}");
}
Performance Issues
// Check cache metrics
var metrics = cache.Metrics;
Console.WriteLine($"Hits: {metrics.Hits}");
Console.WriteLine($"Misses: {metrics.Misses}");
Console.WriteLine($"Errors: {metrics.Errors}");
Console.WriteLine($"Hit Ratio: {(double)metrics.Hits / (metrics.Hits + metrics.Misses):P}");
// Low hit ratio? Consider:
// - Increasing TTL
// - Using multi-level cache
// - Pre-warming cache on startup
📚 API Reference
IEasyCache Interface
| Method | Description |
|---|---|
GetAsync<T>(key) |
Get cached value |
SetAsync<T>(key, value, options?) |
Set cached value |
RemoveAsync(key) |
Remove cached value |
ExistsAsync(key) |
Check if key exists |
GetOrSetAsync<T>(key, factory, options?) |
Get or compute and cache |
GetKeysAsync(pattern) |
Get keys matching pattern |
ClearAsync() |
Clear all cache entries |
WithScope(scopeName) |
Create scoped cache view |
Metrics |
Get cache metrics |
Extension Methods
| Method | Description |
|---|---|
GetOrSetAsync<T>(key, factory, expiration) |
Simplified GetOrSet |
SetWithExpirationAsync<T>(key, value, expiration) |
Set with TTL |
AddAsync<T>(key, value, expiration?) |
Add if not exists |
GetAndRemoveAsync<T>(key) |
Get and remove atomically |
GetMultipleAsync<T>(keys) |
Batch get operation |
SetMultipleAsync<T>(values, options?) |
Batch set operation |
💡 Performance Tips
- Use Multi-Level Cache for best performance and reliability
- Enable Compression for large objects in distributed cache
- Set appropriate TTLs - not too short (cache thrashing), not too long (stale data)
- Use Scoped Caches to avoid key conflicts
- Monitor Metrics to identify cache effectiveness
- Batch Operations when possible to reduce round trips
- Use Memory Cache for hot data accessed frequently
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
💬 Support
- 📧 Email: pratapsinghsurya19.com
- 📖 Documentation: Full Documentation
🌟 Features Roadmap
- Redis Cluster support
- Cache warming strategies
- Automatic cache invalidation
- Cache tagging and bulk invalidation
- Distributed lock support
- Cache statistics dashboard
- Memory cache size limits
- Custom eviction policies
Made with ❤️ by the SkyWebFramework Team
| 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 was computed. 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
- Microsoft.Extensions.Caching.Abstractions (>= 6.0.0)
- Microsoft.Extensions.Caching.Memory (>= 6.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 6.0.4)
- Newtonsoft.Json (>= 13.0.3)
- System.Text.Json (>= 6.0.10)
-
net8.0
- Microsoft.Extensions.Caching.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Caching.Memory (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.2)
- Newtonsoft.Json (>= 13.0.3)
- System.Text.Json (>= 8.0.5)
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 |
|---|---|---|
| 1.0.0 | 213 | 11/9/2025 |
v1.0.0:
- Initial release
- Multi-level caching (L1 Memory + L2 Distributed)
- Scoped cache contexts for logical partitioning
- Automatic GZIP compression support
- Dual serialization (System.Text.Json and Newtonsoft.Json)
- Built-in cache hit/miss/error metrics
- Thread-safe GetOrSet with semaphore locking
- Unified IEasyCache interface
- Support for Memory, Distributed (Redis/SQL), and Multi-level caches