Uncategorized

Application, Page and Control lifecycle

The problem

Every now and then we receive an advisory case from a customer whom needs some advices from us about how to implement some kind of functionality, and this particular one had an interesting and weird behavior with the viewstate in a custom composite control: basically the customer implemented a sort of tabbed control which had to act as a container for other controls like checkboxes, and was meant to be used at design time within Visual Studio by other developers. The control itself was actually based on a CompositeControl and a View (and MultiView) class, and everything was apparently working fine until he tried to uncheck one of the checkboxes within the control and submit the page: after the postback the checkbox was still flagged ?. The custom control was clearly the the one having the problem because another checkbox outsite it (just placed somewhere else on the page) worked as expected. It’s interesting to note that the customer has customized the viewstate management and was overriding the SaveViewState and LoadViewState events, and on my experience viewstate and the chain of events which occurs in the page lifecycle are often the ones to blame for such problems…

A quick test was trying to disable the viewstate for the checkbox:

<cc:MyTabView ID="MyTab2" runat="server">
    <cc:MyTab ID="MyTab1" runat="server" Title="Tab 1">
        <asp:CheckBox ID="CheckBox1" Text="My checkbox" runat="server" EnableViewState="false" />
    </cc:MyTab>
</cc:MyTabView>

This fixed the problem, the checkbox was behaving fine now. But that was not an acceptable solution: this custom control was meant to be used by other developers within their applications and imposing a constraint like this one, forcing developers to disable the viewstate for their contained controls could severely limit their ability to develop nice and fully functional controls. So, back to debugging.

The first think I did was to enable tracing in my web.config to get an idea of the chain of events raised:

Begin PreInit   
End PreInit  
Begin Init  
End Init  
Begin InitComplete  
End InitComplete  
Begin LoadState  
End LoadState  
Begin ProcessPostData  
End ProcessPostData  
Begin PreLoad  
End PreLoad  
Begin Load  
End Load  
Begin ProcessPostData Second Try 
End ProcessPostData Second Try  
Begin Raise ChangedEvents  
End Raise ChangedEvents  
Begin Raise PostBackEvent 
End Raise PostBackEvent  
Begin LoadComplete  
End LoadComplete  
Begin PreRender  
End PreRender  
Begin PreRenderComplete  
End PreRenderComplete  
Begin SaveState  
End SaveState  
Begin SaveStateComplete  
End SaveStateComplete  
Begin Render  
End Render

As you can see when the POST data reaches the server, the runtime loads the “old” viewstate (the one saved at the previous request), then processes the posted data, then the “change” events are fired and immediately after also the postback events are fired, and after rendering the page the viewstate is saved once again ready to start the cycle again when the next request will come in. The reason to compare the control values and state posted from the client with the “old” viewstate is to find what has changed and fire the appropriate events on relevant objects and classes; specifically to checkboxes, to determine if the “checked” status has changed the runtime verifies which value the checkbox had in the “old” viewstate and confronts it with the new value coming with the posted data. In practice this means that is the checkbox value is part of the posted back data is it flagged, otherwise it is not (and the Checked event is fired accordingly).

Back to our problem, debugging the page showed that the form values where posted back correctly, so was the checkbox value, also when reproducing the problem submitting the page the second time; well, the checkbox was not part of the posted back data but since it was unchecked that is what we are excepting, right? An once on the server side I could hook the Checked event and see that is was fired as expected. Stepping through the code even showed that while elaborating the request and building the HTML to send to the client, the checkbox was actually unchecked until around the latest LoadViewstate event… ?

Isn’t that enough to point to the page and control lifecycle events?

A bit of theory

There are a few articles and blog posts on this subject, here is what the MSDN Library says:

The key point is: you can do almost whatever you want but you must do it at the right time, or bad things could happen… So here’s an event list (should be quite comprehensive but something may be still missing) that takes place when an HTTP request is received from the ASP.NET ISAPI till the resulting HTML is sent to the client; this is the result of a sample page which hosts a UserControl which in turn contains a CheckBox:

HttpApplication Based on the file name extension of the requested resource (mapped in the application’s configuration file), select a class that implements IHttpHandler to process the request. If the request is for an object (page) derived from the Page class and the page needs to be compiled, ASP.NET compiles the page before creating an instance of it
HttpApplication Call the ProcessRequest method (or the asynchronous version IHttpAsyncHandler::BeginProcessRequest) of the appropriate IHttpHandler class for the request. For example, if the request is for a page, the current page instance handles the request
HttpApplication Perform response filtering if the Filter property is defined
HttpApplication Start Called when the first resource (such as a page) in an ASP.NET application is requested. The Application_Start method is called only one time during the life cycle of an application. You can use this method to perform startup tasks such as loading data into the cache and initializing static values.

