LevelUp.ServiceDefaults.Caching 0.5.0

dotnet add package LevelUp.ServiceDefaults.Caching --version 0.5.0
                    
NuGet\Install-Package LevelUp.ServiceDefaults.Caching -Version 0.5.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="LevelUp.ServiceDefaults.Caching" Version="0.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="LevelUp.ServiceDefaults.Caching" Version="0.5.0" />
                    
Directory.Packages.props
<PackageReference Include="LevelUp.ServiceDefaults.Caching" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add LevelUp.ServiceDefaults.Caching --version 0.5.0
                    
#r "nuget: LevelUp.ServiceDefaults.Caching, 0.5.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package LevelUp.ServiceDefaults.Caching@0.5.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=LevelUp.ServiceDefaults.Caching&version=0.5.0
                    
Install as a Cake Addin
#tool nuget:?package=LevelUp.ServiceDefaults.Caching&version=0.5.0
                    
Install as a Cake Tool

LevelUp.ServiceDefaults.Caching

Opt-in caching satellite for LevelUp.ServiceDefaults. Wires FusionCache's .AsHybridCache() adapter as the application's HybridCache, giving an in-process L1 with an optional Redis L2 and a Redis backplane for cross-instance invalidation. The FusionCache/Redis dependencies are isolated here so they stay out of the AOT-safe core.

PHI cache keys

Use PhiCacheKey for any cache entry that can identify or contain protected health information. It returns a PhiCacheIdentity containing both the digest-addressed key and the full, canonical, unhashed subject discriminator. Store values in PhiCacheEntry<T> and compare the stored subject before returning a hit. Because both the discriminator and value are stored in Redis, require AES-256-GCM encryption and register a production key provider before enabling this path:

builder.Services.AddLevelUpKeyVaultCacheEncryptionKey(options =>
    options.VaultUri = "https://my-vault.vault.azure.net/");

builder.AddLevelUpHybridCache(options =>
{
    options.RequireDistributedCache = true;
    options.Encrypt = true;
});

const string patientId = "Patient/123";
string canonicalPatient = $"patient-id:v1:{patientId}";
PhiCacheIdentity identity = PhiCacheKey.ForOperation(
    "fhir:v1",
    nameof(ReadPatientAsync),
    canonicalPatient);

PhiCacheEntry<Patient>? cached = await cache.GetOrCreateAsync(
    identity.Key,
    async cancellationToken => new PhiCacheEntry<Patient>(
        identity.Subject,
        await ReadPatientAsync(patientId, cancellationToken)));

if (!PhiCacheKey.Matches(cached?.Subject, identity.Subject))
{
    // Treat a missing or mismatched discriminator as a cache miss.
}

AddLevelUpKeyVaultCacheEncryptionKey comes from LevelUp.ServiceDefaults.Azure. The static key provider in this package is for development and tests only. Do not put a PhiCacheEntry<T> in a distributed cache unless Encrypt = true and an ICacheEncryptionKeyProvider is registered; an unhashed subject is identifying material even when the cached value looks harmless.

The PhiCacheKey factories accept only strings deliberately. The caller owns a versioned, culture-invariant canonical representation that includes every field capable of changing the answer; do not pass an object or tuple and rely on default serializer behavior. The factories length-frame their own prefix/operation/payload segments, then hash the exact full subject later used by Matches.

PhiCacheKey routes all of its hashed key material through HashGenerator, which uses SHA-256 truncated to 128 bits and emits {prefix}:{base64 digest}. The construction is deterministic across processes and runs. It accepts only a caller-owned canonical string; it does not serialize objects. Do not hash key material inline. A truncated digest does not make collisions impossible, and SHA-256 is neither encryption nor an HMAC: it does not make predictable key material private. The same subject feeds the digest, but the discriminator stored in the envelope remains unhashed; consumers must compare it before returning a value and encrypt the distributed-cache envelope at rest.

This package provides the independent ServiceDefaults primitive only. It does not implement the E-A.9a keyed-HMAC decision and does not adopt the primitive in the Health FHIR read path; that health integration and its planted-collision proof remain separate #174 work.

Usage

builder.AddLevelUpServiceDefaults()
       .AddLevelUpHybridCache(); // L1 always; L2 + backplane only with ConnectionStrings:cache

