$npx -y skills add wshaddix/dotnet-skills --skill caching-strategiesComprehensive caching patterns for ASP.NET Core Razor Pages applications. Covers output caching, response caching, memory caching, distributed caching with Redis, cache invalidation strategies, and HybridCache (.NET 9+). Use when implementing caching in Razor Pages applications,
| 1 | You are a senior ASP.NET Core architect specializing in caching strategies. When implementing caching in Razor Pages applications, apply these patterns to maximize performance while maintaining correctness. Target .NET 8+ with modern features and nullable reference types enabled. |
| 2 | |
| 3 | ## Rationale |
| 4 | |
| 5 | Caching is one of the most effective ways to improve application performance, but improper implementation leads to stale data, cache stampedes, and complexity. These patterns provide a hierarchy of caching solutions from simple to distributed, with clear guidance on when to use each. |
| 6 | |
| 7 | ## Caching Hierarchy |
| 8 | |
| 9 | | Strategy | Scope | Use Case | Latency | |
| 10 | |----------|-------|----------|---------| |
| 11 | | **Output Caching** | Server-wide | Full page responses | Low | |
| 12 | | **Response Caching** | Client + Proxy | Static pages, assets | Low | |
| 13 | | **Memory Cache** | Single instance | Short-lived, expensive data | Very Low | |
| 14 | | **Distributed Cache** | Multi-instance | Shared data across servers | Low-Medium | |
| 15 | | **HybridCache (.NET 9+)** | Multi-instance | Best of memory + distributed | Very Low | |
| 16 | |
| 17 | ## Pattern 1: Output Caching (Full Page) |
| 18 | |
| 19 | Use for pages that don't change often and don't contain user-specific data. |
| 20 | |
| 21 | ### Configuration |
| 22 | |
| 23 | ```csharp |
| 24 | // Program.cs |
| 25 | builder.Services.AddOutputCache(options => |
| 26 | { |
| 27 | options.AddBasePolicy(builder => |
| 28 | builder.Expire(TimeSpan.FromSeconds(10))); |
| 29 | options.AddPolicy("LongCache", builder => |
| 30 | builder.Expire(TimeSpan.FromMinutes(5))); |
| 31 | options.AddPolicy("AuthenticatedCache", builder => |
| 32 | builder.Expire(TimeSpan.FromMinutes(1)) |
| 33 | .Tag("user-specific")); |
| 34 | }); |
| 35 | |
| 36 | // Add middleware (order matters!) |
| 37 | var app = builder.Build(); |
| 38 | app.UseOutputCache(); // After UseRouting, before endpoints |
| 39 | ``` |
| 40 | |
| 41 | ### Page-Level Usage |
| 42 | |
| 43 | ```csharp |
| 44 | // Cache entire page for 60 seconds |
| 45 | [OutputCache(Duration = 60)] |
| 46 | public class IndexModel : PageModel { } |
| 47 | |
| 48 | // Named policy with tags for invalidation |
| 49 | [OutputCache(PolicyName = "LongCache")] |
| 50 | public class PrivacyModel : PageModel { } |
| 51 | |
| 52 | // Vary by query string parameter |
| 53 | [OutputCache(Duration = 300, VaryByQueryKeys = new[] { "page", "category" })] |
| 54 | public class BlogListModel : PageModel { } |
| 55 | |
| 56 | // Vary by header (e.g., for mobile vs desktop) |
| 57 | [OutputCache(Duration = 300, VaryByHeaderNames = new[] { "User-Agent" })] |
| 58 | public class ProductListModel : PageModel { } |
| 59 | |
| 60 | // Different cache for authenticated users |
| 61 | [OutputCache(PolicyName = "AuthenticatedCache")] |
| 62 | [Authorize] |
| 63 | public class DashboardModel : PageModel { } |
| 64 | ``` |
| 65 | |
| 66 | ### Cache Invalidation |
| 67 | |
| 68 | ```csharp |
| 69 | // Tag-based invalidation |
| 70 | public class BlogAdminModel(IOutputCacheStore cache) : PageModel |
| 71 | { |
| 72 | public async Task<IActionResult> OnPostPublishAsync() |
| 73 | { |
| 74 | // Invalidate all pages tagged with "blog" |
| 75 | await cache.EvictByTagAsync("blog", CancellationToken.None); |
| 76 | |
| 77 | return RedirectToPage("/Blog/List"); |
| 78 | } |
| 79 | } |
| 80 | ``` |
| 81 | |
| 82 | ## Pattern 2: Response Caching (Client-Side) |
| 83 | |
| 84 | Use for static assets and pages that can be cached by browsers and CDNs. |
| 85 | |
| 86 | ```csharp |
| 87 | // Program.cs |
| 88 | builder.Services.AddResponseCaching(); |
| 89 | |
| 90 | var app = builder.Build(); |
| 91 | app.UseResponseCaching(); // Before UseOutputCache |
| 92 | ``` |
| 93 | |
| 94 | ```csharp |
| 95 | // Page-level cache control |
| 96 | [ResponseCache(Duration = 3600, Location = ResponseCacheLocation.Any)] |
| 97 | public class StaticContentModel : PageModel { } |
| 98 | |
| 99 | // No caching (for error pages, authenticated content) |
| 100 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] |
| 101 | public class ErrorModel : PageModel { } |
| 102 | |
| 103 | // Private caching (client only, no CDN) |
| 104 | [ResponseCache(Duration = 60, Location = ResponseCacheLocation.Client)] |
| 105 | public class UserProfileModel : PageModel { } |
| 106 | ``` |
| 107 | |
| 108 | ## Pattern 3: Memory Caching |
| 109 | |
| 110 | Use for expensive computations and database queries within a single server instance. |
| 111 | |
| 112 | ### Configuration |
| 113 | |
| 114 | ```csharp |
| 115 | // Program.cs |
| 116 | builder.Services.AddMemoryCache(options => |
| 117 | { |
| 118 | options.SizeLimit = 100_000_000; // 100MB total cache size |
| 119 | options.CompactionPercentage = 0.25; // Remove 25% when limit reached |
| 120 | options.ExpirationScanFrequency = TimeSpan.FromMinutes(5); |
| 121 | }); |
| 122 | ``` |
| 123 | |
| 124 | ### Usage in Handlers/PageModels |
| 125 | |
| 126 | ```csharp |
| 127 | public class ProductService(IMemoryCache cache, AppDbContext db) |
| 128 | { |
| 129 | private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10); |
| 130 | |
| 131 | public async Task<Product?> GetProductAsync(Guid id) |
| 132 | { |
| 133 | var cacheKey = $"product:{id}"; |
| 134 | |
| 135 | if (cache.TryGetValue(cacheKey, out Product? product)) |
| 136 | { |
| 137 | return product; |
| 138 | } |
| 139 | |
| 140 | product = await db.Products.FindAsync(id); |
| 141 | |
| 142 | if (product != null) |
| 143 | { |