Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/masastack/MASA.Framework/llms.txt

Use this file to discover all available pages before exploring further.

MASA Framework’s caching layer sits above IDistributedCache and provides a richer API: typed key formatting, pattern-based queries, pub/sub channel support, atomic increment/decrement operations, and a two-tier multi-level cache that combines in-memory L1 with a distributed L2 backend. Redis is the primary supported backend via StackExchange.Redis.

Installation

# Distributed cache with Redis backend
dotnet add package Masa.Contrib.Caching.Distributed.StackExchangeRedis

# Multi-level cache (memory + distributed)
dotnet add package Masa.Contrib.Caching.MultilevelCache

Registration

Distributed Cache (Redis)

builder.Services.AddDistributedCache(options =>
    options.UseStackExchangeRedisCache("localhost:6379"));
Pass a full StackExchange.Redis configuration string, or provide a ConfigurationOptions object for advanced settings:
builder.Services.AddDistributedCache(options =>
    options.UseStackExchangeRedisCache(redisOptions =>
    {
        redisOptions.Servers = new List<RedisServerOptions>
        {
            new() { Host = "localhost", Port = 6379 }
        };
        redisOptions.DefaultDatabase = 1;
    }));

Multi-Level Cache

builder.Services.AddMultilevelCache(distributedCacheOptions =>
    distributedCacheOptions.UseStackExchangeRedisCache("localhost:6379"));
The multi-level cache automatically synchronises L1 (memory) eviction across instances using a Redis pub/sub channel, ensuring all nodes see consistent data after an invalidation.

IDistributedCacheClient API Reference

Get

// Single key
T? Get<T>(string key);
Task<T?> GetAsync<T>(string key, CancellationToken token = default);

// Multiple keys — returns list preserving input order (null for missing keys)
IEnumerable<T?> GetList<T>(IEnumerable<string> keys);
Task<IEnumerable<T?>> GetListAsync<T>(IEnumerable<string> keys, ...);

Get-or-Set

GetOrSet atomically returns a cached value or populates the cache by invoking the factory delegate:
T? GetOrSet<T>(string key, Func<CacheEntry<T>> setter);
Task<T?> GetOrSetAsync<T>(string key, Func<CacheEntry<T>> setter, ...);
CacheEntry<T> wraps the value with optional expiry:
new CacheEntry<T>(value)                                 // no expiry
new CacheEntry<T>(value, TimeSpan.FromMinutes(10))       // sliding expiry
new CacheEntry<T>(value, DateTimeOffset.UtcNow.AddHours(1)) // absolute expiry

Set

void Set<T>(string key, T value, CacheEntryOptions? options = null);
Task SetAsync<T>(string key, T value, CacheEntryOptions? options = null, ...);

// Bulk set
void SetList<T>(Dictionary<string, T> keyValues, CacheEntryOptions? options = null);
Task SetListAsync<T>(Dictionary<string, T> keyValues, ...);

Remove and Refresh

// Remove one or more keys
void Remove(params string[] keys);
Task RemoveAsync(params string[] keys, ...);

// Reset the sliding expiry window
void Refresh(params string[] keys);
Task RefreshAsync(params string[] keys, ...);

Key Existence and Pattern Queries

bool Exists(string key);
bool Exists<T>(string key, Action<CacheOptions>? options = null);
Task<bool> ExistsAsync<T>(string key, ...);

// Find all keys matching a Redis glob pattern
IEnumerable<string> GetKeys(string keyPattern);
IEnumerable<string> GetKeys<T>(string keyPattern, Action<CacheOptions>? options = null);
Task<IEnumerable<string>> GetKeysAsync(string keyPattern, ...);

// Fetch values for all keys matching a pattern
IEnumerable<T?> GetByKeyPattern<T>(string keyPattern);
Task<IEnumerable<T?>> GetByKeyPatternAsync<T>(string keyPattern, ...);

Pub/Sub

MASA caching exposes Redis channels as first-class operations:
// Publish a message to a channel
void Publish(string channel, Action<PublishOptions> options);
Task PublishAsync(string channel, Action<PublishOptions> options, ...);

// Subscribe to a channel
void Subscribe<T>(string channel, Action<SubscribeOptions<T>> options);
Task SubscribeAsync<T>(string channel, Action<SubscribeOptions<T>> options, ...);

