VapeCache.Extensions.Aspire 1.2.12

There is a newer version of this package available.
See the version list below for details.
dotnet add package VapeCache.Extensions.Aspire --version 1.2.12
                    
NuGet\Install-Package VapeCache.Extensions.Aspire -Version 1.2.12
                    
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="VapeCache.Extensions.Aspire" Version="1.2.12" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="VapeCache.Extensions.Aspire" Version="1.2.12" />
                    
Directory.Packages.props
<PackageReference Include="VapeCache.Extensions.Aspire" />
                    
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 VapeCache.Extensions.Aspire --version 1.2.12
                    
#r "nuget: VapeCache.Extensions.Aspire, 1.2.12"
                    
#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 VapeCache.Extensions.Aspire@1.2.12
                    
#: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=VapeCache.Extensions.Aspire&version=1.2.12
                    
Install as a Cake Addin
#tool nuget:?package=VapeCache.Extensions.Aspire&version=1.2.12
                    
Install as a Cake Tool

VapeCache.Extensions.Aspire

Wire VapeCache into .NET Aspire without Program.cs boilerplate. You get service discovery, health checks, telemetry, and wrapper endpoints in one fluent chain.

Features

Service Discovery - Auto-configure Redis connection from Aspire resources ✅ Health Checks - Redis connectivity and circuit breaker monitoring ✅ Telemetry - Cache/Redis metrics visible in Aspire Dashboard (plus EF Core cache telemetry when installed) ✅ Distributed Tracing - End-to-end traces for Redis operations (plus EF Core cache traces when installed) ✅ Wrapper Endpoints - MapVapeCacheEndpoints(...) for diagnostics and MapVapeCacheAdminEndpoints(...) for control routes ✅ SEQ by Default - OTLP exporter falls back to Seq when no endpoint is configured ✅ Fluent Telemetry API - .UseSeq(...), custom headers, and wrapper callbacks ✅ Fluent Stampede Profiles - .WithCacheStampedeProfile(...) with optional overrides ✅ ASP.NET Core Pipeline Hook - .WithAspNetCoreOutputCaching(...) for MVC/Blazor/minimal output cache store ✅ Failover Affinity Hints - .WithFailoverAffinityHints(...) for cluster/web-garden sticky-session routing ✅ Kitchen Sink Profile - .WithKitchenSink(...) or AddVapeCacheKitchenSink(...) for one-call full integration ✅ Production Observability Profile - .WithProductionObservability(...) or AddVapeCacheWithProductionObservability(...)Low Ceremony - Single fluent chain to enable all major features

Installation

dotnet add package VapeCache.Extensions.Aspire

Quick Start

1. AppHost (Aspire Orchestrator)

var builder = DistributedApplication.CreateBuilder(args);

// Add Redis resource
var redis = builder.AddRedis("redis");

// Add your API with Redis reference
var api = builder.AddProject<Projects.MyApi>("api")
    .WithReference(redis);  // Injects connection string

builder.Build().Run();

2. API Project (Your Application)

var builder = WebApplication.CreateBuilder(args);

// Production baseline: observability on, app-hosted debug/admin endpoint surface remains opt-in.
var vapeCache = builder.AddVapeCache()
    .WithRedisFromAspire("redis")
    .UseTransport(VapeCacheAspireTransportMode.Balanced)
    .WithCacheStampedeProfile(CacheStampedeProfile.Balanced)
    .WithProductionObservability();

// Optional app-hosted diagnostics surface (protect with auth/network policy):
vapeCache.WithAutoMappedEndpoints(options =>
{
    options.Enabled = true;
    options.EnableDashboard = true;
    options.EnableLiveStream = true;
});

var app = builder.Build();

app.UseVapeCacheOutputCaching();
app.UseVapeCacheFailoverAffinityHints();
app.MapHealthChecks("/health");
app.Run();

3. Use the Cache

public class UserService
{
    private readonly ICacheService _cache;

    public UserService(ICacheService cache) => _cache = cache;

