Caching and Performance
|
This section documents ASP.NET MVC 5.3.x, ASP.NET Web API 2.2, ASP.NET Web Pages 3, OWIN/Katana, SignalR 2,
and ASP.NET Identity 2 — all running on .NET Framework 4.8.1 — the This is not ASP.NET Core MVC. ASP.NET MVC 5 is the pre-Core, This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
This page documents caching and performance for ASP.NET MVC 5.3.x on .NET Framework 4.8.1 — not ASP.NET Core (see Performance and Caching under ASP.NET Core for output caching, response caching middleware, and hybrid caching).
[OutputCache] and cache profiles
[OutputCache(Duration = 300, VaryByParam = "category;page", Location = OutputCacheLocation.Server)]
public ActionResult Index(string category, int page = 1) => View(_repository.Browse(category, page));
<!-- web.config -->
<system.web>
<caching>
<outputCacheSettings>
<outputCacheProfiles>
<add name="ProductListing" duration="300" varyByParam="category;page" />
</outputCacheProfiles>
</outputCacheSettings>
</caching>
</system.web>
[OutputCache(CacheProfile = "ProductListing")]
public ActionResult Index(string category, int page = 1) { ... }
A cache profile centralizes cache settings in web.config so they can be changed (or disabled entirely for
debugging) without recompiling, and reused across multiple actions.
Donut caching and donut-hole caching
Caching an entire page is easy but breaks the moment part of that page is per-user (a "Welcome, Alice" header, a cart count). Two patterns split the difference:
-
Donut caching — cache everything except a hole (the opposite naming convention some libraries use); effectively, cache the outer shell and leave a gap for personalized content, typically via a third-party library since
[OutputCache]alone caches an entire action’s output. -
Donut-hole caching — the built-in MVC 5 approach: cache the page action normally, but render the personalized fragment through
Html.Action/Html.RenderActioncalling a separate,[ChildActionOnly]action that is not itself output-cached (or is cached with a much shorter, per-user duration):
@{ Html.RenderAction("CartSummary", "Cart"); } <!-- always executes fresh, even though the parent view is cached -->
[OutputCache(Duration = 300)]
public ActionResult Index() => View(); <!-- the whole page, cached -->
[ChildActionOnly]
public ActionResult CartSummary() => PartialView(_cart.GetSummary()); <!-- the "hole": never cached -->
See Views, Layouts, and Partials for Html.Action/
Html.RenderAction, and Filters for [ChildActionOnly] and [OutputCache]
as a result filter.
System.Runtime.Caching.MemoryCache and the ASP.NET Cache
For caching values that aren’t HTTP responses (a computed result, an external API call), MVC 5-era code
generally uses either the framework-agnostic System.Runtime.Caching.MemoryCache or the older, System.Web-
coupled HttpContext.Cache:
var cache = MemoryCache.Default;
var key = $"product:{id}";
if (!(cache.Get(key) is Product product))
{
product = _repository.Find(id);
cache.Set(key, product, DateTimeOffset.UtcNow.AddMinutes(10));
}
MemoryCache has no dependency on System.Web, making it usable from a service layer shared with, e.g.,
Web API self-hosting; HttpContext.Cache additionally supports cache-item dependencies tied to files or SQL
notifications.
Response compression and static-content caching in IIS
<system.webServer>
<urlCompression doDynamicCompression="true" doStaticCompression="true" />
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00" />
</staticContent>
</system.webServer>
Bundled scripts/styles already carry a cache-busting v= token (see
Bundling and Client-Side Integration), which is
what makes a far-future clientCache max-age safe for those URLs specifically.
View precompilation
Compiling .cshtml on first request adds latency to that request and defers syntax errors to runtime;
aspnet_compiler (run at publish time, e.g. via a Web Deploy publish profile) or RazorGenerator (see
Views, Layouts, and Partials) both move that cost to build
time instead.
Async all the way and thread-pool starvation
IIS serves each request from a limited thread-pool. A synchronous, blocking call inside an otherwise
async-looking chain (.Result, .Wait(), a genuinely synchronous EF6/HttpClient call) ties up a thread for
the full duration of that I/O instead of releasing it back to the pool — under load, this exhausts the pool and
queues requests behind it (thread-pool starvation) even though the CPU itself is nearly idle:
// Starves a thread for the entire HTTP call
public ActionResult Bad() => Content(new HttpClient().GetStringAsync(url).Result);
// Releases the thread while awaiting
public async Task<ActionResult> Good() => Content(await new HttpClient().GetStringAsync(url));
"Async all the way" means every layer between the action and the actual I/O call is async/await — see
Controllers and Actions for async action methods and
Data Access with EF6 for EF6’s async query methods.
Server.MapPath and synchronous I/O costs
Server.MapPath itself is cheap (a string translation), but the file I/O that often follows it
(File.ReadAllText(Server.MapPath("~/App_Data/config.json"))) is synchronous by default in System.IO unless
explicitly performed with File.ReadAllTextAsync/a FileStream opened with useAsync: true — a common,
easy-to-miss source of the same thread-pool starvation described above when it happens inside a request.
Profiling with MiniProfiler and Glimpse
-
MiniProfiler — a lightweight, always-on profiler that overlays timing for the request, nested custom steps, and (with the EF6 integration package) individual SQL queries directly in the rendered page.
-
Glimpse — a more heavyweight in-browser diagnostics panel (routes matched, model binding, SQL, session) installed as a set of NuGet packages per feature (
Glimpse.Mvc5,Glimpse.EF6).
Both are development/staging tools and should not ship enabled to production by default. See ASP.NET MVC Performance and Improving Performance with Output Caching.
Next: Testing and Diagnostics covers verifying these behaviors with tests rather than by observation alone.