Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
What's New in Caching with .NET 11: MemoryCache Metrics, HybridCache Factory Context and IOutputCachePolicyProvider
.NET 11 brings three caching changes: with TrackStatistics on, MemoryCache publishes dotnet.cache.requests, evictions, entries and estimated_size metrics on its own; the HybridCache factory can choose the duration from the value; and IOutputCachePolicyProvider resolves output cache policies at runtime. The catch we measured: with today's package the new HybridCache overload isn't atomic (40 factory runs instead of 20 for 20 cold keys), and a custom provider silently drops AddPolicy definitions.
Measuring and Testing Caches: Hit Ratio, Metrics, Memory Usage and Consistency Tests
A cache proves its worth through p95 and database load, not hit ratio: the report cache cut p95 from 245 ms to 1.3 ms and 3,001 queries to 1; the book cache at 87% hit ratio didn't change p95 but cut queries by 87%; the one at 16% should go. In .NET 10 you measure with TrackStatistics, a Meter and HybridCache's EventSource; FakeTimeProvider and Testcontainers tests caught tag invalidation not reaching another server.
Cache Stampede, Penetration and Avalanche: Three Problems That Break Caches in Production
All three push the same load onto the database, but the fixes differ: in a 500-VU k6 run HybridCache cut a stampede from 223 queries to 3 and a Redis lock to 1; against unique ISBNs negative caching let 49,980 queries through, while ISBN validation plus a Bloom filter cut it to 9; 20% TTL jitter lowered the avalanche peak from 1,627 to 247 queries/s; with Redis down, FailFast and short timeouts cut p95 from 6 s to 7.3 ms.
Cache Invalidation Strategies: TTL, Explicit Removal, Event-Driven Invalidation and Versioned Keys
Correct invalidation means tying the delete to the commit and bounding the remaining race with a TTL, generation keys or events: in Bookstore even "write, then delete" left the old 12.00 price in the cache for 600 seconds, an interceptor deleting in SavedChangesAsync made it worse under an explicit transaction, and TransactionCommittedAsync fixed it. Generation keys closed the race on the same rig, and Redis pub/sub cleared the second instance's L1 in 0.7 ms.
ASP.NET Core Output Cache in Depth: Policies, Vary-By, Tag Eviction, Resource Locking and the Redis Store
Policies, vary-by rules and middleware order decide whether output cache is safe and effective: in Bookstore, resource locking ran the handler once for 100 concurrent requests (100 times with locking off), and placing UseOutputCache before authentication served Ada's personal recommendations to an anonymous user and to Grace on the 10.0.3 runtime. The Redis store gives two instances a shared cache and eviction, while locking stays per instance.
HTTP Caching: Cache-Control, ETag, 304 Not Modified and Vary to Spare the Server
The HTTP cache is driven by headers, not the server: while max-age holds the request never arrives, and afterwards an ETag request gets a bodiless 304. In our test the menu's second request came from the browser with 0 bytes, and Angular HttpClient saw the 304 as a 200. Behind nginx, a book response without Vary gave an English user the Turkish price, and a cart marked public showed Bob Alice's cart.
HybridCache in Depth: L1/L2, Tags, Logical Invalidation and L1 Staleness Across Servers
RemoveByTagAsync deletes nothing; it writes a timestamp to Redis and tells no other server's L1: in a two-instance test, B with a 10-second L1 still served the old menu after 22 seconds, because it reads a tag's timestamp once and keeps it. Removing by key plus a short LocalCacheExpiration cut the window to 10 seconds, and FusionCache's Redis backplane fixed B immediately.
IDistributedCache and Running Redis as a Cache: Serialization, Key Design, TTL and Eviction Policy
Running Redis as a cache takes a single ConnectionMultiplexer, short timeouts, versioned keys and allkeys-lru. With default settings every request took 11-12 s once Redis stopped; a 200 ms WaitAsync cap cut that to 0.4 s and a simple circuit breaker to under 1 ms. The official redis:8.2 image ships with noeviction: at 8 MB writes failed with OOM, while allkeys-lru kept going by evicting 2,534 keys. Redis 8 adds an AGPLv3 option; Valkey is BSD and Garnet MIT licensed.
IMemoryCache in Depth: SizeLimit, Eviction, Change Tokens and the Hidden Stampede in GetOrCreate
In an IMemoryCache with a full SizeLimit, an entry that exceeds the limit is never cached: book 101 added to a 100-unit cache was rejected, and background compaction evicted Low priority entries first, then the least recently used, bringing the cache down to 85. GetOrCreateAsync isn't atomic; 100 concurrent requests on an empty cache ran the factory 100 times, while striped SemaphoreSlim locks and HybridCache cut that to 1. Expiration has no timer; group invalidation uses change tokens.
Caching Strategy: What to Cache, at Which Layer and for How Long
A caching decision starts with measurement: in Bookstore the category menu, with 2 queries per request and a 19.9 ms p95, was the only real candidate, and HybridCache cut p95 to 3.2 ms and 4,000 queries to 0, while the cheap, per-user cart should not be cached. TTL is derived from freshness requirements, and each layer's invalidation owner is decided up front.
What's New in MVC and Razor Pages from .NET 8 to .NET 11
With .NET 8 and 9 support ending on November 10, 2026, the target for MVC and Razor Pages apps is .NET 10. Running one project on net8.0 and net10.0, we show cookie auth returning 401 instead of 302 for API endpoints, the Minimal API form binding change, the ASP0026 warning and RazorComponentResult, plus .NET 11's MVC items and an upgrade checklist.
Migrating from ASP.NET MVC 5 to ASP.NET Core MVC: Equivalents of Global.asax, Web.config, HtmlHelper and Bundling
Moving from MVC 5 to ASP.NET Core turns Global.asax into Program.cs, Web.config into Options, HttpModules into middleware, child actions into view components and bundling into MapStaticAssets; a large app moves piece by piece behind a YARP fallback. We show real build errors for @helper and @Html.Action, the CS0104 trap with System.Web adapters and what replaced Upgrade Assistant.