    public async Task<User?> GetUserAsync(int id, CancellationToken ct)
    {
        var key = $"user:{id}";
        return await _cache.GetOrSetAsync(
            key,
            async ct => await _db.Users.FindAsync(id, ct),
            (writer, user) => JsonSerializer.Serialize(writer, user),
            bytes => JsonSerializer.Deserialize<User>(bytes),
            new CacheEntryOptions(
                Ttl: TimeSpan.FromMinutes(5),
                Intent: new CacheIntent(CacheIntentKind.ReadThrough, Reason: "user detail lookup")),
            ct);
    }
}

Aspire Dashboard

Navigate to http://localhost:15888 to view:

Metrics

  • cache.current.backend - Current active backend (1=redis, 0=in-memory, -1=unknown) - Real-time visibility
  • cache.get.hits - Cache hits (by backend: redis, in-memory, hybrid)
  • cache.get.misses - Cache misses
  • cache.fallback.to_memory - Circuit breaker fallback events
  • cache.set.payload.bytes - Payload size histogram for writes (large-key visibility)
  • cache.set.large_key - Large payload writes (>64 KB)
  • cache.evictions - In-memory eviction count (tagged by eviction reason)
  • cache.op.ms - Operation latency
  • redis.cmd.calls - Redis commands executed
  • redis.pool.wait.ms - Connection pool wait time
  • redis.mux.lane.inflight - Current in-flight operations by lane (connection.id tag)
  • redis.mux.lane.inflight.utilization - In-flight utilization ratio by lane
  • redis.mux.lane.bytes.sent - Cumulative bytes sent by lane
  • redis.mux.lane.bytes.received - Cumulative bytes received by lane
  • redis.mux.lane.operations - Cumulative operations started by lane
  • redis.mux.lane.failures - Cumulative transport/connect failures by lane
  • efcore.cache.query.execution.completed - EF Core query executions observed by interceptor cache pipeline
  • efcore.cache.query.execution.failed - EF Core query execution failures observed by interceptor cache pipeline
  • efcore.cache.query.execution.ms - EF Core query execution duration histogram
  • efcore.cache.invalidation.zone.invalidated - EF Core-derived zone invalidations completed
  • efcore.cache.invalidation.zone.failed - EF Core-derived zone invalidations failed

Traces

End-to-end distributed traces showing:

  • Cache operation → Pool acquisition → Redis command → Response parsing
  • EF Core second-level cache events when VapeCache.Extensions.EntityFrameworkCore.OpenTelemetry is installed

Health Checks

  • redis: Redis connectivity (PING validation)
  • vapecache: Circuit breaker state and cache statistics

API Reference

AddVapeCache()

Registers core VapeCache services.

builder.AddVapeCache()

AddVapeCacheKitchenSink(configure?)

Adds core VapeCache services and applies a composed Aspire profile in one call:

builder.AddVapeCacheKitchenSink(options =>
{
    options.RedisConnectionName = "redis";
    options.ConfigureEndpoints = endpoint => endpoint.Enabled = true;
});

This composes:

  • WithRedisFromAspire(...)
  • UseTransport(...)
  • WithCacheStampedeProfile(...)
  • WithHealthChecks()
  • WithAspireTelemetry()
  • WithStartupWarmup()
  • WithAspNetCoreOutputCaching()
  • WithFailoverAffinityHints()
  • WithAutoMappedEndpoints(...)

AddVapeCacheWithProductionObservability(configure?)

Adds core VapeCache services and wires production observability defaults:

builder.AddVapeCacheWithProductionObservability(options =>
{
    options.EnableStartupWarmup = true; // optional
});

Default composition:

  • WithHealthChecks()
  • WithAspireTelemetry()

Optional composition:

  • WithStartupWarmup(...)
  • WithRedisExporterMetrics(...)

WithRedisFromAspire(connectionName)

Uses the AppHost resource name so connection details come from Aspire service discovery.

.WithRedisFromAspire("redis")  // Matches AppHost resource name

WithHealthChecks()

Adds health checks for Redis and VapeCache.

.WithHealthChecks()

// Map in your host:
app.MapHealthChecks("/health");
app.MapHealthChecks("/health/redis", new HealthCheckOptions
{
    Predicate = check => check.Name == "redis"
});

WithAspireTelemetry()

Configures OpenTelemetry for VapeCache metrics/traces and OTLP export. Also registers the EF Core cache meter/source (VapeCache.EFCore.Cache) so EF Core cache telemetry is auto-collected when the EF Core OTEL package is installed. Resolution order for OTLP endpoint:

  1. options.OtlpEndpoint
  2. OpenTelemetry:Otlp:Endpoint (configuration)
  3. OTEL_EXPORTER_OTLP_ENDPOINT (environment)
  4. DOTNET_DASHBOARD_OTLP_ENDPOINT_URL (Aspire dashboard fallback)
  5. Seq default: http://localhost:5341/ingest/otlp
.WithAspireTelemetry()

WithKitchenSink(configure?)

Composes all major Aspire integration extensions for an existing builder chain:

builder.AddVapeCache()
    .WithKitchenSink(options =>
    {
        options.RedisConnectionName = "redis";
        options.EnableRedisExporterMetrics = true;
        options.ConfigureEndpoints = endpoint =>
        {
            endpoint.Enabled = true;
            endpoint.EnableLiveStream = true;
        };
    });

WithProductionObservability(configure?)

Production observability baseline without enabling app-hosted dashboard/admin route surfaces:

builder.AddVapeCache()
    .WithProductionObservability(options =>
    {
        options.EnableStartupWarmup = true;         // optional
        options.EnableRedisExporterMetrics = false; // optional
    });

WithCacheStampedeProfile(profile, configure?)

Applies named stampede defaults with optional fluent overrides.

.WithCacheStampedeProfile(
    CacheStampedeProfile.Balanced,
    options => options.WithLockWaitTimeout(TimeSpan.FromMilliseconds(600)));

WithAspNetCoreOutputCaching(configureOutputCache?, configureStore?)

Adds ASP.NET Core output caching and swaps the default store for VapeCacheOutputCacheStore.

builder.AddVapeCache()
    .WithAspNetCoreOutputCaching(
        configureOutputCache: options =>
        {
            options.AddBasePolicy(policy => policy.Expire(TimeSpan.FromSeconds(30)));
        },
        configureStore: store =>
        {
            store.KeyPrefix = "vapecache:output";
            store.DefaultTtl = TimeSpan.FromSeconds(30);
            store.EnableTagIndexing = true;
        });

var app = builder.Build();
app.UseVapeCacheOutputCaching();

WithFailoverAffinityHints(configure?)

Adds options for middleware that emits node-affinity hints during failover:

builder.AddVapeCache()
    .WithFailoverAffinityHints(options =>
    {
        options.NodeId = Environment.MachineName;
        options.CookieName = "VapeCacheAffinity";
    });

var app = builder.Build();
app.UseVapeCacheFailoverAffinityHints();

MapVapeCacheEndpoints(prefix, includeBreakerControlEndpoints, includeLiveStreamEndpoint, includeIntentEndpoints, includeDashboardEndpoint)

Maps wrapper-facing HTTP endpoints:

  • GET {prefix}/status
  • GET {prefix}/stats
  • GET {prefix}/stream (Server-Sent Events realtime channel)
  • GET {prefix}/dashboard (built-in realtime dashboard UI, optional)
  • GET {prefix}/dashboard/dashboard.js (dashboard script)
  • GET {prefix}/dashboard/dashboard.css (dashboard styles)
  • optional legacy: POST {prefix}/breaker/force-open
  • optional legacy: POST {prefix}/breaker/clear

Minimal API contract notes:

  • status returns backend state, cache stats, breaker state, and autoscaler diagnostics (when available).
  • stats returns cache counters + hit-rate + autoscaler diagnostics (when available).
  • stream emits SSE event: vapecache-stats frames with the live sample payload.
  • control endpoints are intentionally opt-in and should be mapped on an internal admin prefix.
var app = builder.Build();

app.MapVapeCacheEndpoints("/vapecache");

