Skip to main content
skill CX Codex

caching — Claude Code skill: .NET caching guide

Guides caching work in .NET 10 applications with HybridCache, response caching, and distributed cache patterns. Use it when reducing database reads or planning cache invalidation.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add codewithmukesh/dotnet-claude-kit --skill caching -a claude-code
Open on GitHub

What caching does

This skill guides caching work in .NET 10 applications with HybridCache, which combines an in-memory L1 cache and a distributed L2 layer. It covers stampede protection, explicit expiration periods, and user-scoped keys for mutable user data.

For full HTTP responses, it describes output caching middleware with policies for expiration, route-value variation, and cache tags. Cache read operations rather than writes. After a mutation, remove an entry with cache.RemoveAsync() or evict response entries by tag.

The skill recommends HybridCache for new code because manual IDistributedCache cache-aside needs serialization and does not provide L1/L2 layering or built-in stampede protection. Redis can serve as the L2 backend in a distributed deployment.

What it helps with

  • cache a database read with HybridCache
  • invalidate a cache entry after updating data
  • configure full HTTP response caching
  • scope user data cache keys by user ID
  • add Redis as an L2 distributed cache

Example prompts for the agent

  • Add HybridCache for product reads and invalidate the entry after an update.
  • Configure output caching for the product list and evict it by tag after PUT.
  • Review this IDistributedCache cache-aside code and replace it with HybridCache where appropriate.

How to install caching

1Skills CLI (Vercel)

npx skills add codewithmukesh/dotnet-claude-kit --skill caching -a claude-code

2Manually: put the files in place

~/.claude/skills/caching/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/caching/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about caching

How do I install caching in Claude Code?

Run npx skills add codewithmukesh/dotnet-claude-kit --skill caching -a claude-code, then run npx skills add codewithmukesh/dotnet-claude-kit --skill caching -a claude-code again. The skill file is placed at ~/.claude/skills/caching/SKILL.md.

What does the caching skill do?

It provides guidance for .NET 10 caching with HybridCache, output caching, response caching, and distributed cache patterns. It also covers invalidation and entry expiration.

Why does it recommend HybridCache for new code?

HybridCache combines local L1 and distributed L2 caching and includes stampede protection. Manual IDistributedCache cache-aside requires serialization and lacks that layered setup.

Source text

SKILL.md · MIT
---
name: caching
description: >
  Caching strategies for .NET 10 applications. Covers HybridCache (the default),
  output caching, response caching, and distributed cache patterns.
  Load this skill when implementing caching, optimizing read performance, reducing
  database load, or when the user mentions "cache", "HybridCache", "Redis",
  "output cache", "response cache", "distributed cache", "IMemoryCache",
  "cache invalidation", "stampede protection", or "cache-aside".
---

# Caching

## Core Principles

1. **HybridCache is the default** — .NET 9+ introduced `HybridCache` as the unified caching abstraction. It combines in-memory (L1) and distributed (L2) caching with stampede protection. See ADR-004.
2. **Cache reads, not writes** — Cache GET operations. Invalidate on mutations. Never cache POST/PUT/DELETE responses.
3. **Output caching for entire responses** — When the full HTTP response can be cached (public APIs, static data), use output caching middleware.
4. **Set explicit TTLs** — Every cached item needs an expiration. No unbounded caches.

## Patterns

### HybridCache (Recommended Default)

```csharp
// Program.cs
builder.Services.AddHybridCache(options =>
{
    options.DefaultEntryOptions = new HybridCacheEntryOptions
    {
        Expiration = TimeSpan.FromMinutes(5),
        LocalCacheExpiration = TimeSpan.FromMinutes(2)
    };
});

// Optional: Add Redis as the L2 distributed cache
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = builder.Configuration.GetConnectionString("Redis");
});
```

```csharp
// Usage in a handler
public class GetProduct
{
    public record Query(Guid Id);
    public record Response(Guid Id, string Name, decimal Price);

    internal class Handler(AppDbContext db, HybridCache cache)
    {
        public async Task<Response?> Handle(Query query, CancellationToken ct)
        {
            return await cache.GetOrCreateAsync(
                $"products:{query.Id}",
                async token => await db.Products
                    .Where(p => p.Id == query.Id)
                    .Select(p => new Response(p.Id, p.Name, p.Price))
                    .FirstOrDefaultAsync(token),
                new HybridCacheEntryOptions
                {
                    Expiration = TimeSpan.FromMinutes(10)
                },
                cancellationToken: ct);
        }
    }
}
```

### Cache Invalidation

