Skip to main content

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 the Clustron.Zaris.DistributedCache package.
  • HybridCache — a two-tier (local + distributed) cache with stampede protection and tag invalidation, via the Clustron.Zaris.HybridCache package.

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:

ParameterDefaultDescription
storeNameThe registered Zaris store the cache reads and writes.
configurenoneConfigure ClustronDistributedCacheOptions (see below).
setAsDefaultfalseForce 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:

OptionDefaultDescription
KeyPrefixhybrid: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. Expiration sets the L2 lifetime and LocalCacheExpiration sets 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 tags when writing, then call RemoveByTagAsync(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

UseWhen
IDistributedCacheYou need a single shared cache and want the standard interface — session state, response caching, or an existing IDistributedCache dependency.
HybridCacheYou want a local tier in front of the shared store for lower read latency, stampede protection, and tag-based invalidation.

Next steps