// Admin controls on an internal control-plane prefix:
app.MapVapeCacheAdminEndpoints("/internal/vapecache-admin");

MapVapeCacheAdminEndpoints(prefix = "/vapecache/admin", requireAuthorization = false, authorizationPolicy = null)

Maps admin-only breaker control endpoints:

  • POST {prefix}/breaker/force-open
  • POST {prefix}/breaker/clear

Set requireAuthorization: true to apply RequireAuthorization() in one line, or pass authorizationPolicy for RequireAuthorization(policy). Keep this prefix internal-only.

app.MapVapeCacheAdminEndpoints(
    prefix: "/internal/vapecache-admin",
    requireAuthorization: true,
    authorizationPolicy: "VapeCacheAdmin");

GET {prefix}/status and GET {prefix}/stats include the stampede hardening counters:

  • stampedeKeyRejected
  • stampedeLockWaitTimeout
  • stampedeFailureBackoffRejected

They also include autoscaler diagnostics when IRedisMultiplexerDiagnostics is available:

  • autoscaler.currentConnections
  • autoscaler.targetConnections
  • autoscaler.highSignalCount
  • autoscaler.timeoutRatePerSec
  • autoscaler.rollingP95LatencyMs
  • autoscaler.rollingP99LatencyMs
  • autoscaler.unhealthyConnections
  • autoscaler.reconnectFailureRatePerSec
  • autoscaler.scaleEventsInCurrentMinute
  • autoscaler.maxScaleEventsPerMinute
  • autoscaler.frozen
  • autoscaler.freezeReason
  • autoscaler.lastScaleDirection
  • autoscaler.lastScaleReason

They also include lane diagnostics for graphing:

  • lanes[].laneIndex
  • lanes[].connectionId
  • lanes[].role (read, write, or read-write)
  • lanes[].writeQueueDepth
  • lanes[].inFlight
  • lanes[].maxInFlight
  • lanes[].inFlightUtilization
  • lanes[].bytesSent
  • lanes[].bytesReceived
  • lanes[].operations
  • lanes[].failures
  • lanes[].healthy

/stream emits event: vapecache-stats frames with a JSON payload compatible with Blazor realtime chart components.

Lane query/panel pack for Aspire Metrics explorer:

Example payload:

{
  "timestampUtc": "2026-02-24T20:54:00.0000000+00:00",
  "currentBackend": "redis",
  "hits": 123456,
  "misses": 7890,
  "setCalls": 45678,
  "removeCalls": 321,
  "fallbackToMemory": 12,
  "redisBreakerOpened": 2,
  "stampedeKeyRejected": 0,
  "stampedeLockWaitTimeout": 1,
  "stampedeFailureBackoffRejected": 0,
  "hitRate": 0.9399,
  "autoscaler": {
    "enabled": true,
    "currentConnections": 6,
    "targetConnections": 7,
    "minConnections": 4,
    "maxConnections": 16,
    "currentReadLanes": 3,
    "currentWriteLanes": 3,
    "highSignalCount": 2,
    "avgInflightUtilization": 0.81,
    "avgQueueDepth": 7.4,
    "maxQueueDepth": 34,
    "timeoutRatePerSec": 0.0,
    "rollingP95LatencyMs": 22.7,
    "rollingP99LatencyMs": 34.0,
    "unhealthyConnections": 0,
    "reconnectFailureRatePerSec": 0.0,
    "scaleEventsInCurrentMinute": 1,
    "maxScaleEventsPerMinute": 2,
    "frozen": false,
    "frozenUntilUtc": null,
    "freezeReason": null,
    "lastScaleEventUtc": "2026-02-24T21:02:08.0000000+00:00",
    "lastScaleDirection": "up",
    "lastScaleReason": "inflight+queue"
  },
  "lanes": [
    {
      "laneIndex": 0,
      "connectionId": 12,
      "role": "read-write",
      "writeQueueDepth": 1,
      "inFlight": 22,
      "maxInFlight": 128,
      "inFlightUtilization": 0.171875,
      "bytesSent": 8021569,
      "bytesReceived": 14482991,
      "operations": 54120,
      "failures": 0,
      "healthy": true
    }
  ]
}

