Skip to main content

Caching

Vali-Mediator.Caching adds declarative pipeline caching to any IRequest<T> without modifying handler code — including automatic coalescing of concurrent cache misses so a stampede of identical requests only runs the handler once.

Installation​

dotnet add package Vali-Mediator.Caching

Setup​

builder.Services.AddValiMediator(config =>
{
config.RegisterServicesFromAssemblyContaining<Program>();
config.AddCachingBehavior();
});

// Register the cache store
builder.Services.AddInMemoryCacheStore();

How It Works​

loading...

Quick Example​

// Mark request as cacheable
public record GetProductQuery(Guid Id)
: IRequest<Result<ProductDto>>, ICacheable
{
public string CacheKey => $"product:{Id}";
public TimeSpan? AbsoluteExpiration => TimeSpan.FromMinutes(10);
public TimeSpan? SlidingExpiration => null;
public string? CacheGroup => "products";
public bool BypassCache => false;
public CacheOrder Order => CacheOrder.ReadThenWrite;
}

The handler needs no changes — caching is applied transparently by the pipeline behavior.

Key Concepts​

ConceptDescription
ICacheableMarks a request as cacheable; provides cache key and expiration
IInvalidatesCacheMarks a request that invalidates cached entries on execution
ICacheStoreAbstraction for the cache backend (in-memory, Redis, etc.)
CacheOrderControls check-then-store vs store-only vs check-only
CacheGroupGroups related cache entries for bulk invalidation

Cache Store Options​

StorePackageUse Case
InMemoryCacheStoreBuilt-inDevelopment, single-node
RedisImplement ICacheStoreProduction, distributed
SQL ServerImplement ICacheStoreDurable caching

Coalescing Concurrent Misses​

When several requests ask for the same cache key at the same time and it's a miss, only the first one runs the handler — the rest await that same in-flight call instead of each hitting the backing store (and, transitively, the database). This is automatic whenever AddCachingBehavior() is registered; the only knob is how long a follower waits before giving up and running the handler itself:

builder.Services.AddCachingOptions(options =>
{
options.CoalescingWaitTimeout = TimeSpan.FromSeconds(30); // bounds the damage of a hung handler
});

In-Memory Store Limits​

Every limit is validated (must be greater than zero) and bounds memory even when keys/groups come from client-controlled input — an oversized key or a group past the limit is dropped instead of accepted:

builder.Services.AddInMemoryCacheStore(options =>
{
options.MaxEntries = 10_000; // least-recently-used entry evicted when full
options.MaxKeyLength = 512; // longer keys/group names are never stored
options.MaxGroups = 10_000; // distinct groups indexed at once
options.MaxKeysPerGroup = 10_000; // keys indexed under one group
options.CleanupInterval = TimeSpan.FromMinutes(5);
});