Persilsoft.Caching.Abstractions
1.0.0
dotnet add package Persilsoft.Caching.Abstractions --version 1.0.0
NuGet\Install-Package Persilsoft.Caching.Abstractions -Version 1.0.0
<PackageReference Include="Persilsoft.Caching.Abstractions" Version="1.0.0" />
<PackageVersion Include="Persilsoft.Caching.Abstractions" Version="1.0.0" />
<PackageReference Include="Persilsoft.Caching.Abstractions" />
paket add Persilsoft.Caching.Abstractions --version 1.0.0
#r "nuget: Persilsoft.Caching.Abstractions, 1.0.0"
#:package Persilsoft.Caching.Abstractions@1.0.0
#addin nuget:?package=Persilsoft.Caching.Abstractions&version=1.0.0
#tool nuget:?package=Persilsoft.Caching.Abstractions&version=1.0.0
Persilsoft.Caching.Abstractions
Core abstractions for building provider-agnostic distributed and in-memory caching solutions in .NET applications.
🎯 Overview
Persilsoft.Caching.Abstractions provides clean, well-designed interfaces and models that enable you to write caching code once and switch between different caching backends (Redis, Memcached, InMemory, etc.) without changing your application code.
Built following SOLID principles with a focus on dependency inversion and interface segregation.
✨ Features
- ✅ Provider-Agnostic - Write code once, switch implementations without changes
- ✅ Clean Interfaces -
ICacheProviderfor core operations,IRedisSetOperationsfor extended features - ✅ Flexible Expiration Policies - Absolute, relative, and sliding expiration via
CacheItemPolicy - ✅ Batch Operations - Efficient
GetManyandRemoveManyAsyncfor performance - ✅ Fully Async - Complete async/await support with
CancellationToken - ✅ Type-Safe - Generic methods for strongly-typed object caching
- ✅ Zero Dependencies - Pure abstraction with no external dependencies
- ✅ Extensible - Interface segregation allows provider-specific optimizations
📦 Installation
dotnet add package Persilsoft.Caching.Abstractions
Package Manager Console
Install-Package Persilsoft.Caching.Abstractions
.NET CLI
dotnet add package Persilsoft.Caching.Abstractions
🚀 Quick Start
1. Install an implementation
# Choose your caching provider
dotnet add package Persilsoft.Caching.Redis
# Or
dotnet add package Persilsoft.Caching.Memory
# Or
dotnet add package Persilsoft.Caching.Hybrid
2. Register services
using Persilsoft.Caching.Redis;
var builder = WebApplication.CreateBuilder(args);
// Register Redis implementation
builder.Services.AddRedisCaching(builder.Configuration);
var app = builder.Build();
app.Run();
3. Inject and use
using Persilsoft.Caching.Abstractions;
using Persilsoft.Caching.Abstractions.Models;
public class ProductService
{
private readonly ICacheProvider _cache;
public ProductService(ICacheProvider cache)
{
_cache = cache;
}
public async Task<Product?> GetProductAsync(int productId)
{
var key = $"product:{productId}";
// Try cache first
var cached = await _cache.GetAsync<Product>(key);
if (cached != null)
return cached;
// Cache miss - get from database
var product = await _db.Products.FindAsync(productId);
if (product != null)
{
// Cache for 1 hour
var policy = CacheItemPolicy.WithAbsoluteExpiration(TimeSpan.FromHours(1));
await _cache.SetAsync(key, product, policy);
}
return product;
}
}
📚 Core Interfaces
ICacheProvider
The main interface for cache operations, supported by all providers:
public interface ICacheProvider
{
// String operations
Task SetStringAsync(string key, string value,
CacheItemPolicy? policy = null,
CancellationToken cancellationToken = default);
Task<string?> GetStringAsync(string key,
CancellationToken cancellationToken = default);
// Object operations (JSON serialization)
Task SetAsync<T>(string key, T value,
CacheItemPolicy? policy = null,
CancellationToken cancellationToken = default);
Task<T?> GetAsync<T>(string key,
CancellationToken cancellationToken = default);
// Key management
Task<bool> RemoveAsync(string key,
CancellationToken cancellationToken = default);
Task<bool> ExistsAsync(string key,
CancellationToken cancellationToken = default);
Task<bool> SetExpirationAsync(string key, TimeSpan expiry,
CancellationToken cancellationToken = default);
// Batch operations
Task<IDictionary<string, string?>> GetManyAsync(IEnumerable<string> keys,
CancellationToken cancellationToken = default);
Task<long> RemoveManyAsync(IEnumerable<string> keys,
CancellationToken cancellationToken = default);
}
Total: 9 methods - All providers must implement these.
IRedisSetOperations
Extended interface for providers supporting Set operations (like Redis):
public interface IRedisSetOperations
{
Task<bool> SetAddAsync(string key, string value,
CancellationToken cancellationToken = default);
Task<IEnumerable<string>> SetMembersAsync(string key,
CancellationToken cancellationToken = default);
Task<bool> SetRemoveAsync(string key, string value,
CancellationToken cancellationToken = default);
Task<long> SetLengthAsync(string key,
CancellationToken cancellationToken = default);
Task<bool> SetContainsAsync(string key, string value,
CancellationToken cancellationToken = default);
}
Total: 5 methods - Only for providers that support Set data structures.
CacheItemPolicy
Flexible expiration policies for cache entries:
public class CacheItemPolicy
{
// Absolute expiration at specific time
public DateTimeOffset? AbsoluteExpiration { get; set; }
// Relative expiration after duration
public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; }
// Sliding expiration (renews on access)
public TimeSpan? SlidingExpiration { get; set; }
// Priority for eviction
public CacheItemPriority Priority { get; set; }
// Helper methods
public static CacheItemPolicy WithAbsoluteExpiration(TimeSpan expiration);
public static CacheItemPolicy WithSlidingExpiration(TimeSpan expiration);
}
public enum CacheItemPriority
{
Low,
Normal,
High,
NeverRemove
}
💡 Usage Examples
Example 1: Basic Caching (Cache-Aside Pattern)
public class UserService
{
private readonly ICacheProvider _cache;
private readonly IDbContext _db;
public UserService(ICacheProvider cache, IDbContext db)
{
_cache = cache;
_db = db;
}
public async Task<User?> GetUserAsync(int userId)
{
var key = $"user:{userId}";
// Check cache
var cached = await _cache.GetAsync<User>(key);
if (cached != null)
return cached;
// Cache miss - query database
var user = await _db.Users.FindAsync(userId);
if (user != null)
{
// Cache for 1 hour
var policy = CacheItemPolicy.WithAbsoluteExpiration(
TimeSpan.FromHours(1));
await _cache.SetAsync(key, user, policy);
}
return user;
}
public async Task UpdateUserAsync(User user)
{
// Update database
await _db.SaveChangesAsync();
// Invalidate cache
await _cache.RemoveAsync($"user:{user.Id}");
}
}
Example 2: Sliding Expiration (Session Cache)
public class SessionService
{
private readonly ICacheProvider _cache;
public async Task<UserSession?> GetSessionAsync(string sessionId)
{
var key = $"session:{sessionId}";
var session = await _cache.GetAsync<UserSession>(key);
if (session != null)
{
// Renew sliding expiration on each access
var policy = CacheItemPolicy.WithSlidingExpiration(
TimeSpan.FromMinutes(30));
await _cache.SetAsync(key, session, policy);
}
return session;
}
public async Task CreateSessionAsync(UserSession session)
{
var key = $"session:{session.SessionId}";
// 30 minutes of inactivity before expiration
var policy = CacheItemPolicy.WithSlidingExpiration(
TimeSpan.FromMinutes(30));
await _cache.SetAsync(key, session, policy);
}
}
Example 3: Batch Operations
public class ProductCatalogService
{
private readonly ICacheProvider _cache;
private readonly IDbContext _db;
public async Task<List<Product>> GetProductsAsync(List<int> productIds)
{
// Generate keys
var keys = productIds.Select(id => $"product:{id}").ToList();
// Get all from cache in one operation
var cachedItems = await _cache.GetManyAsync(keys);
var products = new List<Product>();
var missingIds = new List<int>();
for (int i = 0; i < productIds.Count; i++)
{
var key = keys[i];
if (cachedItems.TryGetValue(key, out var json) && json != null)
{
var product = JsonSerializer.Deserialize<Product>(json);
if (product != null)
products.Add(product);
}
else
{
missingIds.Add(productIds[i]);
}
}
// Load missing products
if (missingIds.Any())
{
var dbProducts = await _db.Products
.Where(p => missingIds.Contains(p.Id))
.ToListAsync();
// Cache them for next time
foreach (var product in dbProducts)
{
var policy = CacheItemPolicy.WithAbsoluteExpiration(
TimeSpan.FromHours(1));
await _cache.SetAsync($"product:{product.Id}", product, policy);
products.Add(product);
}
}
return products;
}
public async Task InvalidateProductsAsync(List<int> productIds)
{
var keys = productIds.Select(id => $"product:{id}");
await _cache.RemoveManyAsync(keys);
}
}
Example 4: Using Redis-Specific Sets (Optional)
public class OnlineUsersService
{
private readonly ICacheProvider _cache;
private readonly IRedisSetOperations? _redisOps;
public OnlineUsersService(ICacheProvider cache)
{
_cache = cache;
// Detect if provider supports Set operations
_redisOps = cache as IRedisSetOperations;
}
public async Task AddUserOnlineAsync(string userId)
{
if (_redisOps != null)
{
// Use efficient Redis Sets
await _redisOps.SetAddAsync("users:online", userId);
}
else
{
// Fallback for other providers
await _cache.SetStringAsync($"user:online:{userId}", "1",
CacheItemPolicy.WithSlidingExpiration(TimeSpan.FromMinutes(30)));
}
}
public async Task<IEnumerable<string>> GetOnlineUsersAsync()
{
if (_redisOps == null)
throw new NotSupportedException(
"Online users tracking requires a provider with Set support");
return await _redisOps.SetMembersAsync("users:online");
}
public async Task<bool> IsUserOnlineAsync(string userId)
{
if (_redisOps != null)
{
return await _redisOps.SetContainsAsync("users:online", userId);
}
else
{
return await _cache.ExistsAsync($"user:online:{userId}");
}
}
}
Example 5: Different Expiration Strategies
public class CacheStrategyExamples
{
private readonly ICacheProvider _cache;
// Absolute expiration - expires at specific time
public async Task CacheWithAbsoluteExpirationAsync()
{
var policy = new CacheItemPolicy
{
AbsoluteExpiration = DateTimeOffset.UtcNow.AddHours(2)
};
await _cache.SetAsync("key", data, policy);
}
// Relative expiration - expires after duration
public async Task CacheWithRelativeExpirationAsync()
{
var policy = CacheItemPolicy.WithAbsoluteExpiration(
TimeSpan.FromMinutes(30));
await _cache.SetAsync("key", data, policy);
}
// Sliding expiration - renews on access
public async Task CacheWithSlidingExpirationAsync()
{
var policy = CacheItemPolicy.WithSlidingExpiration(
TimeSpan.FromMinutes(15));
await _cache.SetAsync("key", data, policy);
}
// With priority
public async Task CacheWithPriorityAsync()
{
var policy = new CacheItemPolicy
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1),
Priority = CacheItemPriority.High
};
await _cache.SetAsync("key", data, policy);
}
// No expiration
public async Task CacheForeverAsync()
{
await _cache.SetAsync("key", data); // No policy = no expiration
}
}
🔌 Available Implementations
🏗️ Design Principles
This library follows SOLID principles:
Single Responsibility
Each interface has a specific, well-defined purpose:
ICacheProvider- Core caching operationsIRedisSetOperations- Redis-specific Set operationsCacheItemPolicy- Expiration policy configuration
Open/Closed
- Open for extension - Create new providers by implementing interfaces
- Closed for modification - Abstractions remain stable
Liskov Substitution
All ICacheProvider implementations are interchangeable:
// Switch from Redis to Memory without code changes
// builder.Services.AddRedisCaching(config);
builder.Services.AddMemoryCaching(config);
Interface Segregation
Core operations separated from provider-specific features:
- All providers implement
ICacheProvider(9 methods) - Only Redis implements
IRedisSetOperations(5 methods) - Consumers choose what they need
Dependency Inversion
Depend on abstractions, not implementations:
// ✅ Good - Depends on abstraction
public class MyService
{
private readonly ICacheProvider _cache;
public MyService(ICacheProvider cache) => _cache = cache;
}
// ❌ Bad - Depends on concrete implementation
public class MyService
{
private readonly RedisCacheProvider _cache;
public MyService(RedisCacheProvider cache) => _cache = cache;
}
✅ Benefits
| Benefit | Description |
|---|---|
| Testability | Easy to mock ICacheProvider in unit tests |
| Flexibility | Switch from Redis to Memcached without code changes |
| Composability | Combine implementations (e.g., hybrid cache with L1/L2) |
| Type Safety | Compile-time checks with generic methods |
| Performance | Batch operations reduce network round trips |
| Future-Proof | Add new providers without breaking existing code |
| Best Practices | Follows industry-standard caching patterns |
🧪 Testing
Easy to mock in unit tests:
using Moq;
using Xunit;
public class ProductServiceTests
{
[Fact]
public async Task GetProduct_WhenCached_ReturnsCachedValue()
{
// Arrange
var mockCache = new Mock<ICacheProvider>();
var expectedProduct = new Product { Id = 1, Name = "Test" };
mockCache
.Setup(c => c.GetAsync<Product>(
"product:1",
It.IsAny<CancellationToken>()))
.ReturnsAsync(expectedProduct);
var service = new ProductService(mockCache.Object, null);
// Act
var result = await service.GetProductAsync(1);
// Assert
Assert.Equal(expectedProduct.Id, result.Id);
mockCache.Verify(
c => c.GetAsync<Product>("product:1", It.IsAny<CancellationToken>()),
Times.Once);
}
}
📖 Documentation
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🔗 Related Packages
- Persilsoft.Caching.Redis - Redis implementation
- Persilsoft.Storage.Abstractions - Object storage abstractions
- Persilsoft.DRM.Abstractions - DRM abstractions
💬 Support
- 📧 Email: support@persilsoft.com
- 🐛 Issues: GitHub Issues
- 💡 Discussions: GitHub Discussions
🙏 Acknowledgments
- Inspired by
Microsoft.Extensions.Caching.Abstractions - Built with ❤️ for the .NET community
Made with ❤️ by Persilsoft
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Persilsoft.Caching.Abstractions:
| Package | Downloads |
|---|---|
|
Persilsoft.Caching.Redis
High-performance Redis implementation for Persilsoft.Caching.Abstractions with comprehensive feature support and flexible configuration options. Provides distributed caching with Redis including string/object operations, batch operations (GetMany/RemoveMany), and Redis-specific Set operations. Built on StackExchange.Redis with integrated logging and robust error handling. Key Features: • Complete ICacheProvider implementation - All 9 core caching methods • IRedisSetOperations support - 5 Redis Set operations (SetAdd, SetMembers, SetRemove, SetLength, SetContains) • Flexible configuration - 5 different configuration methods (connection string, IConfiguration, ConfigurationOptions, factory, Action) • Multi-tenant support - InstanceName prefixing for isolated caches • Batch operations - GetMany/RemoveMany for improved performance • JSON serialization - Automatic serialization for complex objects • Integrated logging - Comprehensive ILogger support • Argument validation - Robust error handling with ArgumentNullException • Production-ready - Battle-tested in enterprise environments Perfect for building scalable distributed caching solutions with Redis in .NET applications. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 266 | 12/14/2025 |
v1.0.0 - Initial Release (December 2025)
Core Features:
• ICacheProvider interface with 9 essential methods
- SetStringAsync/GetStringAsync for string operations
- SetAsync<T>/GetAsync<T> for object operations with JSON serialization
- RemoveAsync, ExistsAsync, SetExpirationAsync for key management
- GetManyAsync, RemoveManyAsync for batch operations
• IRedisSetOperations interface for provider-specific features
- SetAddAsync, SetMembersAsync, SetRemoveAsync
- SetLengthAsync, SetContainsAsync
- Enables Redis-specific optimizations while maintaining abstraction
• CacheItemPolicy with flexible expiration strategies
- AbsoluteExpiration - Expires at specific time
- AbsoluteExpirationRelativeToNow - Expires after duration
- SlidingExpiration - Renews on access
- CacheItemPriority for eviction policies
Design Principles:
• Dependency Inversion - Depend on abstractions, not implementations
• Interface Segregation - Core operations separated from provider-specific
• Open/Closed - Open for extension, closed for modification
• Zero dependencies - No external packages required
Compatibility:
• .NET 10.0+
• Fully async/await with CancellationToken support
• Nullable reference types enabled
Next Steps:
• Install Persilsoft.Caching.Redis for Redis implementation
• Install Persilsoft.Caching.Memory for in-memory implementation (coming soon)
• Install Persilsoft.Caching.Hybrid for two-tier caching (coming soon)