Page Life Cycle and Postback

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.

The page life cycle is the single most load-bearing concept in Web Forms: almost every bug that looks like "my control’s value disappeared" or "my event handler ran before/after I expected" traces back to a misunderstanding of when, in this fixed sequence, a given piece of code actually executes.

Application life cycle vs. page life cycle

The application life cycle is process-wide and covers global.asax events — Application_Start, Application_BeginRequest, Application_AuthenticateRequest, Application_EndRequest, Application_End — fired once per application-domain lifetime or once per request, regardless of which page is being served (see HTTP Pipeline, Handlers, and Configuration). The page life cycle nests inside a single request, from the moment ASP.NET resolves a .aspx URL to a Page object through to that object being discarded after rendering. This page covers only the latter.

The six Web Forms page-processing stages and their ordered events, from Page request through Unload

The six stages, in order

Stage What happens

1. Page request

ASP.NET decides whether to parse and compile the page (or use a cached compiled output) before creating the Page instance.

2. Start

Request, Response, IsPostBack, and UICulture become available on the Page object.

3. Initialization

PreInit → theme and master page applied → Init fires bottom-up (innermost control first) → InitComplete. ViewState is not yet loaded when Init runs.

4. Load

PreLoad → ViewState and postback data are loaded onto controls → Load fires top-down (page before its children) → if IsPostBack, postback event handling runs next (change events, then the triggering action event).

5. Rendering

LoadCompletePreRender fires bottom-up → PreRenderComplete → ViewState is saved (SaveStateComplete) → Render writes HTML for the whole control tree to the response.

6. Unload

Cleanup, bottom-up; the page object is about to be discarded. Do not set control properties or attempt further ViewState changes here — there is no further rendering pass to pick them up.

The order in full, as fired: PreInit, Init, InitComplete, PreLoad, Load, (postback control events), LoadComplete, PreRender, PreRenderComplete, SaveStateComplete, Render, Unload. See ASP.NET Page Life Cycle Overview — the canonical reference this ordering is verified against — and General Page Life Cycle Stages.

public partial class OrderPage : System.Web.UI.Page
{
    protected override void OnPreInit(EventArgs e)
    {
        base.OnPreInit(e);
        // Dynamic master/theme selection must happen here -- too late anywhere after.
        MasterPageFile = Request.QueryString["compact"] == "1" ? "~/Compact.master" : "~/Site.master";
    }

    protected void Page_Init(object sender, EventArgs e)
    {
        // Controls exist, but ViewState/postback values are not loaded yet.
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            BindOrderGrid(); // first request only -- postbacks restore state from ViewState instead
        }
    }

    protected void SubmitButton_Click(object sender, EventArgs e)
    {
        // Runs during postback event handling, after Page_Load, only when this control caused the postback.
    }

    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
        SubmitButton.Enabled = OrderTotal > 0; // last safe point to change control state before it renders
    }
}

IsPostBack, IsCallback, and IsCrossPagePostBack

  • Page.IsPostBack — true when the request is a postback of this same page (the __EVENTTARGET/ViewState machinery round-tripping), false on the first ("fresh") request. Almost every Page_Load guards expensive work — initial data binding, default values — with if (!IsPostBack).

  • Page.IsCallback — true inside an ICallbackEventHandler client-callback request (see Server Controls); the full page life cycle still runs, but no full-page HTML is rendered.

  • Page.IsCrossPagePostBack — true on the target page when the postback originated from a different page via cross-page posting (below).

Bottom-up Init vs. top-down Load

This asymmetry is a frequent source of surprise: Init fires innermost control first, page last, while Load (and postback event handling) fires page first, innermost control last. Practically: a naming container or user control has finished its own Init before its parent’s Init runs, but the parent’s Load has already run by the time a child control’s Load executes — so parent-set state in Load is visible to child controls' own Load handlers, but not the reverse.

Catch-up events for dynamically added controls

Controls added to the tree at runtime (Controls.Add(new TextBox())) after their "natural" stage has already passed for statically declared controls must go through catch-up processing so they still receive Init, Load, and ViewState/postback-data application correctly. Practically this means: add dynamic controls as early as possible — ideally in Page_Init or CreateChildControls — and always with a stable, explicit ID on every request (postback or not), because ViewState and postback data are matched to controls by ID and tree position; a control added with a different ID or in a different position across postbacks will not recover its prior state.

protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
    // Re-create the SAME control, with the SAME ID, on every postback -- including the first request.
    var dynamicBox = new TextBox { ID = "DynamicBox" };
    PlaceHolder1.Controls.Add(dynamicBox);
}

Data-binding events

Data-bound controls (Data Binding and Data Controls) fire their own event sequence around each call to DataBind(): DataBinding → per-row RowCreated (GridView) / ItemCreated (Repeater/DataList) as each row’s control tree is built → RowDataBound / ItemDataBound once the row is actually bound to its data item → DataBound once for the whole control. RowDataBound/ItemDataBound is where per-row formatting logic belongs (it has both the built control tree and the underlying data item); RowCreated/ItemCreated fires even for rows with no data (e.g. pager or header rows) and does not yet have the data item.

Cross-page postback and PreviousPage

By default a postback always targets the same page (<form action> points at the current URL). Setting PostBackUrl on a Button/LinkButton/ImageButton posts to a different page instead:

<%-- Checkout.aspx --%>
<asp:Button ID="ContinueButton" runat="server" Text="Continue"
    PostBackUrl="~/Confirm.aspx" />
// Confirm.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
    if (PreviousPage != null && PreviousPage.IsCrossPagePostBack)
    {
        var sourceBox = (TextBox)PreviousPage.FindControl("CustomerNameBox");
        WelcomeLabel.Text = "Thanks, " + sourceBox.Text;
    }
}

The originating page’s public/strongly typed members are reachable through PreviousPage, but only after casting or exposing them via @PreviousPageType for compile-time-checked access; the source page runs its own full life cycle through PreRender (but not Render) before control passes to the target page, so both pages' Load handlers execute on a cross-page postback.

A single postback, end to end

sequenceDiagram participant B as Browser participant F as
(__doPostBack) participant P as Page (server) B->>F: user clicks SubmitButton F->>F: set __EVENTTARGET / __EVENTARGUMENT, submit form (POST) F->>P: POST including __VIEWSTATE, __EVENTVALIDATION, all form field values P->>P: Start (IsPostBack = true) P->>P: Init (bottom-up), InitComplete P->>P: Load ViewState + postback data onto controls P->>P: Load fires top-down (Page_Load, then child controls) P->>P: raise change events (e.g. TextChanged), then the __EVENTTARGET action event (SubmitButton_Click) P->>P: LoadComplete, PreRender (bottom-up), SaveViewState P->>B: Render -> HTML response (fresh __VIEWSTATE for the next round trip)

doPostBack(eventTarget, eventArgument) is the hidden JavaScript function ASP.NET injects into every page with a server <form>; controls that are not natively submit elements (a LinkButton, a GridView sort header) call it to populate EVENTTARGET/EVENTARGUMENT and submit the form, simulating a native postback. See ViewState and Control State for what travels in VIEWSTATE and __EVENTVALIDATION on that round trip.