Caching

codewithmukesh/dotnet-claude-kit/skills/caching

by codewithmukesh23300897f4d1No license754 starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 2 months ago

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".

Instructions onlySoftware Development
AI-generated overview

Guidance on caching strategies for .NET 10 apps, covering HybridCache, output caching, response caching and distributed cache patterns.

What it does
This skill provides reference guidance and code examples for implementing caching in .NET 10 applications. It covers HybridCache as the recommended default, output caching for full HTTP responses, cache invalidation on mutations, and the legacy cache-aside pattern. It also lists anti-patterns such as caching without expiration, caching mutable user-specific data under global keys, and hand-rolled stampede protection, plus a decision guide mapping scenarios to recommendations.
When to use it
Use it 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.
Requirements
No scripts or tooling are required; it is instructions only. The examples assume a .NET 10 application and optionally a Redis instance as an L2 distributed cache.

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.csbuilder.Services.AddHybridCache(options =>{    options.DefaultEntryOptions = new HybridCacheEntryOptions    {        Expiration = TimeSpan.FromMinutes(5),        LocalCacheExpiration = TimeSpan.FromMinutes(2)    };});
// Optional: Add Redis as the L2 distributed cachebuilder.Services.AddStackExchangeRedisCache(options =>{    options.Configuration = builder.Configuration.GetConnectionString("Redis");});
csharp
// Usage in a handlerpublic 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 mutationpublic 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.csbuilder.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 endpointsgroup.MapGet("/", ListProducts).CacheOutput("ProductList");group.MapGet("/{id:guid}", GetProduct).CacheOutput("ProductById");
// Invalidate by tag on mutationsgroup.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 guaranteedawait cache.SetStringAsync(key, value);
// GOOD — always set TTLawait 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 keyawait cache.GetOrCreateAsync("shopping-cart", ...);
// GOOD — include user ID in keyawait cache.GetOrCreateAsync($"shopping-cart:{userId}", ...);

Don't Build Your Own Stampede Protection

csharp
// BAD — manual lock to prevent cache stampedeprivate 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 protectionawait hybridCache.GetOrCreateAsync(key, factory);

Decision Guide

ScenarioRecommendation
General data cachingHybridCache (GetOrCreateAsync)
Full HTTP responseOutput caching with .CacheOutput()
Frequently read, rarely writtenHybridCache with longer TTL
User-specific dataHybridCache with user-scoped key
Cache invalidation on writecache.RemoveAsync() or output cache tags
Distributed deploymentHybridCache + Redis L2 backend
Single-server deploymentHybridCache with in-memory only

Source and attribution

Source:codewithmukesh/dotnet-claude-kitinskills/cachingat commit2330089

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal

More from codewithmukesh/dotnet-claude-kit

Wrap Up

codewithmukesh

Captures end-of-session work, pending tasks and learnings into a handoff file, and reloads it at session start.

Productivity & Workflow754updated 2 months ago

Workflow Mastery

codewithmukesh

Claude Code workflow mastery for .NET developers. Covers parallel execution with git worktrees, plan mode strategy, verification loops, auto-formatting hooks, permission setup for dotnet CLI, prompting techniques, subagent patterns, and context discipline — token budget management, MCP-first navigation, lazy loading, and subagent isolation — all adapted for the .NET ecosystem. Load this skill when setting up Claude Code for a .NET project, optimizing workflows, running parallel sessions, when context is running low or sessions feel sluggish, when exploring a large codebase efficiently, or when the user mentions "productivity", "workflow", "parallel", "worktree", "plan mode", "permissions", "hooks", "10x", "setup Claude Code", "speed up development", "context", "tokens", "budget", "running out of context", "too many files", or "large codebase". Inspired by tips from Boris Cherny (creator of Claude Code) and the Anthropic team.

Awaiting classification754updated 2 months ago

Vertical Slice

codewithmukesh

Guides .NET developers in structuring applications with Vertical Slice Architecture, covering feature folders, endpoint grouping and handler patterns.

Software Development754updated 2 months ago

Testing

codewithmukesh

Testing strategy for .NET 10 applications. Covers xUnit v3, WebApplicationFactory for integration tests, Testcontainers for real database testing, Verify for snapshot testing, and the AAA pattern. Load this skill when writing tests, setting up test infrastructure, reviewing test coverage, or when the user mentions "test", "xUnit", "WebApplicationFactory", "Testcontainers", "integration test", "unit test", "bUnit", "snapshot test", "Verify", "test coverage", "AAA pattern", "WireMock", or "FakeTimeProvider".

Awaiting classification754updated 2 months ago

Tdd

codewithmukesh

Guided test-driven development workflow for .NET 10 using xUnit v3, WebApplicationFactory, Testcontainers, and Verify snapshots. Follows the strict red-green-refactor cycle. Use when: "TDD", "test-driven", "let's TDD this", "red green refactor", "write the test first", or when building a feature with clear acceptance criteria.

Awaiting classification754updated 2 months ago

Spec

codewithmukesh

Turns a vague feature idea into an agreed, persisted specification file through structured questioning rounds.

Productivity & Workflow754updated 2 months ago