Pop quiz, what is wrong with the following class?
The class above is a custom server control that derives from an ASP.NET
PlaceHolder control. This example is a stripped down version of a
class I am currently working on this week (which loads controls
dynamically from custom XML). I chose the PlaceHolder control to
derive from because I needed a container control, in which to hold my
dynamically instantiated controls - the PlaceHolder control is designed for,
and ideal for this purpose.
I ran into a problem with persistence of property values belonging to
dynamic controls when contained in my custom
MyPlaceHolder
control. In the above example I have created a
DropDownList
control dynamically in the
OnPreRender method, and added it to the
controls collection of
MyPlaceHolder, which, as it turns out is a problem.....
Those of you who know enough about how ASP.NET works (1.1 and 2.0), will know about
"catch-up":
When controls are declared statically in an ASPX page, ASP.NET
instantiates these controls, adds them to an in-memory control tree,
and then calls various event methods on each control, and child
controls recursively, before the page is rendered. (I have
included the order of events of all controls in ASP.NET later in this
post).
When a control is created dynamically, the time in which the control is
instantiated is at the mercy of the developer. For this reason, ASP.NET
plays "catch-up" and arranges to call all missed event methods so that
the control is in sync with it's container. Using the above code as an
example - after instantiating the drop down list control and adding this
control to the controls collection of the placeholder, ASP.NET will
call the
OnInit,
LoadViewState, and
OnLoad event methods of the drop down list control,
after which the drop down list control will have caught up to the pre-render event of the
placeholder container.
So, why do I loose persistence of property values in my drop down list control, specifically the selected index property?
When ASP.NET plays "catch up", the framework will call some, or all of
the
OnInit,
LoadViewState,
OnLoad, and
OnPreRender methods, depending
on the current state of the containing control.
The methods to process post back data are not called during catch-up.
The following AddedControl method exists as part of the
Control class
in the ASP.NET 2.0 framework (version 1.1 will have similar code), and
demonstrates "catch-up", using the current state of the control. Notice
that no call exists to load post data.
My drop down list box looses persistence because, by the time the
control is instantiated at the pre-render stage, the time for loading
post back data has passed.
Loading of post back data is in fact performed by the
Page class in the
ProcessPostData method, which is called twice by the
ProcessRequestMain
method - once before the
OnLoad method (before
OnPreLoad in ASP.NET
2.0), and again (if not called prior) just after the
OnLoad method
completes. This loading of post data before and after
OnLoad allows the
developer to add dynamic control instantiation code in the most derived
version of
OnLoad method without having to worry about when the base
version is called.
More details about event "catch-up" can be on this excellent
post. Also,
check out the
Page.ProcessRequestMain,
Page.ProcessPostData and
Control.AddedControl methods in the framework using
Lutz Roeder's
Reflector.
As promised earlier, here is a list of the event methods called by ASP.NET 2.0 in a page render life cycle:
Page Life cycle methods in ASP.NET 2.0
| Method |
Active |
| Constructor |
Always |
| Construct |
Always |
| TestDeviceFilter |
Always |
| AddParsedSubObject |
Always |
| DeterminePostBackMode |
Always |
| OnPreInit |
Always |
| LoadPersonalizationData |
Always |
| InitializeThemes |
Always |
| OnInit |
Always |
| ApplyControlSkin |
Always |
| ApplyPersonalization |
Always |
| OnInitComplete |
Always |
| LoadPageStateFromPersistenceMedium |
PostBack |
| LoadControlState |
PostBack |
| LoadViewState |
PostBack |
| ProcessPostData1 |
PostBack |
| OnPreLoad |
Always |
| OnLoad |
Always |
| ProcessPostData2 |
PostBack |
| RaiseChangedEvents |
PostBack |
| RaisePostBackEvent |
PostBack |
| OnLoadComplete |
Always |
| OnPreRender |
Always |
| OnPreRenderComplete |
Always |
| SavePersonalizationData |
Always |
| SaveControlState |
Always |
| SaveViewState |
Always |
| SavePageStateToPersistenceMedium |
Always |
| Render |
Always |
| OnUnload |
Always |