State Management and Caching

This section documents ASP.NET Web Forms on .NET Framework 4.8.1, the last and permanent version of Web Forms — the page life cycle and postback model, ViewState and control state, server controls, validation controls, master pages and themes, data-bound controls, and the provider-based security model — as described by the official documentation at Microsoft Learn and the ASP.NET previous-versions archive, which are the reference these pages are written and verified against.

Web Forms receives security fixes only and has no forward path onto modern .NET (.NET Framework 4.8.1 is Microsoft’s last version of .NET Framework; Web Forms itself never shipped on .NET Core/.NET 5+). It remains supported for existing applications running on Windows but is not recommended for new development — see Choosing an ASP.NET Framework and Migrating to Modern ASP.NET for what that means in practice.

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.

Web Forms offers several distinct ways to keep data alive across requests, each with a different lifetime, scope, and cost — picking the wrong one is a common source of both bugs (data that should have persisted did not) and performance problems (data that did not need to persist was carried anyway).

Client-side vs. server-side state

Mechanism Scope Notes

ViewState / control state

One page, one user, round-trips every postback

See ViewState and Control State — opaque to the client, carried in the response/request body.

Hidden fields

One page, one user

Like ViewState but explicit and app-defined; no automatic serialization/tree-matching.

Cookies

One user (or session), across pages, size-limited (~4KB)

Sent on every request to the matching domain/path regardless of whether the page needs them.

Query string

One user, one URL, visible/bookmarkable

No server storage; easily tampered with, so never a substitute for authorization.

Application

Whole app, all users, in-process only

See below.

Session

One user, across pages, server- or out-of-process

See below.

Cache

Whole app, expiration/eviction-aware

See below.

Profile

One (typically authenticated) user, persisted to storage

Strongly typed, configured in web.config; see User and Custom Controls.

Application, Session, Cache, and Profile

Application is a single, process-wide IDictionary shared by every user and every request — rarely the right choice today (no expiration, no locking safety by default, lost on app-pool recycle) but still seen as a crude read-mostly cache:

protected void Application_Start(object sender, EventArgs e)
{
    Application["StartedAtUtc"] = DateTime.UtcNow;
}

protected void Page_Load(object sender, EventArgs e)
{
    Application.Lock();
    Application["HitCount"] = (int)(Application["HitCount"] ?? 0) + 1;
    Application.UnLock();
}

Session is per-user and is where the great majority of "remember this across pages" state actually belongs (shopping cart contents, wizard-in-progress data, small caches of the current user’s own data).

Session modes

sessionState mode determines where session data physically lives:

Mode Behavior

InProc (default)

Stored in the worker process’s memory. Fastest; lost on app-pool recycle/restart and does not work across a farm without sticky sessions.

StateServer

Stored by a separate Windows service (ASP.NET State Service), survives app-pool recycles, works across a farm; objects must be serializable.

SQLServer

Stored in a SQL Server database (aspnet_regsql-provisioned); survives process and even server restarts, works across a farm without sticky sessions; slowest of the three.

Custom

A custom SessionStateStoreProviderBase implementation (e.g. a Redis- or NoSQL-backed provider).

<system.web>
  <sessionState mode="SQLServer"
      sqlConnectionString="Data Source=sqlserver;Initial Catalog=ASPState;Integrated Security=true"
      cookieless="false" timeout="20" />
</system.web>

Session-less pages (<%@ Page EnableSessionState="false" %> or "ReadOnly") skip the session-state locking overhead entirely (EnableSessionState="True" serializes concurrent requests from the same session against each other) or read-only access, which is worth setting explicitly on any page that does not touch Session. Cookieless sessions (cookieless="true" or "AutoDetect") embed the session ID in the URL instead of a cookie, at the cost of session-fixation risk and unwieldy URLs — generally avoided in favor of requiring cookies.

The Cache API

System.Web.Caching.Cache (HttpContext.Cache/Page.Cache) is an application-wide, expiration- and memory-pressure-aware store distinct from Application — entries can expire on a timer, on a dependency (file, another cache key, a SQL change), or be evicted under memory pressure, and can notify code when removed:

public Product GetProduct(int id)
{
    string key = "Product_" + id;
    if (Cache[key] is Product cached) return cached;

    var product = _repository.Find(id);
    Cache.Insert(key, product,
        dependencies: null,
        absoluteExpiration: DateTime.UtcNow.AddMinutes(10),
        slidingExpiration: Cache.NoSlidingExpiration,
        priority: CacheItemPriority.Normal,
        onRemoveCallback: (k, value, reason) => Trace.WriteLine($"Evicted {k}: {reason}"));
    return product;
}

Dependencies (CacheDependency) invalidate an entry when a file changes on disk, or when another cache key is removed (chained invalidation); priority (CacheItemPriority) influences which entries are evicted first under memory pressure; the callback lets code react to eviction (e.g. to re-warm the entry).

SQL cache dependency

SqlCacheDependency invalidates a cache entry when the underlying database rows actually change, using either polling (SQL Server 7/2000-compatible, a background poll of a tracked table) or the newer query-notification mechanism (SqlDependency, SQL Server 2005+):

<system.web>
  <caching>
    <sqlCacheDependency enabled="true" pollTime="60000">
      <databases>
        <add name="CatalogDb" connectionStringName="CatalogDb" />
      </databases>
    </sqlCacheDependency>
  </caching>
</system.web>
var dependency = new SqlCacheDependency("CatalogDb", "Products");
Cache.Insert("AllProducts", products, dependency);

Output caching

@OutputCache caches an entire page’s (or user control’s — "fragment caching") rendered response, keyed by its VaryBy* attributes:

<%@ OutputCache Duration="120" VaryByParam="category;page" VaryByHeader="Accept-Language" %>
  • VaryByParam — a separate cached entry per distinct query-string/form value combination ("none" for a single shared entry, "*" for every parameter).

  • VaryByControl — vary by a named control’s value (typically on a user control being fragment-cached).

  • VaryByCustom — a free-form string resolved by overriding HttpApplication.GetVaryByCustomString (e.g. vary by device type or by authenticated-vs-anonymous).

  • VaryByHeader — vary by one or more request header values.

Fragment caching applies @OutputCache to a .ascx user control instead of the whole page, so a mostly static sidebar can be cached independently of a dynamic main content area on the same page.

Post-cache substitution

<asp:Substitution> punches a hole in an otherwise output-cached page for content that must always be per-request fresh (e.g. "Welcome, `<username>`" on an otherwise static page):

<%@ OutputCache Duration="300" VaryByParam="none" %>
...
<asp:Substitution ID="Greeting" runat="server" MethodName="GetGreeting" />
public static string GetGreeting(HttpContext context)
{
    // Static method; runs on every request even while the rest of the page is served from cache.
    return "Welcome, " + (context.User.Identity.Name ?? "Guest");
}

Distributed output-cache providers (System.Web.Caching.OutputCacheProvider, pluggable since ASP.NET 4) let output-cache entries live outside a single worker process — e.g. backed by a distributed cache such as Redis — so a farm shares one cache instead of each server caching independently.