The L2 distributed cache and the invalidation backplane self-disable when no Redis connection resolves: the call registers an L1-only cache and does not throw, keeping the canonical shim uniform across services. At host start a log line reports the selected mode (Information for L1 + L2 + backplane, Warning for L1-only) so the disable is never silent.

Entry points

Three overloads route through one private core, so the wiring is identical across surfaces:

// 1. Host builder (returns the builder, so the fluent chain keeps compiling).
builder.AddLevelUpHybridCache(o => o.ConnectionName = "redis");

// 2. IServiceCollection + IConfiguration — resolves ConnectionStrings:{ConnectionName}.
//    Use this when caching is registered in an IServiceCollection extension.
services.AddLevelUpHybridCache(configuration, o => o.ConnectionName = "redis");

// 3. IServiceCollection + an explicit connection string (used verbatim, no lookup).
services.AddLevelUpHybridCache(redisConnectionString);

The builder overload returns TBuilder (so builder.AddLevelUpServiceDefaults().AddLevelUpHybridCache() chains); the IServiceCollection overloads return IServiceCollection.

Overload resolution: a bare null literal is ambiguous (CS0121) between the IConfiguration and string? overloads. Cast it to pick one, e.g. services.AddLevelUpHybridCache((string?)null).

Options (LevelUpCacheOptions)

Option Default Purpose
ConnectionName "cache" The ConnectionStrings key whose value supplies the Redis L2 + backplane. Set this when your Redis resource is named something other than cache (for example AddRedis("redis")ConnectionName = "redis"). A blank value at the resolved key self-disables to L1-only.
RequireDistributedCache false When true, an unresolved connection throws eagerly at registration (inside the Add… call) with an actionable message instead of self-disabling — for production topologies that must not silently lose the backplane.
ConfigureRedis null An Action<RedisCacheOptions> escape hatch onto the standard Redis options surface (InstanceName, ConfigurationOptions, ssl/abort-connect, …).
Encrypt false Require AES-256-GCM at-rest encryption of L2 payloads (needs a registered ICacheEncryptionKeyProvider; a missing provider fails fast at start).
LocalDuration / DistributedDuration 1 min / 10 min The L1 / L2 default entry durations.

Namespacing shared Redis with InstanceName

When several apps share one Redis, set InstanceName via ConfigureRedis to prefix this app's L2 keys:

services.AddLevelUpHybridCache(configuration, o =>
{
    o.ConnectionName = "redis";
    o.ConfigureRedis = redis => redis.InstanceName = "tenantA:";
});

There is no implicit key-prefix default, so the at-rest key shape is unchanged unless you opt in here.

ConfigureRedis precedence vs the shared multiplexer

The L2 and the backplane share one connect-once IConnectionMultiplexer (one connection, per FusionCache's own guidance). The opinionated defaults — including the shared-multiplexer factory and the resolved connection — are applied first; your ConfigureRedis callback runs last, so consumer values win. Note the precedence consequence: a callback that re-sets ConnectionMultiplexerFactory or Configuration replaces the shared factory and forfeits connection sharing with the backplane. Prefer tuning ConfigurationOptions/InstanceName over replacing the factory.

The L2 stays private to FusionCache

The Redis L2 is constructed inline and held by FusionCache only — the satellite registers no application-wide IDistributedCache. This is deliberate: registering a global IDistributedCache would silently re-home ASP.NET session / data-protection / output-cache onto Redis and let sibling IDistributedCache consumers write plaintext into the encrypted L2's Redis. GetService<IDistributedCache>() is therefore unaffected by AddLevelUpHybridCache. Program against the GA HybridCache abstraction the satellite registers.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on LevelUp.ServiceDefaults.Caching:

Package Downloads
LevelUp.ServiceDefaults.Azure

Azure satellite for LevelUp.ServiceDefaults: Azure Monitor OpenTelemetry distro and Key Vault configuration provider, each gated on config. No-op otherwise.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.5.0 1,302 8/29/2026
0.4.0 1,874 7/7/2026
0.4.0-preview.1 148 6/26/2026
0.3.5 590 6/26/2026
0.3.4 181 6/24/2026
0.3.3 189 6/23/2026
0.3.2 138 6/23/2026
0.3.1 160 6/22/2026
0.3.0 140 6/21/2026
0.3.0-preview.1 83 6/21/2026
0.2.0 123 6/21/2026
0.1.0 126 6/21/2026