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
                    
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="Persilsoft.Caching.Abstractions" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Persilsoft.Caching.Abstractions" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Persilsoft.Caching.Abstractions" />
                    
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 Persilsoft.Caching.Abstractions --version 1.0.0
                    
#r "nuget: Persilsoft.Caching.Abstractions, 1.0.0"
                    
#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 Persilsoft.Caching.Abstractions@1.0.0
                    
#: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=Persilsoft.Caching.Abstractions&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Persilsoft.Caching.Abstractions&version=1.0.0
                    
Install as a Cake Tool

Persilsoft.Caching.Abstractions

NuGet Version NuGet Downloads License: MIT .NET

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 - ICacheProvider for core operations, IRedisSetOperations for extended features
  • Flexible Expiration Policies - Absolute, relative, and sliding expiration via CacheItemPolicy
  • Batch Operations - Efficient GetMany and RemoveManyAsync for 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

Package Description NuGet
Persilsoft.Caching.Redis Redis implementation with Set operations NuGet
Persilsoft.Caching.Memory In-memory implementation Coming soon
Persilsoft.Caching.Hybrid Two-tier (Memory + Redis) Coming soon
Persilsoft.Caching.Memcached Memcached implementation Coming soon

🏗️ Design Principles

This library follows SOLID principles:

Single Responsibility

Each interface has a specific, well-defined purpose:

  • ICacheProvider - Core caching operations
  • IRedisSetOperations - Redis-specific Set operations
  • CacheItemPolicy - 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.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

💬 Support

🙏 Acknowledgments

  • Inspired by Microsoft.Extensions.Caching.Abstractions
  • Built with ❤️ for the .NET community

Made with ❤️ by Persilsoft

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