WithAutoMappedEndpoints(options => ...)

Registers a startup filter that maps VapeCache endpoints automatically so you don't need to wire them in Program.cs. Endpoint mapping is disabled until options.Enabled = true.

builder.AddVapeCache()
    .WithAutoMappedEndpoints(options =>
    {
        options.Enabled = true;
        options.Prefix = "/cache";
        options.AdminPrefix = "/internal/cache-admin";
        options.IncludeBreakerControlEndpoints = true;
        options.RequireAuthorizationOnAdminEndpoints = true;
        options.AdminAuthorizationPolicy = "VapeCacheAdmin";
        options.EnableLiveStream = true;
        options.EnableDashboard = true;
        options.LiveSampleInterval = TimeSpan.FromMilliseconds(500);
        options.LiveChannelCapacity = 512;
    });

When IncludeBreakerControlEndpoints is enabled, auto-mapping keeps Prefix read-only and maps control routes under AdminPrefix.

Dashboard UI frontend source is maintained in VapeCache.Extensions.Aspire/dashboard-ui (Vite + TypeScript). To rebuild the shipped dashboard assets (DashboardAssets/):

cd VapeCache.Extensions.Aspire/dashboard-ui
npm install
npm run build

Enterprise transport/autoscaler architecture and tuning:

Custom Wrapper/Exporter Scenario

builder.AddVapeCache()
    .WithAspireTelemetry(options =>
    {
        options.UseSeq(seqBaseUrl: "http://localhost:5341", apiKey: "dev-seq-key")
               .WithOtlpHeader("x-env", "dev")
               .AddMetricsConfiguration(m =>
               {
                   // Add custom metric exporter extensions here
               })
               .AddTracingConfiguration(t =>
               {
                   // Add custom trace exporter extensions here
               });
    });

Health Check Details

Redis Health Check (redis)

  • Healthy: Redis is reachable and can execute commands
  • Degraded: Connection pool timeout (under pressure)
  • Unhealthy: Redis connection failed

VapeCache Health Check (vapecache)

  • Healthy: Redis is the active backend and the circuit breaker is closed
  • Degraded: Manual failover is enabled, the breaker is open, or the current backend is not Redis
  • Unhealthy: The health probe itself failed while reading cache state or dependencies

Diagnostic Data:

{
  "circuit_breaker_open": false,
  "consecutive_failures": 0,
  "hit_count": 12345,
  "miss_count": 678,
  "hit_rate": 0.948
}

Production Deployment

Azure Container Apps

azd up  # Deploy via Aspire

Health checks are automatically configured for liveness/readiness probes.

Kubernetes

livenessProbe:
  httpGet:
    path: /health/redis
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 30

readinessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 10

License

Apache 2.0

Blazor Realtime Example

See docs/BLAZOR_DASHBOARD_EXAMPLE.md for a full Blazor component and stream client using:

  • GET /vapecache/stream (SSE realtime feed)
  • GET /vapecache/status (snapshot fallback)

See Also

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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.22 113 4/26/2026
1.2.21 107 4/26/2026
1.2.20 129 4/13/2026
1.2.19 100 4/12/2026
1.2.18 112 3/30/2026
1.2.17 102 3/27/2026
1.2.15 106 3/19/2026
1.2.14 107 3/18/2026
1.2.13 109 3/14/2026
1.2.12 110 3/14/2026
1.2.11 106 3/14/2026
1.2.10 108 3/13/2026
1.2.9 109 3/12/2026
1.2.8 105 3/12/2026
1.2.7 105 3/11/2026
1.2.6 104 3/11/2026
1.2.5 108 3/11/2026
1.2.4 119 3/10/2026
1.2.3 107 3/10/2026
1.2.2 105 3/10/2026
Loading failed

1.2.10 release: stabilized Redis telemetry parser metrics, hardened connection registration across DI and Autofac, and refreshed production-capable documentation.