The Page Life Cycle
A page instance is created on every request from the client, and its execution causes itself and its contained controls to iterate through their life-cycle stages. Page execution begins when the HTTP runtime invokes ProcessRequest, which kicks off the page and control life cycles. The life cycle consists of a sequence of stages and steps. Some of these stages can be controlled through user-code events; some require a method override. Some other stages—or more exactly, substages—are just not public, are out of the developer’s control, and are mentioned here mostly for completeness.
The page life cycle is articulated in three main stages: setup, postback, and finalization. Each stage might have one or more substages and is composed of one or more steps and points where events are raised. The life cycle as described here includes all possible paths. Note that there are modifications to the process depending upon cross-page posts, script callbacks, and postbacks.
When the HTTP runtime instantiates the page class to serve the current request, the page constructor builds a tree of controls. The tree of controls ties into the actual class that the page parser created after looking at the ASPX source. Note that when the request processing begins, all child controls and page intrinsics—such as HTTP context, request objects, and response objects—are set.
The very first step in the page lifetime is determining why the run time is processing the page request. There are various possible reasons: a normal request, postback, cross-page postback, or callback. The page object configures its internal state based on the actual reason, and it prepares the collection of posted values (if any) based on the method of the request—either GET or POST. After this first step, the page is ready to fire events to the user code.
The PreInit Event
This event is the entry point in the page life cycle. When the event fires, no master page or theme has been associated with the page as yet. Furthermore, the page scroll position has been restored, posted data is available, and all page controls have been instantiated and default to the properties values defined in the ASPX source. (Note that at this time controls have no ID, unless it is explicitly set in the .aspx source.) Changing the master page or the theme programmatically is possible only at this time. This event is available only on the page. IsCallback, IsCrossPagePostback, and IsPostback are set at this time.
The Init Event
The master page, if one exists, and the theme have been set and can’t be changed anymore. The page processor—that is, the ProcessRequest method on the Page class—proceeds and iterates over all child controls to give them a chance to initialize their state in a context-sensitive way. All child controls have their OnInit method invoked recursively. For each control in the control collection, the naming container and a specific ID are set, if not assigned in the source.
The Init event reaches child controls first and the page later. At this stage, the page and controls typically begin loading some parts of their state. At this time, the view state is not restored yet.
The InitComplete Event
Introduced with ASP.NET 2.0, this page-only event signals the end of the initialization substage. For a page, only one operation takes place in between the Init and InitComplete events: tracking of view-state changes is turned on. Tracking view state is the operation that ultimately enables controls to really persist in the storage medium any values that are programmatically added to the ViewState collection. Simply put, for controls not tracking their view state, any values added to their ViewState are lost across postbacks.
All controls turn on view-state tracking immediately after raising their Init event, and the page is no exception. (After all, isn’t the page just a control?)
If the page is being processed because of a postback—that is, if the IsPostBack property is true—the contents of the __VIEWSTATE hidden field is restored. The __VIEWSTATE hidden field is where the view state of all controls is persisted at the end of a request. The overall view state of the page is a sort of call context and contains the state of each constituent control the last time the page was served to the browser.
At this stage, each control is given a chance to update its current state to make it identical to what it was on last request. There’s no event to wire up to handle the view-state restoration. If something needs be customized here, you have to resort to overriding the LoadViewState method, defined as protected and virtual on the Control class.
Processing Posted Data
All the client data packed in the HTTP request—that is, the contents of all input fields defined with the <form> tag—are processed at this time. Posted data usually takes the following form:
It’s an &-separated string of name/value pairs. These values are loaded into an internal-use collection. The page processor attempts to find a match between names in the posted collection and ID of controls in the page. Whenever a match is found, the processor checks whether the server control implements the IPostBackDataHandler interface. If it does, the methods of the interface are invoked to give the control a chance to refresh its state in light of the posted data. In particular, the page processor invokes the LoadPostData method on the interface. If the method returns true—that is, the state has been updated—the control is added to a separate collection to receive further attention later.
If a posted name doesn’t match any server controls, it is left over and temporarily parked in a separate collection, ready for a second try later.
The PreLoad Event
The PreLoad event merely indicates that the page has terminated the system-level initialization phase and is going to enter the phase that gives user code in the page a chance to further configure the page for execution and rendering. This event is raised only for pages.
The Load Event
The Load event is raised for the page first and then recursively for all child controls. At this time, controls in the page tree are created and their state fully reflects both the previous state and any data posted from the client. The page is ready to execute any initialization code related to the logic and behavior of the page. At this time, access to control properties and view state is absolutely safe.
Handling Dynamically Created Controls
When all controls in the page have been given a chance to complete their initialization before display, the page processor makes a second try on posted values that haven’t been matched to existing controls. The behavior described earlier in the “Processing Posted Data” section is repeated on the name/value pairs that were left over previously. This apparently weird approach addresses a specific scenario—the use of dynamically created controls.
Imagine adding a control to the page tree dynamically—for example, in response to a certain user action. As mentioned, the page is rebuilt from scratch after each postback, so any information about the dynamically created control is lost. On the other hand, when the page’s form is submitted, the dynamic control there is filled with legal and valid information that is regularly posted. By design, there can’t be any server control to match the ID of the dynamic control the first time posted data is processed. However, the ASP.NET framework recognizes that some controls could be created in the Load event. For this reason, it makes sense to give it a second try to see whether a match is possible after the user code has run for a while.
If the dynamic control has been re-created in the Load event, a match is now possible and the control can refresh its state with posted data.
Handling the Postback
The postback mechanism is the heart of ASP.NET programming. It consists of posting form data to the same page using the view state to restore the call context—that is, the same state of controls existing when the posting page was last generated on the server.
After the page has been initialized and posted values have been taken into account, it’s about time that some server-side events occur. There are two main types of events. The first type of event signals that certain controls had the state changed over the postback. The second type of event executes server code in response to the client action that caused the post.
Detecting Control State Changes
The whole ASP.NET machinery works around an implicit assumption: there must be a one-to-one correspondence between some HTML input tags that operate in the browser and some other ASP.NET controls that live and thrive in the Web server. The canonical example of this correspondence is between <input type=“text”> and TextBox controls. To be more technically precise, the link is given by a common ID name. When the user types some new text into an input element and then posts it, the corresponding TextBox control—that is, a server control with the same ID as the input tag—is called to handle the posted value. I described this step in the “Processing Posted Data” section earlier in the chapter.
For all controls that had the LoadPostData method return true, it’s now time to execute the second method of the IPostBackDataHandler interface: the RaisePostDataChangedEvent method. The method signals the control to notify the ASP.NET application that the state of the control has changed. The implementation of the method is up to each control. However, most controls do the same thing: raise a server event and give page authors a way to kick in and execute code to handle the situation. For example, if the Text property of a TextBox changes over a postback, the TextBox raises the TextChanged event to the host page.
Executing the Server-Side Postback Event
Any page postback starts with some client action that intends to trigger a server-side action. For example, clicking a client button posts the current contents of the displayed form to the server, thus requiring some action and a new, refreshed page output. The client button control—typically, a hyperlink or a submit button—is associated with a server control that implements the IPostBackEventHandler interface.
The page processor looks at the posted data and determines the control that caused the postback. If this control implements the IPostBackEventHandler interface, the processor invokes the RaisePostBackEvent method. The implementation of this method is left to the control and can vary quite a bit, at least in theory. In practice, though, any posting control raises a server event letting page authors write code in response to the postback. For example, the Button control raises the onclick event.
There are two ways a page can post back to the server—by using a submit button (that is, <input type=“submit”>) or through script. A submit HTML button is generated through the Button server control. The LinkButton control, along with a few other postback controls, inserts some script code in the client page to bind an HTML event (for example, onclick) to the form’s submit method in the browser’s HTML object model. We’ll return to this topic in the next chapter.
The LoadComplete Event
The page-only LoadComplete event signals the end of the page-preparation phase. Note that no child controls will ever receive this event. After firing LoadComplete, the page enters its rendering stage.
After handling the postback event, the page is ready for generating the output for the browser. The rendering stage is divided in two parts: pre-rendering and markup generation. The pre-rendering substage is in turn characterized by two events for pre-processing and post-processing.
The PreRender Event
By handling this event, pages and controls can perform any updates before the output is rendered. The PreRender event fires for the page first and then recursively for all controls. Note that at this time the page ensures that all child controls are created. This step is important, especially for composite controls.
The PreRenderComplete Event
Because the PreRender event is recursively fired for all child controls, there’s no way for the page author to know when the pre-rendering phase has been completed. For this reason, ASP.NET supports an extra event raised only for the page. This event is PreRenderComplete.
The SaveStateComplete Event
The next step before each control is rendered out to generate the markup for the page is saving the current state of the page to the view-state storage medium. Note that every action taken after this point that modifies the state could affect the rendering, but it is not persisted and won’t be retrieved on the next postback. Saving the page state is a recursive process in which the page processor walks its way through the whole page tree, calling the SaveViewState method on constituent controls and the page itself. SaveViewState is a protected and virtual (that is, overridable) method that is responsible for persisting the content of the ViewState dictionary for the current control. (We’ll come back to the ViewState dictionary in Chapter 19.)
ASP.NET server controls can provide a second type of state, known as a “control state.” A control state is a sort of private view state that is not subject to the application’s control. In other words, the control state of a control can’t be programmatically disabled, as is the case with the view state. The control state is persisted at this time, too. Control state is another state storage mechanism whose contents are maintained across page postbacks much like the view state, but the purpose of the control state is to maintain necessary information for a control to function properly. That is, state behavior property data for a control should be kept in the control state, while user interface property data (such as the control’s contents) should be kept in the view state.
The SaveStateComplete event occurs when the state of controls on the page have been completely saved to the persistence medium.
Generating the Markup
The generation of the markup for the browser is obtained by calling each constituent control to render its own markup, which will be accumulated in a buffer. Several overridable methods allow control developers to intervene in various steps during the markup generation—begin tag, body, and end tag. No user event is associated with the rendering phase.
The Unload Event
The rendering phase is followed by a recursive call that raises the Unload event for each control, and finally for the page itself. The Unload event exists to perform any final cleanup before the page object is released. Typical operations are closing files and database connections.
Note that the unload notification arrives when the page or the control is being unloaded but has not been disposed of yet. Overriding the Dispose method of the Page class—or more simply, handling the page’s Disposed event—provides the last possibility for the actual page to perform final clean up before it is released from memory. The page processor frees the page object by calling the method Dispose. This occurs immediately after the recursive call to the handlers of the Unload event has completed.