You should set only static data during application start. Do not set any instance data because it will be available only to the first instance of the HttpApplication class that is created

HttpModule BeginRequest The BeginRequest event signals the creation of any given new request. This event is always raised and is always the first event to occur during the processing of a request
HttpModule AuthenticateRequest The AuthenticateRequest event signals that the configured authentication mechanism has authenticated the current request. Subscribing to the AuthenticateRequest event ensures that the request will be authenticated before processing the attached module or event handler
HttpModule PostAuthenticateRequest The PostAuthenticateRequest event is raised after the AuthenticateRequest event has occurred. Functionality that subscribes to the PostAuthenticateRequest event can access any data that is processed by the PostAuthenticateRequest
HttpModule AuthorizeRequest The AuthorizeRequest event signals that ASP.NET has authorized the current request. Subscribing to the AuthorizeRequest event ensures that the request will be authenticated and authorized before processing the attached module or event handler
HttpModule ResolveRequestCache Occurs when ASP.NET finishes an authorization event to let the caching modules serve requests from the cache, bypassing execution of the event handler (for example, a page or an XML Web service).
HttpModule PostResolveRequestCache Occurs when ASP.NET bypasses execution of the current event handler and allows a caching module to serve a request from the cache
HttpModule PostMapRequestHandler Occurs when ASP.NET has mapped the current request to the appropriate event handler
Session Start
HttpModule AcquireRequestState Occurs when ASP.NET acquires the current state (for example, session state) that is associated with the current request
HttpModule PostAcquireRequestState Occurs when the request state (for example, session state) that is associated with the current request has been obtained
HttpModule PreRequestHandlerExecute Occurs just before ASP.NET starts executing an event handler (for example, a page or an XML Web service)
Page PreInit Use this event for the following:

    • Check the IsPostBack property to determine whether this is the first time the page is being processed
    • Create or re-create dynamic controls
    • Set a master page dynamically
    • Set the Theme property dynamically
    • Read or set profile property values

      Note

      If the request is a postback, the values of the controls have not yet been restored from view state. If you set a control property at this stage, its value might be overwritten in the next event.

WebUserControl Init Initialize settings needed during the lifetime of the incoming Web request. See Handling Inherited Events
Page Init Raised after all controls have been initialized and any skin settings have been applied. Use this event to read or initialize control properties
Page InitComplete Use this event for processing tasks that require all initialization be complete
Page PreLoad Use this event if you need to perform processing on your page or control before the Load event.

After the Page raises this event, it loads view state for itself and all controls, and then processes any postback data included with the Request instance

WebUserControl LoadViewState At the end of this phase, the ViewState property of a control is automatically populated as described in Maintaining State in a Control. A control can override the default implementation of the LoadViewState method to customize state restoration
WebUserControl LoadPostData Process incoming form data and update properties accordingly. See Processing Postback Data.

Note  

Only controls that process postback data participate in this phase

Page Load The Page calls the OnLoad event method on the Page, then recursively does the same for each child control, which does the same for each of its child controls until the page and all controls are loaded.

Use the OnLoad event method to set properties in controls and establish database connections

WebUserControl Load Perform actions common to all requests, such as setting up a database query. At this point, server controls in the tree are created and initialized, the state is restored, and form controls reflect client-side data. See Handling Inherited Events
WebUserControl RaisePostDataChangedEvent

(if IPostBackDataHandler is implemented)

Raise change events in response to state changes between the current and previous postbacks. See Processing Postback Data.

Note   Only controls that raise postback change events participate in this phase

WebUserControl RaisePostBackEvent (if IPostBackEventHandler is implemented) Handle the client-side event that caused the postback and raise appropriate events on the server. See Capturing Postback Events.

Note   Only controls that process postback events participate in this phase.

Page CONTROL EVENTS Use these events to handle specific control events, such as a Button control’s Click event or a TextBox control’s TextChanged event.

Note

In a postback request, if the page contains validator controls, check the IsValid property of the Page and of individual validation controls before performing any processing.

Page LoadComplete Use this event for tasks that require that all other controls on the page be loaded
Page PreRender Before this event occurs:

    • The Page object calls EnsureChildControls for each control and for the page.
    • Each data bound control whose DataSourceID property is set calls its DataBind method. For more information, see Data Binding Events for Data-Bound Controls below.

