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?
IMemoryCacheis powerful but low-level. AspNetCoreCacheKit adds group-based key management, per-group and per-entry expiration,appsettings.jsonconfiguration, and a consistent type-safe API that makes caching easy to use and easy to mock in tests.
- 🔑 Group-based keys — organise cache entries with prefixes like
"users:123" - ⚡
GetOrCreateandGetOrCreateAsync— read-through pattern out of the box - ⏱️ 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.jsonconfiguration with sensible defaults - 🧪 DI-ready — register with one line, mock
ICacheServicein tests
| Requirement | Minimum version |
|---|---|
| .NET | 9.0+ |
| ASP.NET Core | 9.0+ |
dotnet add package AspNetCoreCacheKit{
"CacheOptions": {
"IsEnabled": true,
"DurationMinutes": 60,
"GroupDurations": {
"users": 30,
"tokens": 5,
"countries": 1440
}
}
}All duration values are expressed in minutes.
GroupDurationsis optional — groups without an entry use the globalDurationMinutes.
// With appsettings.json
builder.Services.AddAspNetCoreCacheKit(builder.Configuration);
// Without appsettings (uses defaults)
builder.Services.AddAspNetCoreCacheKit();[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),
ct: ct);
return user is null ? NotFound() : Ok(user);
}
}Every method that writes to the cache resolves the expiration following this priority chain — first wins:
durationparameter passed explicitly to the methodGroupDurations[groupKey]configured inappsettings.json- Global
DurationMinutesfromappsettings.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());// With group — uses GroupDurations["users"] or global fallback
var user = await _cache.GetOrCreateAsync("users", "42", _ => GetUserFromDb(42), ct: 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(), ct: ct);// 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));_cache.Set("users", "42", user); // group duration or global
_cache.Set("users", "42", user, TimeSpan.FromMinutes(10)); // explicit override_cache.Delete("users", "42");
_cache.Delete("app:config");| 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.
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" });If you find AspNetCoreCacheKit useful, consider sponsoring its development.
MIT — see LICENSE for details.