// Unsubscribe
void UnSubscribe<T>(string channel);
Task UnSubscribeAsync<T>(string channel, ...);

Atomic Increment and Decrement

// Increment — creates the key with `value` if absent
Task<long> HashIncrementAsync(string key, long value = 1,
    Action<CacheOptions>? options = null, ...);

// Decrement — never goes below `defaultMinVal` (default 0)
Task<long> HashDecrementAsync(string key, long value = 1,
    long defaultMinVal = 0, Action<CacheOptions>? options = null, ...);

Key Expiry

void KeyExpire(string key, TimeSpan? absoluteExpiration);
void KeyExpire<T>(string key, DateTimeOffset absoluteExpiration,
    Action<CacheOptions>? options = null);
Task KeyExpireAsync(string key, CacheEntryOptions? options, ...);
// ... several additional overloads for TimeSpan and DateTimeOffset

Cache Key Formatting

By default, MASA prepends a type-based prefix to every key, preventing collisions between different cached types stored in the same Redis database. CacheKeyType controls the format:
ValueKey format
TypeName (default){FullTypeName}_{key}
TypeAlias{ShortAlias}_{key}
None{key} (no prefix)
Override formatting per-call using the Action<CacheOptions> parameter:
await _cache.GetAsync<Product>(id, options =>
    options.CacheKeyType = CacheKeyType.None);

Usage Examples

GetOrSet, Invalidation, and Atomic Counter

public class ProductService
{
    private readonly IDistributedCacheClient _cache;

    public ProductService(IDistributedCacheClient cache) => _cache = cache;

    public async Task<Product?> GetProductAsync(Guid id)
        => await _cache.GetOrSetAsync<Product>(
            id.ToString(),
            () => new CacheEntry<Product>(
                FetchFromDb(id), TimeSpan.FromMinutes(10)));

    public async Task InvalidateAsync(Guid id)
        => await _cache.RemoveAsync(id.ToString());

    public async Task<long> IncrementViewCountAsync(string productId)
        => await _cache.HashIncrementAsync($"views:{productId}");

    public async Task<long> DecrementStockAsync(string productId, long qty)
        => await _cache.HashDecrementAsync(
            $"stock:{productId}", qty, defaultMinVal: 0);
}

Pub/Sub for Cache Invalidation

// Publisher — invalidate a product across all instances
await _cache.PublishAsync("product-invalidated", options =>
    options.Value = productId.ToString());

// Subscriber — typically in a background service or startup
await _cache.SubscribeAsync<string>("product-invalidated", options =>
{
    options.Subscribe = async (productId) =>
    {
        await _cache.RemoveAsync(productId);
    };
});

Pattern-Based Query

// Remove all cached product pages for a category
var keys = await _cache.GetKeysAsync<Product>("category:electronics:*");
await _cache.RemoveAsync(keys.ToArray());

Multi-Level Cache (IMultilevelCacheClient)

IMultilevelCacheClient is a drop-in replacement for IDistributedCacheClient that adds an in-process memory layer:
public class CatalogService
{
    private readonly IMultilevelCacheClient _cache;

    public CatalogService(IMultilevelCacheClient cache) => _cache = cache;

    public async Task<Category?> GetCategoryAsync(int id)
        => await _cache.GetOrSetAsync<Category>(
            id.ToString(),
            () => new CacheEntry<Category>(
                LoadCategory(id), TimeSpan.FromHours(1)));
}
On a cache hit the value is served from L1 memory without a Redis round-trip. On a miss the value is loaded from Redis (L2) and promoted to memory. When a key is removed or updated on any node, MASA publishes an invalidation message to the shared pub/sub channel so every node evicts its local copy immediately.

Memory Cache Options

Tune the L1 layer at registration:
builder.Services.AddMultilevelCache(
    memoryCacheOptions =>
    {
        memoryCacheOptions.ExpirationScanFrequency = TimeSpan.FromMinutes(1);
        memoryCacheOptions.SizeLimit = 1024;
    },
    distributedCacheOptions =>
        distributedCacheOptions.UseStackExchangeRedisCache("localhost:6379"));

Build docs developers (and LLMs) love