The PreRender event occurs for each control on the page. Use the event to make final changes to the contents of the page or its controls

WebUserControl PreRender Perform any updates before the output is rendered. Any changes made to the state of the control in the prerender phase can be saved, while changes made in the rendering phase are lost. See Handling Inherited Events
Page PreRenderComplete Occurs before the page content is rendered. The PreRenderComplete event is raised when the pre-render stage of the page life cycle is complete. At this stage of the page life cycle, all controls are created, any pagination required is completed, and the page is ready to render to the output.

This is the last event raised before the page’s view state is saved

Page SaveViewState Saves any server control view-state changes that have occurred since the time the page was posted back to the server.
WebUserControl SaveViewState The ViewState property of a control is automatically persisted to a string object after this stage. This string object is sent to the client and back as a hidden variable. For improving efficiency, a control can override the SaveViewState method to modify the ViewState property. See Maintaining State in a Control
Page SaveStateComplete Before this event occurs, ViewState has been saved for the page and for all controls. Any changes to the page or controls at this point will be ignored.

Use this event perform tasks that require view state to be saved, but that do not make any changes to controls

Page RenderControl Outputs server control content to a provided HtmlTextWriter object and stores tracing information about the control if tracing is enabled.

If a server control’s Visible property is set to true, this method determines whether tracing is enabled for the page. If so, it stores trace information associated with the control, and renders the server control content to the page.

This method is automatically called by the page during the rendering, but can be overridden by custom control developers.

Page Render This is not an event; instead, at this stage of processing, the Page object calls this method on each control. All ASP.NET Web server controls have a Render method that writes out the control’s markup that is sent to the browser.

If you create a custom control, you typically override this method to output the control’s markup. However, if your custom control incorporates only standard ASP.NET Web server controls and no custom markup, you do not need to override the Render method. For more information, see Developing Custom ASP.NET Server Controls.

A user control (an .ascx file) automatically incorporates rendering, so you do not need to explicitly render the control in code

Page RenderChildren Outputs the content of a server control’s children to a provided HtmlTextWriter object, which writes the content to be rendered on the client. This method notifies ASP.NET to render any Active Server Pages (ASP) code on the page. If no ASP code exists on the page, this method renders any child controls for the server control.
WebUserControl RenderControl Outputs server control content to a provided HtmlTextWriter object and stores tracing information about the control if tracing is enabled.
WebUserControl Render Generate output to be rendered to the client. See Rendering an ASP.NET Server Control
WebUserControl RenderChildren Outputs the content of a server control’s children to a provided HtmlTextWriter object, which writes the content to be rendered on the client. This method notifies ASP.NET to render any Active Server Pages (ASP) code on the page. If no ASP code exists on the page, this method renders any child controls for the server control.
WebUserControl Unload Perform any final cleanup before the control is torn down. Control authors generally perform cleanup in Dispose and do not handle this event
WebUsercontrol Dispose Perform any final cleanup before the control is torn down. References to expensive resources such as database connections must be released in this phase. See Methods in ASP.NET Server Controls
Page Unload This event occurs for each control and then for the page. In controls, use this event to do final cleanup for specific controls, such as closing control-specific database connections.

For the page itself, use this event to do final cleanup work, such as closing open files and database connections, or finishing up logging or other request-specific tasks.

Note

During the unload stage, the page and its controls have been rendered, so you cannot make further changes to the response stream. If you attempt to call a method such as the Response.Write method, the page will throw an exception.

Page Dispose Enables a server control to perform final clean up before it is released from memory. (inherited from Control)
HttpModule PostRequestHandlerExecute Occurs when the ASP.NET event handler (for example, a page or an XML Web service) finishes execution
HttpModule ReleaseRequestState Occurs after ASP.NET finishes executing all request event handlers. This event causes state modules to save the current state data. When the ReleaseRequestState event is raised, the application is finished with the request and ASP.NET is signaled to store the request state
HttpModule PostReleaseRequestState Occurs when ASP.NET has completed executing all request event handlers and the request state data has been stored
HttpModule UpdateRequestCache Occurs when ASP.NET finishes executing an event handler in order to let caching modules store responses that will be used to serve subsequent requests from the cache
HttpModule PostUpdateRequestCache The PostUpdateRequestCache event is raised after the UpdateRequestCache event has occurred. When the PostUpdateRequestCache is raised, ASP.NET has completed processing code and the content of the cache is finalized. Occurs when ASP.NET finishes updating caching modules and storing responses that are used to serve subsequent requests from the cache.
HttpModule EndRequest Occurs as the last event in the HTTP pipeline chain of execution when ASP.NET responds to a request. The EndRequest event is always raised when the CompleteRequest method is called.
HttpModule PreSendRequestHeaders Occurs just before ASP.NET sends HTTP headers to the client
HttpModule PreSendRequestContent Occurs just before ASP.NET sends content to the client; The PreSendRequestContent event may occur multiple times
Session End
HttpModule Disposed Adds an event handler to listen to the Disposed event on the application. When you create a Disposed delegate, you identify the method that handles the event. To associate the event with your event handler, add an instance of the Disposed delegate to the event. The event handler is called whenever the event occurs, unless you remove the Disposed delegate.
Application End Called once per lifetime of the application before the application is unloaded
WebUserControl Finalize
Page Finalize
HttpModule Finalize