```csharp
// Invalidate on mutation
public class UpdateProduct
{
    internal class Handler(AppDbContext db, HybridCache cache)
    {
        public async Task<Result> Handle(Command command, CancellationToken ct)
        {
            var product = await db.Products.FindAsync([command.Id], ct);
            if (product is null) return Result.Failure("Product not found");

            product.Update(command.Name, command.Price);
            await db.SaveChangesAsync(ct);

            // Invalidate the cached entry
            await cache.RemoveAsync($"products:{command.Id}", ct);

            return Result.Success();
        }
    }
}
```

### Output Caching (Full Response Caching)

```csharp
// Program.cs
builder.Services.AddOutputCache(options =>
{
    options.AddBasePolicy(b => b.NoCache()); // Don't cache by default

    options.AddPolicy("ProductList", b => b
        .Expire(TimeSpan.FromMinutes(5))
        .Tag("products"));

    options.AddPolicy("ProductById", b => b
        .Expire(TimeSpan.FromMinutes(10))
        .SetVaryByRouteValue("id")
        .Tag("products"));
});

app.UseOutputCache();

// Apply to endpoints
group.MapGet("/", ListProducts).CacheOutput("ProductList");
group.MapGet("/{id:guid}", GetProduct).CacheOutput("ProductById");

// Invalidate by tag on mutations
group.MapPut("/{id:guid}", async (Guid id, UpdateProductRequest request,
    IOutputCacheStore store, CancellationToken ct) =>
{
    // ... update logic ...
    await store.EvictByTagAsync("products", ct);
    return TypedResults.NoContent();
});
```

### Cache-Aside Pattern (Legacy)

> **Prefer HybridCache** for all new code. Manual `IDistributedCache` cache-aside lacks stampede
> protection, requires manual serialization, and has no L1/L2 layering. Use only when
> integrating with existing code that already uses `IDistributedCache` directly.

## Anti-patterns

### Don't Cache Without Expiration

```csharp
// BAD — cache lives forever, stale data guaranteed
await cache.SetStringAsync(key, value);

// GOOD — always set TTL
await cache.SetStringAsync(key, value, new DistributedCacheEntryOptions
{
    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
});
```

### Don't Cache Mutable User-Specific Data

```csharp
// BAD — caching user's cart with a global key
await cache.GetOrCreateAsync("shopping-cart", ...);

// GOOD — include user ID in key
await cache.GetOrCreateAsync($"shopping-cart:{userId}", ...);
```

### Don't Build Your Own Stampede Protection

```csharp
// BAD — manual lock to prevent cache stampede
private static readonly SemaphoreSlim Lock = new(1, 1);
await Lock.WaitAsync();
try { /* check cache, populate if missing */ }
finally { Lock.Release(); }

// GOOD — HybridCache has built-in stampede protection
await hybridCache.GetOrCreateAsync(key, factory);
```

## Decision Guide

| Scenario | Recommendation |
|----------|---------------|
| General data caching | HybridCache (`GetOrCreateAsync`) |
| Full HTTP response | Output caching with `.CacheOutput()` |
| Frequently read, rarely written | HybridCache with longer TTL |
| User-specific data | HybridCache with user-scoped key |
| Cache invalidation on write | `cache.RemoveAsync()` or output cache tags |
| Distributed deployment | HybridCache + Redis L2 backend |
| Single-server deployment | HybridCache with in-memory only |

© codewithmukesh · MIT · codewithmukesh/dotnet-claude-kit

Similar skills

Everything for Backend and APIs →

From the same set46

workflow-mastery Workflow guide and configurations for .NET development in Claude Code. It introduces parallel sessions via git worktrees, verification loops, and context budgeting. CX Codex +1 2,800installs aspire Guides .NET Aspire setup for local service orchestration, resource wiring, and service discovery. Use it when working with AppHost or shared service defaults. CX Codex +1 2,800installs convention-learner Reads your existing code and turns it into project rules: naming, folder layout, test organisation. Useful when the agent writes code in the wrong style. CX Codex +1 2,800installs api-versioning Guides your agent through ASP.NET Core API versioning using the Asp.Versioning library. Useful when adding URL segment versions or deprecating legacy contracts. CX Codex +1 2,800installs health-check Grades a .NET project across eight dimensions with letter marks A–F and a rolled-up GPA. Useful at onboarding, before a release, and after a cleanup sprint. CX Codex +1 2,800installs openapi Configures native OpenAPI generation in .NET 10 without Swashbuckle. Helps the agent handle transformers, TypedResults metadata, and security schemes. CX Codex +1 2,800installs configuration Guides .NET 10 configuration with typed options, validation, and secret handling. Use it when working with appsettings files or environment variables. CX Codex +1 2,800installs scalar A skill that walks the agent through wiring Scalar into a .NET 10 app as a Swagger UI replacement, with themes, auth prefill and multiple documents. CX Codex +1 2,800installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor