Every binding-visible property on every shipped options class, with shipped defaults, scope, and a one-line note. This page mirrors samples/UiPath.Caching.Sample/appsettings.all.json; keep the two in step.
Reading the Scope column:
- App-wide — set under
Caching:<section>; applies to the whole app. - Per-provider — set under a provider section (
InMemoryRedis,Redis,InMemory). - Per-topic — set inside a
Topics[]entry underBroadcast:RedisStreamsorBroadcast:RedisPubSub. - Per-policy — set inside an entry under
Policieskeyed bytypeof(T).FullName.
Code-only seams — properties whose type is a delegate (Func<...>), System.Type, or a non-collection interface (ICacheKeyStrategy, IRedisStreamKeyStrategy, etc.) cannot bind from JSON via ConfigurationBinder and are not listed in their option's table here. They appear once at the end of each table as a Code-only seams footnote. Set them programmatically through the Action<CacheOptions> / provider-options delegates passed to AddCaching and Add<Provider> builder methods.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
Enabled |
bool |
true |
App-wide | Master on/off switch for the caching subsystem. |
TelemetryEnabled |
bool |
true |
App-wide | Gates the ICachingTelemetryProvider seam; set to false to silence all cache metrics. |
BroadcastEnabled |
bool |
true |
App-wide | Gates the ITopicFactory wiring; set to false to disable all invalidation broadcasts. |
ShardKeyEnabled |
bool |
false |
App-wide | Deprecated. Wraps a brace-free cache key in a {...} hash tag so the key alone picks its slot. That changes which keys co-locate, not how evenly they spread: the tag is the whole key, so it spreads no better and cannot make a multi-key batch single-slot. A key that already carries a valid tag renders the same either way; one whose braces form no valid tag is refused while the flag is on. Redis hashes the first {...} in the key, so a hash tag in KeyPrefix takes precedence over the tag this flag adds; keep braces out of AppShortName, Separator and the differentiator, since braces there change which span Redis hashes. Flipping the flag rewrites every brace-free key, so leave it as your deployment has it. To batch across keys on a cluster, give the keys a shared hash tag yourself. |
AuditEnabled |
bool |
true |
App-wide | Log writes whose serialized size exceeds LargeValueThreshold bytes. |
DefaultCache |
string |
"InMemoryRedis" |
App-wide | Provider name resolved when no explicit provider is requested; values: InMemory, Redis, InMemoryRedis. |
DefaultTopic |
string |
"RedisStreams" |
App-wide | Topic provider used when no explicit topic is requested; values: RedisStreams, RedisPubSub. |
SourceUri |
Uri? |
urn:<hostname> |
App-wide | Machine/pod identity embedded in cross-node sync events; use placeholder "urn:machine1" in config, override per environment. |
Separator |
char |
':' |
App-wide | Character used to join cache key segments. |
AppShortName |
string |
required | App-wide | Short application name prefixed to every cache key; apps throw at startup if blank or missing. |
KeyCasing |
CacheKeyCasing |
Insensitive |
App-wide | Key case folding applied when a key is built without an explicit mode, including every implicit string -> CacheKey conversion. Insensitive trims and lowercases (historical behavior); Sensitive preserves the caller's casing. Changing this relocates every cache key — existing entries become unreachable and are rewritten under the new spelling. The distributed cache always uses Sensitive and is unaffected. Scope is the process, not the container: CacheKey is a struct built by callers without access to DI, so the setting is seeded into the static CacheKey.DefaultCasing. Hosting two differently-configured containers in one process is therefore unsupported — the last AddCaching wins for both. |
LargeValueThreshold |
int |
20000 |
App-wide | Byte threshold for audit logging; writes whose payload exceeds this are logged when AuditEnabled is true. |
ConnectionMonitorEnabled |
bool |
false |
App-wide | Enable Redis health-check polling app-wide; provider-level ConnectionMonitorEnabled inherits this when null. |
LocalLockPoolSize |
int |
100 |
App-wide | Semaphore pool size for the default local lock — allocation hint, not a hard concurrency cap. |
LocalLockPoolInitialFill |
int |
10 |
App-wide | Semaphores pre-allocated at startup; must be in [0, LocalLockPoolSize]. |
DistributedLockPollInterval |
TimeSpan |
00:00:00.050 |
App-wide | Initial wait between distributed-lock acquire retries; doubles per attempt up to DistributedLockMaxPollInterval. |
DistributedLockMaxPollInterval |
TimeSpan |
00:00:00.500 |
App-wide | Upper bound for the exponential-backoff retry interval used by the distributed lock. |
Policies |
IDictionary<string, CachePolicy> |
{} |
App-wide | Named per-cache policies; see Policies[<name>] (CachePolicy). |
DefaultCachePolicy |
CachePolicy? |
null |
App-wide | Fills gaps in each cache instance's effective default. Provider-specific options (IMultilayerCacheOptions.LocalMaxExpiration, DefaultExpiration, lock fields) win per field; DefaultCachePolicy's fields fill any the provider left null. Also merged into every named policy at factory construction. See Policies[<name>] (CachePolicy). |
Code-only seams: the default ICacheFactory and ICachePolicyFactory registrations are swapped via the fluent builder.UseCacheFactory<T>() / builder.UseCachePolicyFactory<T>() extensions (instance and Func<IServiceProvider, T> overloads also exist). Both are intentionally not bindable from JSON — ConfigurationBinder has no string → Type converter. See how-to/extending.md for custom factory wiring.
ThreadPoolSocketManagerwas removed in 2.0 (a no-op since StackExchange.Redis 3.0). A section that still carries it failsAddRedisConnectionat startup; delete the key. See Upgrading to 2.0.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
ConnectionString |
string |
required | App-wide | StackExchange.Redis connection string; apps fail at startup without a valid value. Use "localhost:6379" as a local placeholder. |
ConnectionStringExtraParams |
string? |
null |
App-wide | Appended verbatim to ConnectionString; useful for Azure Redis extra parameters. |
WarmUpOnStart |
bool |
false |
App-wide | When true, kicks off the Redis connection at host startup via a hosted service (running any auth configurators), best-effort (never fails startup). Default false connects lazily on first cache use. |
BackOffMilliseconds |
int |
1000 |
App-wide | ms to wait before reconnecting after a connection failure. |
HeartbeatConsistencyChecks |
bool? |
null |
App-wide | null = StackExchange.Redis default; true enables heartbeat consistency checks. |
HeartbeatInterval |
TimeSpan? |
null |
App-wide | null = StackExchange.Redis default; TimeSpan override for the heartbeat period. |
ProfilerFeatureFlagKey |
string |
"RedisProfiler.Enabled" |
App-wide | Feature-flag key consulted before enabling the StackExchange.Redis command profiler. |
PlannedMaintenanceEnabled |
bool |
true |
App-wide | Tolerate planned-maintenance disconnects gracefully instead of faulting. |
MaintenanceNotifications |
RedisMaintenanceNotifications? |
null |
App-wide | Ask the server for advance notice of maintenance. null leaves the client's own default, so provider enlistment applies as it lands upstream. Auto asks and connects normally if the server does not offer them. Required refuses a connection that will not deliver them — useful to prove the feature is live. Delivered as RESP3 push notifications by Redis Enterprise and Redis Cloud; Azure Managed Redis is recognised as that kind of provider but its servers do not emit them yet, so the opt-in is preparatory there. Azure Cache for Redis uses the AzureRedisEvents channel instead and needs no opt-in. On a server that does not emit them — Redis OSS, Valkey, AMR for now — Auto is refused and the connection carries on, while Required rejects it. One exception, and it is the client's: inside a multi-group (geo-redundant) connection the feature is not activated at all, so Required warns and connects rather than rejecting — failing there would leave a group with no way to opt in, and the restriction is expected to be lifted upstream. Requires RESP3, which is negotiated by default. |
MaintenanceRelaxedTimeout |
TimeSpan? |
null |
App-wide | How long the client keeps command timeouts relaxed while an announced maintenance window is open. null derives twice the effective async timeout; with no connection string to derive from, the client's own default (10s) is left alone. Configured or derived, the value is rounded up into the whole seconds the option is expressed in and clamped to the 1-600s range the client parses, so a sub-second value becomes 1s rather than the 0 the client would otherwise read back. The two values the client derives from it move with it unless they are set themselves: MaintenanceRelaxedWindowMax (3x) and how long timeouts stay relaxed after a disruption completes (2x). |
MaintenanceRelaxedWindowMax |
TimeSpan? |
null |
App-wide | Caps a relaxed window however long the server announced the disruption would take -- the backstop for a closing notification that never arrives. null leaves the client deriving it as three times the effective relaxed timeout, which keeps the two coherent. Set, it is rounded up and clamped like the relaxed timeout above. Nothing checks it against the timeout it caps: set below that, every announced window collapses to this value. |
LogConnectionFailedEvents |
bool |
true |
App-wide | Log ConnectionFailed events from the multiplexer. |
LogConnectionRestoredEvents |
bool |
true |
App-wide | Log ConnectionRestored events from the multiplexer. |
EnableHangDetection |
bool |
true |
App-wide | Detect hung write/read channels and emit log warnings. |
LastWriteIntervalThresholdMilliseconds |
int |
15000 |
App-wide | ms since the last write before the channel is declared hung. |
LastReadIntervalThresholdMilliseconds |
int |
15000 |
App-wide | ms since the last read before the channel is declared hung. |
DefaultVersion |
string? |
"6.0" |
App-wide | Redis server version hint passed to StackExchange.Redis for command compatibility. |
HangDetectionDueTime |
TimeSpan? |
null |
App-wide | Delay before the first hang-detection check; null = 30 s library default. |
HangDetectionPeriod |
TimeSpan? |
null |
App-wide | Period between hang-detection checks; null = library default. |
EnableStaleEndpointDetection |
bool |
true |
App-wide | Rebuild the connection when a cluster node the multiplexer discovered has left the cluster but is still being retried (see Redis connection self-healing). Works under the default allowAdmin=false; emits one Redis.StaleEndpointScanDisabled event and stops when no refreshed node reports a cluster configuration. |
StaleEndpointThreshold |
TimeSpan |
00:01:00 |
App-wide | How long a discovered endpoint must stay disconnected before its cluster membership is checked; non-positive falls back to the default. A reconfiguration the client reports checks it five seconds later without waiting this out. |
StaleEndpointScanInterval |
TimeSpan |
00:00:30 |
App-wide | Period between stale-endpoint scans; non-positive falls back to the default. |
FailFastBacklogPolicy |
bool? |
true |
App-wide | Fail a command immediately when the connection is unavailable, rather than queuing it in the backlog until the connection's own timeout elapses. null, including a JSON null, fails fast like true; only false restores the client's queuing default. |
ProfilerEnabled |
bool |
false |
App-wide | Enable StackExchange.Redis command profiler. |
ProfilerHasDefaultSession |
bool |
true |
App-wide | Start a default profiling session automatically at startup. |
ProfilerFlushInterval |
TimeSpan |
00:00:01 |
App-wide | How often profiling data is flushed to the sink. |
ProfilerSessionMaxLifespan |
TimeSpan? |
00:01:00 |
App-wide | Maximum lifetime of a profiling session before it is auto-closed. |
ProfilerSessionMaxChecks |
int? |
100 |
App-wide | Maximum commands captured per profiling session before it is closed. |
ProfilerTrackMetricEnabled |
bool |
true |
App-wide | Emit profiler metrics via the telemetry provider. |
ConnectionMultiplexerFactoryType |
string? |
null |
App-wide | Assembly-qualified type name of a custom IConnectionMultiplexer factory; null = built-in. See recipes/opentelemetry-multiplexer-factory.md. |
AbortOnConnectFail |
bool |
false |
App-wide | false = retry in background; true = throw on first connect failure. |
Code-only seams: ConnectionFactory, ProfilingSessionFactory.
The connection of a second, complete caching stack registered with AddNamedCaching(name, …); see recipes/second-redis-connection.md. Same shape as Connections:Redis (any RedisConnectionOptions property), at least a ConnectionString; the section is bound over Connections:Redis, so a key left unset here keeps the primary connection's value. Registration fails when the section is empty. Everything else the stack uses (Caching:*, the provider and queue options) is the same section as the primary, unless AddNamedCaching is given another sectionName. The sample's second stack is named SecondaryRedis.
Bound by AddAzureEntraAuthentication() from the UiPath.Caching.Azure package; see recipes/azure-entra-authentication.md.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
ManagedIdentityClientId |
string? |
null |
App-wide | Client id of a user-assigned managed identity. With neither this nor ManagedIdentityOptions set, and no Credential, DefaultAzureCredential is used. |
ManagedIdentityOptions |
ManagedIdentityCredentialOptions? |
null |
App-wide | Options for the managed identity credential; wins over ManagedIdentityClientId. Binding sets tuning such as AuthorityHost, but not the target identity, which is fixed at construction, so a user-assigned identity with custom options is configured in code. |
RequireSsl |
bool |
true |
App-wide | Force TLS on the connection. |
RequireResp3 |
bool |
true |
App-wide | Negotiate RESP3 so Entra pub/sub stays on the re-authenticated connection. |
Code-only seams: Credential.
App-wide-only fields (per-topic Topics[] entries ignore these): Enabled, ConnectionMonitorEnabled, TrackStatistics, MaintainerEnabled, MaintainerCheckInterval, MaintainerTrimInterval, MaintainerQuarantineInterval, MaintainerSearchPattern.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
Enabled |
bool |
true |
App-wide only | Enable/disable the Redis Streams provider for the whole app; per-topic override is ignored. |
MaxLength |
long? |
32768 |
App-wide / Per-topic | Max entries in a stream before approximate trimming (MAXLEN ~); null = unlimited. |
Limit |
long? |
1024 |
App-wide / Per-topic | Max entries returned per XREAD call. |
PollBatchSize |
int |
4096 |
App-wide / Per-topic | Max entries fetched per poll cycle. |
FieldName |
string |
"event" |
App-wide / Per-topic | Stream entry field name used for the serialized event payload. |
PollInterval |
TimeSpan |
00:00:00.250 |
App-wide / Per-topic | Time between poll cycles when no notify signal is received. |
ConsumerCapacity |
int |
2048 |
App-wide / Per-topic | Bounded-channel capacity for in-process event delivery; use -1 for unbounded. |
FullMode |
BoundedChannelFullMode |
Wait |
App-wide / Per-topic | Policy when the channel is full: Wait, DropNewest, DropOldest, DropWrite. |
SlowObserverThreshold |
TimeSpan |
00:00:00.250 |
App-wide / Per-topic | Log a warning when an observer takes longer than this to process an event. |
ConnectionMonitorEnabled |
bool? |
null |
App-wide only | null = inherit from CacheOptions.ConnectionMonitorEnabled. |
TrackStatistics |
bool |
false |
App-wide only | Emit stream-level statistics via the telemetry provider. |
MaintainerEnabled |
bool |
true |
App-wide only | Run the background health maintainer that trims and quarantines stale streams. |
MaintainerCheckInterval |
TimeSpan |
00:30:00 |
App-wide only | How often the maintainer checks stream health. |
MaintainerTrimInterval |
TimeSpan |
01:00:00 |
App-wide only | How often the maintainer trims old entries; should exceed InMemoryRedis.LocalMaxExpiration to avoid trimming live L1 entries. |
MaintainerQuarantineInterval |
TimeSpan |
01:00:00 |
App-wide only | Idle time before the maintainer removes a consumer group with no active consumers. |
MaintainerSearchPattern |
string? |
null |
App-wide only | Redis key glob pattern for maintainer scan; null = use default prefix pattern. |
ProfilerEnabled |
bool |
false |
App-wide / Per-topic | Enable per-stream Redis command profiling. |
EmitStreamReceivedEvent |
bool |
false |
App-wide / Per-topic | Emit a telemetry event for each raw stream message received. |
NotifyEnabled |
bool |
false |
App-wide / Per-topic | Opt-in pub/sub doorbell — PUBLISH after XADD wakes the consumer immediately instead of waiting PollInterval. |
NotifyChannelName |
string |
"notify" |
App-wide / Per-topic | Channel suffix appended to the stream key; ignored when NotifyChannelStrategy is set in code. |
NotifyShardedPubSub |
bool |
false |
App-wide / Per-topic | true = use SPUBLISH/SSUBSCRIBE (Redis 7.0+); ignored when NotifyChannelStrategy is set in code. |
NotifySubscriberTimeout |
TimeSpan? |
null |
App-wide / Per-topic | Resubscribe interval when Subscribe fails; null or non-positive = multiplexer timeout. |
NotifySubscriberDueTime |
TimeSpan? |
null |
App-wide / Per-topic | Delay before the first subscribe attempt; null = half of resolved NotifySubscriberTimeout. |
Per-topic overrides: add entries to Topics[] under Broadcast:RedisStreams. Each entry is matched case-insensitively on Name; only present fields override app-wide values (delta overlay). See how-to/broadcast.md#per-topic-overrides.
Code-only seams: RedisStreamKeyStrategy, NotifyChannelStrategy.
App-wide-only fields (per-topic Topics[] entries ignore these): Enabled, ConnectionMonitorEnabled.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
Enabled |
bool |
false |
App-wide only | Enable/disable the Redis Pub/Sub provider for the whole app; per-topic override is ignored. Default is false — opt-in. |
ConsumerCapacity |
int |
2048 |
App-wide / Per-topic | Bounded-channel capacity for in-process event delivery. |
FullMode |
BoundedChannelFullMode |
Wait |
App-wide / Per-topic | Policy when the channel is full: Wait, DropNewest, DropOldest, DropWrite. |
SlowObserverThreshold |
TimeSpan |
00:00:00.250 |
App-wide / Per-topic | Log a warning when an observer takes longer than this to process an event. |
ConnectionMonitorEnabled |
bool? |
null |
App-wide only | null = inherit from CacheOptions.ConnectionMonitorEnabled. |
SubscriberTimeout |
TimeSpan? |
null |
App-wide / Per-topic | Resubscribe interval when Subscribe fails; null = multiplexer timeout. |
SubscriberDueTime |
TimeSpan? |
null |
App-wide / Per-topic | Delay before the first subscribe attempt; null = half of resolved SubscriberTimeout. |
Per-topic overrides: add entries to Topics[] under Broadcast:RedisPubSub. Each entry is matched case-insensitively on Name; only present fields override app-wide values (delta overlay). See how-to/broadcast.md#per-topic-overrides.
Code-only seams: RedisChannelStrategy.
The 1.x aliases
PrimaryMaxExpiration,PrimaryMaxExpirationDisconnectedandUsePrimaryOnlyWhenDisconnectedwere removed in 2.0. A section that still carries one failsAddInMemoryRedisat startup; rename to theLocal*keys below. See Upgrading to 2.0.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
Enabled |
bool |
true |
Per-provider | Enable/disable this two-tier (L1 in-memory + L2 Redis) cache provider. |
DefaultExpiration |
TimeSpan? |
01:00:00 |
Per-provider | Default TTL when no per-call or per-policy expiration is set. null means inherit, which resolves to CachePolicy.DefaultDistributedExpiration (1 h) — it does not mean "never expire". For unbounded entries set TimeSpan.MaxValue. |
Timeout |
TimeSpan |
00:00:01 |
Per-provider | Max wait for a cache operation before giving up and falling through. |
TrackStatistics |
bool |
true |
Per-provider | Emit hit/miss/eviction counters via the telemetry provider. |
StatisticsFlushInterval |
TimeSpan |
00:01:00 |
Per-provider | How often statistics are flushed to the telemetry sink. |
BroadcastEnable |
bool |
true |
Per-provider | Publish/consume cross-node L1 invalidations for this provider. false keeps L1+L2 with no broadcast traffic. Can only narrow CacheOptions.BroadcastEnabled, never widen it. Note the default is the opposite of InMemoryCacheOptions.BroadcastEnable. |
Topic |
string? |
null |
Per-provider | Topic name for L1 invalidation broadcasts; null = use CacheOptions.DefaultTopic. |
LocalMaxExpiration |
TimeSpan? |
null |
Per-provider | Cap on the L1 (in-memory) TTL while L2 is connected; null = no cap beyond DefaultExpiration. |
ConnectionMonitorEnabled |
bool? |
null |
Per-provider | null = inherit from CacheOptions.ConnectionMonitorEnabled. |
CacheNullValues |
bool |
false |
Per-provider | Persist null/empty factory returns as sentinels to suppress thundering-herd on missing keys. |
ConnectionMonitorPeriod |
TimeSpan? |
00:00:05 |
Per-provider | How often the connection monitor probes Redis health. |
SizeLimit |
long? |
null |
Per-provider | Max bytes for the in-memory tier; null = unlimited. |
CompactionPercentage |
double? |
null |
Per-provider | Fraction of SizeLimit to free when the limit is hit; null = runtime default (0.05). |
UseLocalOnlyWhenDisconnected |
bool? |
null |
Per-provider | null = false; true = serve L1-only responses when L2 is disconnected. |
LocalMaxExpirationDisconnected |
TimeSpan? |
00:00:30 |
Per-provider | L1 TTL cap while L2 is disconnected; limits the stale-read window. |
LocalLockEnabled |
bool? |
true |
Per-provider | Acquire a local (in-process) lock before calling the value factory. |
LocalLockTimeout |
TimeSpan? |
00:00:00.500 |
Per-provider | Max wait to acquire the local lock before bypassing it. |
DistributedLockEnabled |
bool? |
null |
Per-provider | Acquire a distributed (Redis) lock before calling the value factory; null = not configured. |
DistributedLockTimeout |
TimeSpan? |
00:00:00.500 |
Per-provider | Max wait to acquire the distributed lock. |
DistributedLockExpiry |
TimeSpan? |
00:00:05 |
Per-provider | Redis key TTL for the distributed lock (safety expiry to prevent deadlocks). |
Code-only seams: EntryFactory, CacheKeyStrategy, TopicKeyStrategy, SizeProvider, LockKeyStrategy.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
Enabled |
bool |
true |
Per-provider | Enable/disable the standalone Redis cache provider. |
DefaultExpiration |
TimeSpan? |
01:00:00 |
Per-provider | Default TTL when no per-call or per-policy expiration is set. null means inherit, which resolves to CachePolicy.DefaultDistributedExpiration (1 h) — it does not mean "never expire". For unbounded entries set TimeSpan.MaxValue. |
KeyPrefix |
string |
"" |
Per-provider | Fallback for the prefix your IRedisConnector's IDatabase prepends to every key, typically via WithKeyPrefix. The library reads that prefix from the connector's database itself, through StackExchange.Redis's own wrapper or a one-off ECHO, and uses this value only when neither answers. A connector that hides the wrapper behind a decorator and cannot run ECHO (an ACL that denies it, for instance) must still mirror its prefix here. A connector prefix that is not UTF-8 or holds a SCAN glob character (*, ?, [, ], \) is skipped with a warning, and this value is used instead. A value here holding one of those glob characters is skipped the same way, and no prefix is assumed, since * or ? would widen the maintainer's SCAN to other keyspaces. Each skipped prefix, and a value that disagrees with the connector's, is logged once. The prefix is what the stream maintainer scans for and strips from raw SCAN results, and what the cluster slot check hashes with, so a hash tag in it keeps multi-key batches on one slot. The shipped RedisConnector prepends nothing. A tag in the prefix takes precedence over the per-key tag ShardKeyEnabled adds. |
Timeout |
TimeSpan |
00:00:01 |
Per-provider | Max wait for a cache operation before giving up and falling through. |
ConnectionMonitorEnabled |
bool? |
null |
Per-provider | null = inherit from CacheOptions.ConnectionMonitorEnabled. |
CacheNullValues |
bool |
false |
Per-provider | Persist null/empty factory returns as sentinels to suppress thundering-herd on missing keys. |
KeyReadTelemetryEnabled |
bool |
false |
Per-provider | Opt-in per-key read attribution: each read emits a Redis dependency carrying the key in data (one per hash key for hash reads), with a BatchId shared across the operation. Off by default because raw keys are high-cardinality; the per-operation hit/miss metric is always emitted regardless. |
AwaitRefresh |
bool |
false |
Per-provider | Wait for the server to apply a refresh instead of sending KEYEXPIRE/PERSIST fire-and-forget. Off by default, keeping the round trip off the sliding-expiration path, which runs on every read of a sliding entry. While off, a refresh is unverifiable: RefreshAsync returns false whether it succeeded, failed or the key was absent; the reply is never seen, so a rejected command is neither logged nor retried by the resilience pipeline, and telemetry records every refresh as unsuccessful; and the new deadline is not yet in effect when the call returns. Turn it on where a lost refresh matters more than a round trip. AddDistributedCache sets it for its own provider. |
Code-only seams: EntryFactory, CacheKeyStrategy, RedisKeyStrategyFactory.
The 1.x aliases
PrimaryMaxExpiration,PrimaryMaxExpirationDisconnectedandUsePrimaryOnlyWhenDisconnectedwere removed in 2.0. A section that still carries one failsAddMemoryat startup; rename to theLocal*keys below. See Upgrading to 2.0.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
Enabled |
bool |
true |
Per-provider | Enable/disable the in-memory-only cache provider. |
DefaultExpiration |
TimeSpan? |
01:00:00 |
Per-provider | Default TTL when no per-call or per-policy expiration is set. null means inherit, which resolves to CachePolicy.DefaultDistributedExpiration (1 h) — it does not mean "never expire". For unbounded entries set TimeSpan.MaxValue. |
Timeout |
TimeSpan |
00:00:01 |
Per-provider | Max wait for a cache operation before giving up. |
TrackStatistics |
bool |
true |
Per-provider | Emit hit/miss/eviction counters via the telemetry provider. |
StatisticsFlushInterval |
TimeSpan |
00:01:00 |
Per-provider | How often statistics are flushed to the telemetry sink. |
BroadcastEnable |
bool |
false |
Per-provider | Enable broadcast invalidation for this in-memory cache instance. |
Topic |
string? |
null |
Per-provider | Topic name for invalidation broadcasts; null = use CacheOptions.DefaultTopic. |
LocalMaxExpiration |
TimeSpan? |
01:00:00 |
Per-provider | Cap on in-memory TTL; null = no cap (falls back to the resolved DefaultExpiration). |
ConnectionMonitorEnabled |
bool? |
null |
Per-provider | Inert for this provider (no Redis connection); present to satisfy IMultilayerCacheOptions. |
CacheNullValues |
bool |
false |
Per-provider | Persist null/empty factory returns as sentinels. |
ConnectionMonitorPeriod |
TimeSpan? |
00:00:05 |
Per-provider | Inert for this provider; present to satisfy IMultilayerCacheOptions. |
SizeLimit |
long? |
null |
Per-provider | Max bytes for the in-memory store; null = unlimited. |
CompactionPercentage |
double? |
null |
Per-provider | Fraction of SizeLimit to free when the limit is hit; null = runtime default (0.05). |
UseLocalOnlyWhenDisconnected |
bool? |
null |
Per-provider | Inert for this provider; present to satisfy IMultilayerCacheOptions. |
LocalMaxExpirationDisconnected |
TimeSpan? |
00:00:30 |
Per-provider | Inert for this provider; present to satisfy IMultilayerCacheOptions. |
LocalLockEnabled |
bool? |
true |
Per-provider | Acquire a local (in-process) lock before calling the value factory. |
LocalLockTimeout |
TimeSpan? |
00:00:00.500 |
Per-provider | Max wait to acquire the local lock before bypassing it. |
DistributedLockEnabled |
bool? |
null |
Per-provider | Inert for this provider; present to satisfy IMultilayerCacheOptions. Startup validation still applies. |
DistributedLockTimeout |
TimeSpan? |
null |
Per-provider | Inert for this provider; present to satisfy IMultilayerCacheOptions. |
DistributedLockExpiry |
TimeSpan? |
null |
Per-provider | Inert for this provider; present to satisfy IMultilayerCacheOptions. |
Code-only seams: EntryFactory, CacheKeyStrategy, TopicKeyStrategy, SizeProvider, LockKeyStrategy.
The queue package's AddQueueMemory / AddQueueRedis / AddQueueInMemoryRedis bind from the same sections as the core providers by default — Caching:InMemory, Caching:Redis, Caching:InMemoryRedis — into their own options types. A key both types declare (Enabled, DefaultExpiration, LocalMaxExpiration, …) therefore configures both the core cache and the set cache of that backing; keys one type lacks are ignored by the binder. Pass a section name to any of the three to bind from elsewhere — appsettings.all.json lists them under a Caching:Queue section of their own for that reason, so each set of keys is visible separately from the core provider it would otherwise share a section with. The Redis tier of the multilayer set cache reuses RedisCacheOptions and RedisSetCacheOptions, but AddQueueInMemoryRedis binds neither: RedisSetCacheOptions is bound only by an AddQueueRedis call, so a layout that moves it to Caching:Queue:Redis needs AddQueueRedis("Queue:Redis") alongside the multilayer registration.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
Enabled |
bool |
true |
Per-provider | Enable/disable the in-memory set cache. |
DefaultExpiration |
TimeSpan? |
01:00:00 |
Per-provider | Whole-set lifetime when neither the call nor the policy names one; every add re-applies it. null resolves to CachePolicy.DefaultDistributedExpiration (1 h); TimeSpan.MaxValue keeps a set forever. |
LocalMaxExpiration |
TimeSpan? |
null |
Per-provider | Cap on a stored set's lifetime. null by default: with no backing tier DefaultExpiration already bounds every set. |
ConnectionMonitorEnabled |
bool |
false |
Per-provider | Inert for this provider (no backing tier); present for parity with InMemoryRedisQueueCacheOptions. |
ConnectionMonitorPeriod |
TimeSpan? |
00:00:05 |
Per-provider | Inert for this provider. |
UseLocalOnlyWhenDisconnected |
bool |
false |
Per-provider | Inert for this provider. |
LocalMaxExpirationDisconnected |
TimeSpan? |
00:00:30 |
Per-provider | Inert for this provider. |
TrackStatistics |
bool |
true |
Per-provider | Emit hit/miss/eviction counters via the telemetry provider. |
StatisticsFlushInterval |
TimeSpan |
00:01:00 |
Per-provider | How often statistics are flushed to the telemetry sink. |
SizeLimit |
long? |
null |
Per-provider | Max size for the in-memory store; null = unlimited. |
CompactionPercentage |
double? |
null |
Per-provider | Fraction of SizeLimit to free when the limit is hit; null = runtime default (0.05). |
Code-only seams: SizeProvider.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
Enabled |
bool |
true |
Per-provider | Enable/disable the Redis set cache. |
ResilienceKeyName |
string? |
null |
Per-provider | Name of the resilience pipeline applied to destructive reads (SPOP), resolved via IResiliencePipelineProvider; null or empty runs them with no pipeline. The built-in read pipeline is refused: it abandons a call at its timeout, and an abandoned SPOP loses what it popped. |
Lifetimes and the connection come from RedisCacheOptions (DefaultExpiration, ConnectionMonitorEnabled, …), which AddRedis binds (Caching:Redis by default) whatever section AddQueueRedis is given.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
Enabled |
bool |
true |
Per-provider | Enable/disable the multilayer set cache. |
DefaultExpiration |
TimeSpan? |
null |
Per-provider | Whole-set lifetime when neither the call nor the policy names one. A value outranks the Redis tier's RedisCacheOptions.DefaultExpiration for sets written through this provider, as InMemoryRedisCacheOptions.DefaultExpiration does for the other caches; null inherits the tier's default. |
LocalMaxExpiration |
TimeSpan? |
00:01:00 |
Per-provider | How long a locally cached set snapshot is served before it is re-fetched from Redis. Also bounds the staleness window for mutations made on other nodes — this tier does not subscribe to broadcast invalidation. null = no time bound (not recommended multi-node). |
ConnectionMonitorEnabled |
bool |
false |
Per-provider | Monitor the Redis tier's connection. Required for UseLocalOnlyWhenDisconnected; enabled without it, snapshots are dropped rather than served while Redis is unreachable. |
ConnectionMonitorPeriod |
TimeSpan? |
00:00:05 |
Per-provider | How often the connection monitor re-evaluates a failed connection. |
UseLocalOnlyWhenDisconnected |
bool |
false |
Per-provider | Serve reads and apply mutations on the local snapshot while Redis is unreachable. Requires ConnectionMonitorEnabled. |
LocalMaxExpirationDisconnected |
TimeSpan? |
00:00:30 |
Per-provider | Lifetime cap on local set state written while Redis is unreachable, so it dies quickly once connectivity returns. |
TrackStatistics |
bool |
true |
Per-provider | Emit hit/miss/eviction counters via the telemetry provider. |
StatisticsFlushInterval |
TimeSpan |
00:01:00 |
Per-provider | How often statistics are flushed to the telemetry sink. |
SizeLimit |
long? |
null |
Per-provider | Max size for the local snapshot store; null = unlimited. |
CompactionPercentage |
double? |
null |
Per-provider | Fraction of SizeLimit to free when the limit is hit; null = runtime default (0.05). |
Code-only seams: SizeProvider.
Masking is a registration, not a setting: builder.AddKeyMasking(prefixes) or AddKeyMasking<TPolicy>(). Without one,
the container resolves NullKeyMaskingPolicy and keys are logged verbatim. There is no per-provider switch, because a
policy is asked which tier is logging and can answer differently. See
how-to/telemetry-and-strategies.md.
Registered in code via builder.AddDistributedCache(providerName); the backing tier is a required
argument (Redis recommended, InMemoryRedis, or InMemory). The options object is passed to the
extension rather than bound from configuration.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
CacheKeyStrategy |
ICacheKeyStrategy? |
null |
Per registration | Composes the stored key from the caller key. Null applies PrefixCacheKeyStrategy with DefaultKeyPrefix ("d"); pass new PrefixCacheKeyStrategy("d:sess") to sub-namespace while keeping that prefix, or DefaultCacheKeyStrategy to opt out of prefixing entirely. Code-only seam. |
RedisKeyDifferentiator |
string? |
null |
Per registration | Fills the slot after AppShortName that the application's caches fill with a RedisKeyspaces value. Null uses DefaultRedisKeyDifferentiator ("dh"). Inert on the InMemory tier. A value matching a keyspace any registered package reserved is rejected at registration, in whichever order the two were registered, from separate AddCaching calls, and even with Enabled false: the core's RedisKeyspaces, and "se" once AddQueueRedis / AddQueueInMemoryRedis is on the builder, enabled or not. Packages declare theirs with IServiceCollection.ReserveRedisKeyspace. |
RedisKeyStrategyFactory |
IRedisKeyStrategyFactory? |
null |
Per registration | Builds the Redis key, receiving RedisKeyDifferentiator. Null inherits the application's RedisCacheOptions.RedisKeyStrategyFactory, keeping its AppShortName, separator and sharding conventions. Code-only seam. |
PolicyName |
string? |
null |
Per registration | Named CachePolicy applied to the adapter's operations; an unregistered name fails fast at startup. |
DefaultEntryExpiration |
TimeSpan? |
null |
Per registration | Expiration used when the caller supplies none. IDistributedCache treats absent expiration as "until removed"; unless AllowUnboundedEntries is set that is mapped to this value, falling back to the backing tier's DefaultExpiration and then to CachePolicy.DefaultDistributedExpiration. |
AllowUnboundedEntries |
bool |
false |
Per registration | Honor "no expiration" literally. Off by default, and now the only way to reach an unbounded entry through this adapter without naming a lifetime — an unset default resolves to CachePolicy.DefaultDistributedExpiration rather than to "until removed". |
Entries are stored as a Redis hash (data, absexp, sldexp) in a keyspace disjoint from the
application's own caches, so Refresh reads only the expiration metadata. Keys are always
case-sensitive regardless of CacheOptions.KeyCasing.
IBufferDistributedCache. On net9.0 and later the registered IDistributedCache also implements
IBufferDistributedCache, the buffer-based half of the same contract, so a caller reads into an
IBufferWriter<byte> and writes from a ReadOnlySequence<byte> instead of trading a fresh array per
operation. Consumers find it the way the framework's own caches expose it — a type check on the resolved
IDistributedCache, which is what HybridCache does — so nothing extra is registered and no code
changes. Both halves share one read and write path: same keyspace, same expiration and sliding, same
stored fields, so entries written through either are readable through the other and by any other client
on the conventional layout. TryGet reports hit and payload separately, which is the one thing the array
half cannot express: an entry stored with an empty payload is a hit that writes no bytes, where
Get returns an empty array for that and for a miss alike. On a write, what happens to the caller's
buffer depends on the tier: Redis is handed a single-segment sequence as the caller's own memory — no
array in between, since the connection copies it as the command is written and the write is awaited to
completion (see IMemorySerializerProxy) —
and flattens a segmented one into a rented buffer returned after the await; the memory tiers keep what
they are handed, so there the sequence is copied into an owned array. Either way a pooled buffer is safe
to reuse the moment the call returns. The net8.0 floor pins
Microsoft.Extensions.Caching.Abstractions to 8.0.0, which predates the interface; there the type check
comes up empty and consumers stay on the array path.
Keyspace separation. A full Redis key carries three independent segments, each answering a different question:
app : dh : d: <caller key>
└─┬─┘ └┬─┘ └┬┘
│ │ └─ CacheKey level: CacheKeyStrategy ("d:" by default)
│ └─ RedisKeyDifferentiator — separates this cache from the application's own
└─ CacheOptions.AppShortName — separates applications sharing one Redis
AppShortName is required and app-wide, so it is never this registration's job. CacheKeyStrategy
prefixes the CacheKey itself, so a distributed entry cannot be reached through the application's own
ICache/IHashCache with the bare caller key, and the memory tiers' local lock keyspace (keyed by
provider name plus cache key) stays separate too — neither of which a Redis-key-level segment can do.
RedisKeyDifferentiator and RedisKeyStrategyFactory separate the physical Redis keyspace, taking the
slot the application's caches fill with a RedisKeyspaces value. A differentiator landing on a
keyspace some package reserved — the core's own, or "se" from UiPath.Caching.Queue — is rejected at
registration, in whichever order the two were registered and even from separate AddCaching calls.
Because a differentiator only separates anything if the factory honors it, a second check composes a
probe key both ways and fails when the distributed key matches what the application's caches or any
reserved keyspace would produce, so a factory landing on one is rejected instead of quietly sharing
the keyspace. That one runs when the Redis-backed cache is resolved, which is startup only if
something resolves IDistributedCache there. It renders each reserved keyspace through the
application's factory, so it is exact for the caches and approximate for keyspaces whose owner
composes keys elsewhere, such as broadcast streams.
The cache-key strategy is applied by the adapter rather than by the backing provider's own
ICacheOptions.CacheKeyStrategy, which RedisHashCache does not consult — routing it through the
provider would make the stored key depend on which tier is configured.
Keys are masked in logs, verbatim in Redis.
IDistributedCachekeys are chosen by the consumer and can be secrets — under ASP.NET Core session the key is the session id — so this adapter and the private tiers it builds mask keys in every line they write, whatever the application registered, and independently ofAddKeyMasking. That covers the adapter's own two messages (a failed write atWarning, a no-op remove atDebug) and the backing cache's diagnostics:MultilayerHashCachenames theCacheKeyin fiveLogLevel.Warningmessages (raised on inner-cache exceptions) and in several Debug/Trace ones, andRedisHashCachenames the physical key inLogLargeValueDetected(Warning) andLogRefreshingKey(Trace). Storage is a different matter: keys stored verbatim for parity with the conventional layout appear inKEYS/SCANoutput and RDB snapshots, so treat Redis dumps for this provider as containing session identifiers even though the logs no longer do.
Refresh waits for the server on this provider. AddDistributedCache sets
RedisCacheOptions.AwaitRefresh on the private provider it builds, so IDistributedCache.Refresh — and the
sliding extension a Get performs — costs a round trip but is in effect when the call returns, a rejection
reaches the log and the resilience pipeline, and telemetry reflects the real outcome. Deliberate for a
session store: a silently dropped refresh shortens the session and fire-and-forget cannot report one. The
application's own caches keep the fire-and-forget default; see AwaitRefresh above.
Backing-tier caveats. InMemoryRedis carries a stale-read window until backplane invalidation
lands and requires AddInMemoryRedis for its broadcast wiring (enforced at startup). InMemory
stores entries only in memory, where InMemoryCacheOptions.LocalMaxExpiration (1 hour by default)
caps every entry regardless of the caller's requested expiration — use it for tests and single-node
scenarios, not for long-lived entries.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
Enabled |
bool |
true |
App-wide | Enable Polly circuit-breaker and retry policies for Redis operations. |
DurationOfBreak |
TimeSpan |
00:01:00 |
App-wide | How long the circuit stays open after tripping. |
ExceptionsAllowedBeforeBreaking |
int |
500 |
App-wide | Number of failures within the sampling window before the circuit opens. |
RequestTimeout |
TimeSpan? |
00:00:01 |
App-wide | Per-operation timeout enforced by the Polly pipeline. The strategy is cooperative: it bounds a callback that observes the token it arms, and on this library's Redis paths the client takes no cancellation token, so the read pipeline races the callback against it and returns at the timeout, leaving the call to finish underneath. Write and custom pipelines do not, since a write can carry memory the caller reclaims on return and a custom pipeline can guard a destructive read such as SPOP; there it bounds nothing. |
DisruptionRequestTimeout |
TimeSpan? |
null |
Per-pipeline | Replaces RequestTimeout while an announced window is open, so a window the client is deliberately relaxing timeouts for is not cut short by the app's own cap. Resolved per operation, so it follows a window that opens after startup. Left null, the tier's own suggestion applies instead: the Redis tier offers its effective MaintenanceRelaxedTimeout, which is what it is relaxing its command timeouts to, so the two stay coherent without being configured twice. The Azure probe route does not widen it, since that run lasts ten minutes whatever the maintenance did. Neither the configured value nor the suggestion can shorten RequestTimeout. With no IDisruptionState registered, one timeout applies throughout. |
RetryCount |
int? |
1 |
App-wide | Number of immediate retries before propagating a failure. |
TelemetryEnabled |
bool |
true |
App-wide | Emit circuit-breaker state-change events via the telemetry provider. |
RethrowCircuitBreakerExceptions |
bool |
false |
App-wide | true = rethrow BrokenCircuitException to the caller instead of swallowing it. |
Entries are keyed by string under Caching:Policies. ICache<T> and IHashCache<T> bind by name at construction (default key = typeof(T).FullName). Unregistered names fall back to DefaultCachePolicy.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
LocalExpiration |
TimeSpan? |
null |
Per-policy | L1 (in-memory) TTL cap for this policy; null = inherit from provider LocalMaxExpiration. Effective L1 TTL is min(entry.Expiration, LocalExpiration). |
LocalExpirationDisconnected |
TimeSpan? |
null |
Per-policy | L1 TTL cap when L2 is disconnected; null = inherit from provider LocalMaxExpirationDisconnected. |
DistributedExpiration |
TimeSpan? |
null |
Per-policy | L2 (Redis) entry lifetime; null = use provider DefaultExpiration, and under that CachePolicy.DefaultDistributedExpiration (1 h). Per-call expiration arguments still take precedence. Set TimeSpan.MaxValue for unbounded. |
FactoryTimeout |
TimeSpan? |
null |
Per-policy | Max time allowed for the value factory before it is abandoned; null = no timeout. |
JitterMaxDuration |
TimeSpan? |
null |
Per-policy | Max random duration added to the L2 TTL at write time (uniform in [0, JitterMaxDuration)); null or 00:00:00 disables jitter. Caller-supplied expiration is honored exactly (no jitter). |
RehydrateEnabled |
bool? |
null |
Per-policy | Master switch for proactive background refresh; null = inherit (default off). |
Rehydrate |
RehydrateOptions? |
null |
Per-policy | Rehydrate tuning; replaced wholesale (no per-field merge) when overriding. See Policies[<name>].Rehydrate (RehydrateOptions). |
Lock |
LockProfile? |
null |
Per-policy | Per-cache lock overrides; field-level merged against the default policy. See Policies[<name>].Lock (LockProfile). |
Nested under a CachePolicy entry. Replaced wholesale when a named policy overrides it — redeclare all fields you want to keep.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
Threshold |
double |
0.75 |
Per-policy | Soft-TTL trigger fraction in (0, 1]. Refresh fires once the elapsed lifetime reaches Threshold × Duration (i.e. at 0.75, refresh fires after 75% of the TTL has elapsed — 25% remaining). |
BaseCooldown |
TimeSpan |
00:00:05 |
Per-policy | Minimum cooldown between consecutive refresh attempts after the trigger fires. |
MaxCooldown |
TimeSpan |
00:05:00 |
Per-policy | Upper bound on the exponential-backoff cooldown after repeated refresh failures. |
TimeoutFraction |
double |
0.5 |
Per-policy | Background factory timeout as a fraction of the entry's Duration; floored at 1 s. |
Name |
string? |
null |
Per-policy | Profile label surfaced on telemetry as the profile dimension. |
Nested under a CachePolicy entry. Field-level merged against the default policy's LockProfile — set only the fields you want to override; null inherits.
| Property | Type | Default | Scope | Notes |
|---|---|---|---|---|
LocalLockEnabled |
bool? |
null |
Per-policy | null = inherit from provider options or default policy. |
DistributedLockEnabled |
bool? |
null |
Per-policy | null = inherit from provider options or default policy. |
LocalLockTimeout |
TimeSpan? |
null |
Per-policy | null = inherit from provider options or default policy. |
DistributedLockTimeout |
TimeSpan? |
null |
Per-policy | null = inherit from provider options or default policy. |
DistributedLockExpiry |
TimeSpan? |
null |
Per-policy | Redis key TTL for the distributed lock; null = inherit from provider options or default policy. |