Output Caching for .NET Minimal APIs
Overview
Section titled “Overview”Granit.Http.OutputCaching wraps ASP.NET Core’s native
OutputCaching
middleware with Granit defaults: authenticated responses are excluded (GDPR), cache keys
automatically vary by tenant ID when multi-tenancy is active, and tag-based eviction lets
modules invalidate their own cached responses.
The in-memory store works out of the box. For distributed caching across pods, install
Granit.Http.OutputCaching.StackExchangeRedis.
[DependsOn(typeof(GranitHttpOutputCachingModule))]public class AppModule : GranitModule { }{ "OutputCaching": { "DefaultExpiration": "00:01:00" }}[DependsOn(typeof(GranitHttpOutputCachingStackExchangeRedisModule))]public class AppModule : GranitModule { }{ "OutputCaching": { "DefaultExpiration": "00:01:00", "Redis": { "IsEnabled": true, "Configuration": "redis:6379,password=secret", "InstanceName": "myapp:oc:", "RequireTls": true } }}Middleware ordering
Section titled “Middleware ordering”app.UseRouting();app.UseCors(); // Granit.Http.Corsapp.UseGranitOutputCaching(); // ← AFTER CORS, AFTER routingapp.UseAuthorization();GDPR-safe defaults
Section titled “GDPR-safe defaults”The base policy automatically applied to every cached endpoint:
| Rule | Default | Rationale |
|---|---|---|
| Authenticated responses excluded | true | Prevents personal data leaks across users |
| Set-Cookie responses excluded | true | Session tokens must never be shared |
| Default TTL | 60 s | Safe for most read-heavy APIs |
| VaryByQuery keys | page, pageSize, sort, filter, q, include, expand | Covers pagination, search, and resource expansion |
Multi-tenant isolation
Section titled “Multi-tenant isolation”When Granit.MultiTenancy is active, cache keys automatically vary by ICurrentTenant.Id:
- Tenant A’s cached response is never served to Tenant B
- Each cached response is tagged
tenant:{id}for per-tenant bulk eviction - Without multi-tenancy, the policy is a no-op (zero overhead)
Caching endpoints
Section titled “Caching endpoints”Use .CacheOutput() on minimal API endpoints:
app.MapGet("/api/v1/reference-data", () => GetReferenceData()) .CacheOutput(p => p.Tag("reference-data"));
app.MapGet("/api/v1/products/{id}", (int id) => GetProduct(id)) .CacheOutput();Named policies
Section titled “Named policies”| Policy | Behavior |
|---|---|
GranitDefault | GDPR + tenant-aware + configured TTL |
GranitNoCache | Explicitly disables caching for an endpoint |
app.MapGet("/api/v1/health", () => "OK") .CacheOutput(GranitOutputCachePolicyNames.NoCache);Tag-based eviction
Section titled “Tag-based eviction”Inject IOutputCacheEvictionService to invalidate cached responses:
public sealed class UpdateProductHandler(IOutputCacheEvictionService eviction){ public async Task HandleAsync(UpdateProductCommand cmd, CancellationToken ct) { // ... update product ...
// Evict all cached responses tagged "products" await eviction.EvictModuleCacheAsync("products", ct); }}Per-tenant eviction
Section titled “Per-tenant eviction”// Evict all cached responses for a specific tenantawait eviction.EvictTenantCacheAsync(tenantId, ct);Redis backend
Section titled “Redis backend”Granit.Http.OutputCaching.StackExchangeRedis replaces the in-memory store with Redis
for cross-pod cache sharing.
Health check
Section titled “Health check”builder.Services.AddHealthChecks() .AddGranitRedisOutputCacheHealthCheck();Returns Healthy, Degraded (latency > 100 ms), or Unhealthy (unreachable).
Tagged readiness and startup for Kubernetes probes.
Configuration reference
Section titled “Configuration reference”| Property | Default | Description |
|---|---|---|
OutputCaching:DefaultExpiration | 00:01:00 | Default response cache TTL |
OutputCaching:VaryByQueryKeys | ["page","pageSize","sort","filter","q","include","expand"] | Query params included in cache key |
OutputCaching:EnableTenantIsolation | true | Vary by tenant ID when available |
OutputCaching:ExcludeAuthenticatedResponses | true | Skip caching for authenticated users |
OutputCaching:Redis:IsEnabled | true | Enable/disable Redis store |
OutputCaching:Redis:Configuration | localhost:6379 | Redis connection string |
OutputCaching:Redis:InstanceName | dd:oc: | Redis key prefix |
OutputCaching:Redis:RequireTls | true | Enforce TLS for Redis |
Public API summary
Section titled “Public API summary”| Category | Key types | Package |
|---|---|---|
| Module | GranitHttpOutputCachingModule | Granit.Http.OutputCaching |
| Options | OutputCachingOptions | Granit.Http.OutputCaching |
| Policies | GranitOutputCachePolicyNames, GdprCompliantOutputCachePolicy, TenantAwareOutputCachePolicy | Granit.Http.OutputCaching |
| Eviction | IOutputCacheEvictionService | Granit.Http.OutputCaching |
| Extensions | AddGranitOutputCaching(), UseGranitOutputCaching() | Granit.Http.OutputCaching |
| Redis module | GranitHttpOutputCachingStackExchangeRedisModule | Granit.Http.OutputCaching.StackExchangeRedis |
| Redis options | RedisOutputCachingOptions | Granit.Http.OutputCaching.StackExchangeRedis |
| Redis extensions | AddGranitRedisOutputCache(), AddGranitRedisOutputCacheHealthCheck() | Granit.Http.OutputCaching.StackExchangeRedis |
Compliance
Section titled “Compliance”- GDPR Article 5(1)(f) — integrity and confidentiality: authenticated responses excluded from caching by default
- ISO 27001 A.10.1 — encryption in transit: Redis TLS enforced by default
- ISO 27001 A.9.4 — access control: tenant-isolated cache keys prevent cross-tenant data leakage
Further reading
Section titled “Further reading”- Caching — distributed caching with
IFusionCache - CORS — must be registered before output caching
- Multi-tenancy — tenant context used for cache isolation
- ASP.NET Core Output Caching — upstream documentation