The CreateChildControls method is not listed in the table because it is called whenever the ASP.NET page framework needs to create the controls tree and this method call is not limited to a specific phase in a control’s lifecycle. For example, CreateChildControls can be invoked when loading a page, during data binding, or during rendering.

There is also a quite well-known poster about page lifecycle events, probably you have already seen it (source: http://blog.rioterdecker.net/blogs/avalonboy/archive/2006/06/24/114.aspx):

asp.net page life cycle
asp.net page life cycle

I reported it in the table below for easier reading:

Page ProcessRequest
Page ProcessRequestMain
Page DeterminePostBackMode
Adapter DeterminePostBackMode
Page LoadScrollPosition
Page OnPreInit
Page ApplyMasterPage
PostBack ApplyMasterPageRecursive
Control InitRecursive
Control ResolveAdapter
Control ApplySkin
Page ApplyControlSkin
PostBack ApplyControlSkin
Page OnInitComplete
PostBack LoadAllState
PostBack LoadPageStateFromPersistenceMedium
PagePersister Load
Control LoadControlStateInternal
Control LoadControlState
Adapter LoadAdapterControlState
Control LoadViewStateRecursive
Control LoadViewState
Adapter LoadAdapterViewState
PostBack ProcessPostData
Page OnPreLoad
Control LoadRecursive
Control OnLoad
Adapter OnLoad
PostBack ProcessPostData
PostBack RaiseChangeEvents
PostBack RaisePostBackEvents
Page OnLoadComplete
Control PreRenderRecursiveInternal
Control EnsureChildControls
Control ResolveAdapter
Control CreateChildControls
Adapter CreateChildControls
Control OnPreRender
Adapter OnPreRender
Control SaveViewState
Page SavePageStateToPersistenceMedium
PagePersister Save
Page OnSaveStateComplete
Control RenderControl
Control RenderControlInternal
Control Render
Adapter BeginRender
Adapter Render
Adapter EndRender
Control RenderChildren

The solution

The root cause of this problem was that we were setting the child MultiView control’s ActiveViewIndex in the Render stage, which is too late. No “work” should be done in the Render stage, only rendering. There are some exceptions to this, but setting ActiveViewIndex is not one of them. By default the MultiView’s ActiveViewIndex is -1, meaning that none of the views are active. The result of this is that all the child View controls are not visible, which means that their OnPreRender methods will not be called. The CheckBox control registers for post back data during OnPreRender but since it’s never called, it never registers. Since it’s never registered for post back data, it doesn’t see the value of the checkbox being posted back.

We found a few different possible solutions to this problem, so it was up to the customer choose the one they preferred… here are the alternatives:

  • Set the child MultiView’s ActiveViewIndex whenever their control’s ActiveTabIndex is set. Something like (in MTabView.cs):
private int ActiveTabIndex_ = -1;

public int ActiveTabIndex
{
    get { return this.ActiveTabIndex_; }
    set
    {
        this.ActiveTabIndex_ = value;
        InnerMultiView_.ActiveViewIndex = ActiveTabIndex;
    }
}
  • Set the child MultiView’s ActiveViewIndex in CreateChildControls after the other child controls are created (the table, etc.)
  • Set the child MultiView’s ActiveViewIndex in OnPreRender before calling base.OnPreRender().

Conclusion

Should be easy to image at this point: if you get into a situation like this one, check very carefully where you’re doing your changes and the event sequence within your application: very likely your change is applied but overridden later before it get any change to render to the client.

Carlo

Quote of the Day:

There is no worse lie than a truth misunderstood by those who hear it.

–William James

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.