Skip to content

Latest commit

 

History

History
244 lines (176 loc) · 8.03 KB

File metadata and controls

244 lines (176 loc) · 8.03 KB

AspNetCoreCacheKit

NuGet Publish to NuGet License: MIT GitHub Sponsors Changelog

A lightweight caching library for ASP.NET Core featuring group-based keys, per-entry and per-group duration, configurable via appsettings.json, and a DI-ready design that wraps IMemoryCache in a clean, testable abstraction.

Why AspNetCoreCacheKit? IMemoryCache is powerful but low-level. AspNetCoreCacheKit adds group-based key management, per-group and per-entry expiration, appsettings.json configuration, and a consistent type-safe API that makes caching easy to use and easy to mock in tests.


✨ Features

  • 🔑 Group-based keys — organise cache entries with prefixes like "users:123"
  • GetOrCreate and GetOrCreateAsync — read-through pattern out of the box
  • 🛡️ Stampede-safe — concurrent misses on the same key are serialized, so the factory runs once instead of once per caller
  • ⏱️ Per-group and per-entry duration — fine-grained expiration control
  • 🗑️ Delete — remove a single entry by key or group + key
  • Configuration validation with DataAnnotations
  • 📐 Nullable reference types and generic type-safe Set<T> support
  • 🎛️ appsettings.json configuration with sensible defaults
  • 🧪 DI-ready — register with one line, mock ICacheService in tests

📋 Requirements

Requirement Minimum version
.NET 10.0+
ASP.NET Core 10.0+

🚀 Installation

dotnet add package AspNetCoreCacheKit

🎯 Quick Start

1. Configure appsettings.json

{
  "CacheOptions": {
    "IsEnabled": true,
    "DurationMinutes": 60,
    "GroupDurations": {
      "users": 30,
      "tokens": 5,
      "countries": 1440
    }
  }
}

All duration values are expressed in minutes. GroupDurations is optional — groups without an entry use the global DurationMinutes.

2. Register services

// With appsettings.json
builder.Services.AddAspNetCoreCacheKit(builder.Configuration);

// Programmatically, no appsettings.json needed
builder.Services.AddAspNetCoreCacheKit(options =>
{
    options.DurationMinutes = 30;
    options.GroupDurations["users"] = 10;
});

// Without appsettings (uses defaults)
builder.Services.AddAspNetCoreCacheKit();

3. Inject and use

[ApiController]
public class UsersController : ControllerBase
{
    private readonly ICacheService _cache;

    public UsersController(ICacheService cache)
    {
        _cache = cache;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetUser(int id, CancellationToken ct)
    {
        // Uses GroupDurations["users"] = 30 min from appsettings
        var user = await _cache.GetOrCreateAsync(
            "users",
            id.ToString(),
            _ => GetUserFromDb(id),
            cancellationToken: ct);

        return user is null ? NotFound() : Ok(user);
    }
}

📚 API Reference

Note: key must not be null or empty on any method — ArgumentException is thrown otherwise. groupKey may be omitted or empty (falls back to the global duration).

Duration priority

Every method that writes to the cache resolves the expiration following this priority chain — first wins:

  1. duration parameter passed explicitly to the method
  2. GroupDurations[groupKey] configured in appsettings.json
  3. Global DurationMinutes from appsettings.json
// 1. Explicit duration wins — cached for 2 minutes regardless of everything else
await _cache.GetOrCreateAsync("tokens", userId, _ => GenerateTokenAsync(), TimeSpan.FromMinutes(2));

// 2. No explicit duration — uses GroupDurations["users"] = 30 min from appsettings
await _cache.GetOrCreateAsync("users", "42", _ => GetUserFromDb(42));

// 3. No explicit duration, no group config — uses global DurationMinutes = 60 min
await _cache.GetOrCreateAsync("misc", "key", _ => LoadSomethingAsync());

GetOrCreateAsync — async read-through

// With group — uses GroupDurations["users"] or global fallback
var user = await _cache.GetOrCreateAsync("users", "42", _ => GetUserFromDb(42), cancellationToken: ct);

// With group + explicit duration override
var token = await _cache.GetOrCreateAsync(
    "tokens", userId,
    _ => GenerateTokenAsync(userId),
    duration: TimeSpan.FromMinutes(5),
    ct);

// Without group key
var config = await _cache.GetOrCreateAsync("app:config", _ => LoadConfigAsync(), cancellationToken: ct);

GetOrCreate — sync read-through

// Uses GroupDurations["countries"] = 1440 min from appsettings
var countries = _cache.GetOrCreate("countries", () => LoadCountries());

// Explicit override
var result = _cache.GetOrCreate("countries", "IT", () => LoadItaly(), TimeSpan.FromHours(2));

Set — explicit write

_cache.Set("users", "42", user);                                   // group duration or global
_cache.Set("users", "42", user, TimeSpan.FromMinutes(10));         // explicit override

Delete — remove an entry

_cache.Delete("users", "42");
_cache.Delete("app:config");

⚙️ Configuration options

Option Type Default Description
IsEnabled bool true Enables or disables caching entirely. When false, factories are always invoked.
DurationMinutes int 60 Global default cache duration in minutes.
GroupDurations Dictionary<string, int> {} Per-group durations in minutes. Key = group name, Value = duration in minutes.

Setting IsEnabled: false is useful in development or testing environments where you want to bypass the cache without changing code.

Note: CacheOptions is bound once at startup (IOptions<CacheOptions>) and captured by the singleton ICacheService. Changes to appsettings.json at runtime require an application restart to take effect.


🧪 Testing

ICacheService is a plain interface — mock it directly in unit tests:

var cacheMock = new Mock<ICacheService>();

cacheMock
    .Setup(c => c.GetOrCreateAsync(
        "users", "1",
        It.IsAny<Func<ICacheEntry, Task<User>>>(),
        It.IsAny<TimeSpan?>(),
        It.IsAny<CancellationToken>()))
    .ReturnsAsync(new User { Id = 1, Name = "Simone" });

🧩 Part of the ASP.NET Core Kit ecosystem

AspNetCoreCacheKit is part of a cohesive set of lightweight, DI-ready toolkits for ASP.NET Core:

Package Description
AspNetCoreAuthKit JWT, API Key, Refresh Tokens with rotation & dynamic claim policies
AspNetCoreResponseKit Standardized ApiResponse<T>, global exception handling & smart factories
AspNetCoreHttpKit Typed HTTP client with result pattern & StructLog integration
AspNetCoreCacheKit Group-based caching with per-entry & per-group duration
StructLog Structured logging with EventCode support & custom enrichers

Each package works independently — use one or all five.


❤️ Support

If you find AspNetCoreCacheKit useful, consider sponsoring its development.

Sponsor simoneM93


📄 License

MIT — see LICENSE for details.