Skip to main content

distributed-caching-and-locking

Typed distributed caching and cross-instance distributed locking. USE FOR: typed IDistributedCache / GetOrAddAsync (Redis-backed), IAbpDistributedLock.TryAcquireAsync, AbpDistributedCacheOptions / [CacheName]. DO NOT USE FOR: per-entity caching (cache-entities); multi-tier hybrid cache (use-hybrid-caching); events, background jobs, or workers (background-jobs-and-events); settings/features (manage-settings-and-features); data filters (apply-data-filters).

Jump to install

Source facts

Repository
abpframework/abp-skills
Last source activity
July 20, 2026 at 03:24
Detected SKILL.md language
English
Stars
18
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
distributed-caching-and-locking
description
Typed distributed caching and cross-instance distributed locking. USE FOR: typed IDistributedCache / GetOrAddAsync (Redis-backed), IAbpDistributedLock.TryAcquireAsync, AbpDistributedCacheOptions / [CacheName]. DO NOT USE FOR: per-entity caching (cache-entities); multi-tier hybrid cache (use-hybrid-caching); events, background jobs, or workers (background-jobs-and-events); settings/features (manage-settings-and-features); data filters (apply-data-filters).
license
MIT
# Distributed Caching & Locking (ABP) Guidance for ABP's typed distributed cache and distributed lock, plus a short pointer on data filters, settings, and features. All APIs below are verified against ABP source. Inject everything via constructor. ## When to Use - Cache typed values across app instances with `IDistributedCache<TCacheItem>` (Redis-backed). - Use `GetOrAddAsync` to return a cached value or compute-and-store it on a miss. - Coordinate a critical section so only one app instance runs it at a time (`IAbpDistributedLock`). ## When Not to Use - **Publishing/handling events, deferred jobs, or recurring workers** — use the **background-jobs-and-events** skill. - **Defining and checking settings or features in full** — use the **manage-settings-and-features** skill. The settings/features notes below are only a pointer. - **Data-filter details beyond the brief mention** — use the **apply-data-filters** skill. ## How it works ### Typed distributed cache `IDistributedCache<TCacheItem>` (namespace `Volo.Abp.Caching`) is a typed wrapper over `Microsoft.Extensions.Caching.Distributed.IDistributedCache`. `TCacheItem` is a serializable class (`where TCacheItem : class`); ABP handles JSON serialization, tenant-scoped keys, and a per-type cache name automatically. The default key is a `string`; a two-generic overload `IDistributedCache<TCacheItem, TCacheKey>` supports typed keys. Define a cache item as a plain class: ```csharp public class ProductCacheItem { public string Name { get; set; } public decimal Price { get; set; } } ``` Inject and use it. `GetOrAddAsync` is the go-to method — it returns the cached value or runs your factory to produce and store it: ```csharp public class ProductAppService : ApplicationService { private readonly IDistributedCache<ProductCacheItem> _cache; private readonly IRepository<Product, Guid> _repo; public ProductAppService( IDistributedCache<ProductCacheItem> cache, IRepository<Product, Guid> repo) { _cache = cache; _repo = repo; } public async Task<ProductCacheItem?> GetAsync(Guid id) { return await _cache.GetOrAddAsync( id.ToString(), async () => { var product = await _repo.GetAsync(id); return new ProductCacheItem { Name = product.Name, Price = product.Price }; }, () => new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) }); } } ``` Verified method signatures: ```csharp Task<TCacheItem?> GetAsync(TCacheKey key, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default); Task SetAsync(TCacheKey key, TCacheItem value, DistributedCacheEntryOptions? options = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default); Task<TCacheItem?> GetOrAddAsync(TCacheKey key, Func<Task<TCacheItem>> factory, Func<DistributedCacheEntryOptions>? optionsFactory = null, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default); Task RemoveAsync(TCacheKey key, bool? hideErrors = null, bool considerUow = false, CancellationToken token = default); Task RefreshAsync(TCacheKey key, bool? hideErrors = null, CancellationToken token = default); ``` Notes: - `hideErrors` defaults to the global setting (`true`): cache errors are logged, not thrown, so a Redis outage degrades to source reads rather than failing requests. - `AbpDistributedCacheOptions` (configure in a module's `Configure<...>`) exposes `GlobalCacheEntryOptions`, `KeyPrefix`, and `HideErrors`. - The cache name defaults to the full (namespace-qualified) type name with the `CacheItem` suffix stripped (e.g. `MyApp.Books.BookCacheItem` → `MyApp.Books.Book`); override it with `[CacheName("...")]` on the item class. #### Redis provider The in-memory `IDistributedCache` is per-instance and not shared. For a real distributed cache add the `Volo.Abp.Caching.StackExchangeRedis` package, depend on `AbpCachingStackExchangeRedisModule`, and set the `Redis` connection string in `appsettings.json`. The typed `IDistributedCache<T>` API stays identical. ### Distributed lock `IAbpDistributedLock` (namespace `Volo.Abp.DistributedLocking`) coordinates a critical section across multiple app instances — only one holder at a time for a given lock name. The default implementation (`MedallionAbpDistributedLock`) wraps the Medallion DistributedLock library. ```csharp Task<IAbpDistributedLockHandle?> TryAcquireAsync( string name, TimeSpan timeout = default, CancellationToken cancellationToken = default); ``` `TryAcquireAsync` returns an `IAbpDistributedLockHandle` (which is `IAsyncDisposable`) when the lock is taken, or **`null`** when it could not be acquired within `timeout` (default `0` = don't wait). Always check for null and dispose the handle with `await using`: ```csharp public class ReportGenerator : ITransientDependency { private readonly IAbpDistributedLock _distributedLock; public ReportGenerator(IAbpDistributedLock distributedLock) => _distributedLock = distributedLock; public async Task GenerateAsync() { await using (var handle = await _distributedLock.TryAcquireAsync("daily-report")) { if (handle is null) { return; // another instance holds the lock } // critical section — runs on a single instance } } } ``` #### Provider The base `Volo.Abp.DistributedLocking` package needs a backing synchronization provider registered in DI. A common choice is Redis via Medallion's `RedisDistributedSynchronizationProvider` (register it as `IDistributedLockProvider`); other Medallion backends (SQL Server, Azure, ZooKeeper) work too. Without a registered provider, `IAbpDistributedLock` is not functional. ### Related infrastructure (brief) **Data filters** — `IDataFilter` (namespace `Volo.Abp.Data`) toggles global query filters like `ISoftDelete` (namespace `Volo.Abp`) and `IMultiTenant` (`Volo.Abp.MultiTenancy`) for a scope. Wrap in a `using`: ```csharp using (_dataFilter.Disable<ISoftDelete>()) { var all = await _repo.GetListAsync(); // includes soft-deleted rows } ``` **Settings** — `ISettingProvider` (namespace `Volo.Abp.Settings`) reads config-like values with provider fallback. Providers are consulted highest-to-lowest priority (user → tenant → global → configuration → default): the first provider that returns a non-null value wins. Core method is `GetOrNullAsync(name)`; extension methods add `IsTrueAsync(name)` and `GetAsync<T>(name, defaultValue)` (`where T : struct`). Declare settings by deriving from `SettingDefinitionProvider` and overriding `void Define(ISettingDefinitionContext context)`, adding `SettingDefinition` instances via `context.Add(...)`. **Features** — `IFeatureChecker` (namespace `Volo.Abp.Features`) gates functionality per tenant/edition. Use `IsEnabledAsync(name)` for booleans and `GetOrNullAsync(name)` / `GetAsync<T>(name)` for values. Declare features by deriving from `FeatureDefinitionProvider`, overriding `void Define(IFeatureDefinitionContext context)`, calling `context.AddGroup(...)` then `group.AddFeature(name, defaultValue, ...)`. ## Validation - Build the app; `IDistributedCache<T>` and `IAbpDistributedLock` resolve from DI once their packages/providers are wired. - For real cross-instance caching, confirm the `Volo.Abp.Caching.StackExchangeRedis` package + `AbpCachingStackExchangeRedisModule` dependency + `Redis` connection string are in place (the in-memory cache is per-instance and won't be shared). - For locking, confirm a backing synchronization provider (e.g. Medallion's `RedisDistributedSynchronizationProvider` registered as `IDistributedLockProvider`) is in DI — without it `IAbpDistributedLock` is not functional. - Observe that a second instance calling `TryAcquireAsync` on the same name gets `null` while the first holds the handle. ## Common Pitfalls - **Not checking `TryAcquireAsync` for null** — it returns `null` when the lock isn't acquired within `timeout` (default `0` = don't wait). Treat a `null` handle as "someone else holds it" and skip the critical section. - **Forgetting `await using` on the lock handle** — the handle is `IAsyncDisposable`; dispose it to release the lock. - **Expecting the in-memory distributed cache to be shared** — it is per-instance. Add the Redis package/module/connection string for a real distributed cache. - **Missing the distributed-lock provider** — without a registered synchronization provider, `IAbpDistributedLock` does nothing useful. - **Assuming cache errors throw** — `hideErrors` defaults to `true`, so a Redis outage is logged and degrades to source reads instead of failing the request.
View on GitHub