Use Zaris as an ASP.NET Core cache
Clustron Zaris ships two adapters that plug into the standard ASP.NET Core caching abstractions, so you can use a Zaris store wherever your application already expects a cache:
IDistributedCache— a drop-in distributed cache, via theClustron.Zaris.DistributedCachepackage.HybridCache— a two-tier (local + distributed) cache with stampede protection and tag invalidation, via theClustron.Zaris.HybridCachepackage.
Both adapters run on top of a normal Zaris client, so you register a store first and then point the adapter at it by name.
IDistributedCache
IDistributedCache is the interface ASP.NET Core uses for response caching, session state, and any AddStackExchangeRedisCache-style scenario. The Zaris adapter registers an implementation backed by a store.
Register it
Register a Zaris store, then add the distributed-cache adapter for that store:
using Clustron.Zaris.Client.DependencyInjection;
using Clustron.Extensions.Caching.Distributed;
builder.Services.AddClustronZaris("cache", "zaris://10.0.0.11:7861/cache");
builder.Services.AddClustronDistributedCache("cache");
AddClustronDistributedCache takes the store name and two optional arguments:
| Parameter | Default | Description |
|---|---|---|
storeName | — | The registered Zaris store the cache reads and writes. |
configure | none | Configure ClustronDistributedCacheOptions (see below). |
setAsDefault | false | Force this cache to be the application's IDistributedCache. If no default exists yet, the first registration becomes the default regardless. |
ClustronDistributedCacheOptions currently exposes one setting:
| Option | Default | Description |
|---|---|---|
KeyPrefix | hybrid: | A prefix applied to every cache key stored in Zaris, so cache entries are namespaced within the store. |
builder.Services.AddClustronDistributedCache("cache", options =>
{
options.KeyPrefix = "web:";
}, setAsDefault: true);
Use it
Inject IDistributedCache and use it exactly as you would any other implementation. Expiration is honored through DistributedCacheEntryOptions:
public sealed class QuotesController(IDistributedCache cache)
{
public async Task<string> GetQuoteAsync(string symbol)
{
var cached = await cache.GetStringAsync(symbol);
if (cached is not null)
return cached;
var quote = await FetchQuoteAsync(symbol);
await cache.SetStringAsync(symbol, quote, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30)
});
return quote;
}
}
HybridCache
HybridCache (from Microsoft.Extensions.Caching.Hybrid) combines a fast local tier with a shared distributed tier and adds stampede protection — concurrent requests for the same missing key wait for a single factory call rather than all recomputing it. The Zaris adapter implements HybridCache directly, using two Zaris stores: an L1 store (local, fast) and an L2 store (shared, distributed).
Register it
Register both stores, then add the hybrid cache with their names. Each store is a connection string; a common pairing is an in-process L1 (zaris://inproc/...) and a remote L2:
using Clustron.Zaris.Client.DependencyInjection;
using Clustron.Zaris.HybridCache;
builder.Services.AddClustronZaris("l1", "zaris://inproc/l1");
builder.Services.AddClustronZaris("l2", "zaris://10.0.0.11:7861/l2");
builder.Services.AddClustronHybridCache(l1Store: "l1", l2Store: "l2");
Use it
Inject HybridCache and call GetOrCreateAsync — the factory runs only on a miss, and only once even under concurrent access:
public sealed class ProductService(HybridCache cache)
{
public ValueTask<Product> GetProductAsync(int id) =>
cache.GetOrCreateAsync(
$"product:{id}",
id,
async (productId, ct) => await LoadFromDatabaseAsync(productId, ct),
new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(10), // L2 lifetime
LocalCacheExpiration = TimeSpan.FromMinutes(1) // L1 lifetime
});
}
Behavior to know:
- Read path. A read checks L1 first; on a miss it takes a per-key lock, re-checks L1, then checks L2, and only calls your factory if both miss. A value found in L2 is promoted back into L1.
- Expiration.
Expirationsets the L2 lifetime andLocalCacheExpirationsets the L1 lifetime. The L1 lifetime is capped to the L2 lifetime, and the default when you pass no options is 5 minutes. - Tag invalidation. Pass
tagswhen writing, then callRemoveByTagAsync(tag)to invalidate every entry carrying that tag. Invalidation is recorded in L2 and checked on read, so a tagged entry created before the invalidation is treated as stale. - Removal.
RemoveAsync(key)deletes the entry from both tiers.
Choosing between them
| Use | When |
|---|---|
IDistributedCache | You need a single shared cache and want the standard interface — session state, response caching, or an existing IDistributedCache dependency. |
HybridCache | You want a local tier in front of the shared store for lower read latency, stampede protection, and tag-based invalidation. |