Event catch-up in ASP.NET 2.0 Controls

Pop quiz, what is wrong with the following class?

public class MyPlaceHolder : PlaceHolder
{
protected override void OnInit(EventArgs e)
{
Page.Trace.Write(this.ID, “In OnInit”);
base.OnInit(e);
}

protected override void OnLoad(EventArgs e)
{
Page.Trace.Write(this.ID, “In OnLoad”);
base.OnLoad(e);
}

protected override void OnUnload(EventArgs e)
{
Page.Trace.Write(this.ID, “In OnUnload”);
base.OnUnload(e);
}

protected override void OnPreRender(EventArgs e)
{
DropDownList myDropDownList1 = new DropDownList();
myDropDownList1.ID = “_myDropDownListDynamic1″;
myDropDownList1.Items.Add(new ListItem(“One”, “one”));
myDropDownList1.Items.Add(new ListItem(“Two”, “two”));
this.Controls.Add(myDropDownList1);
base.OnPreRender(e);
}
}

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.

protected internal virtual void AddedControl(Control control, int index)
{
if (control.OwnerControl != null)
{
throw new InvalidOperationException(SR.GetString(“Substitution_NotAllowed”));
}
if (control._parent != null)
{
control._parent.Controls.Remove(control);
}
control._parent = this;
control._page = this.Page;
control.flags.Clear(0x20000);
Control control1 = this.flags[0x80] ? this : this._namingContainer;
if (control1 != null)
{
control._namingContainer = control1;
if ((control._id == null) && !control.flags[0x40])
{
control.GenerateAutomaticID();
}
else if ((control._id != null) || ((control._occasionalFields != null) && (control._occasionalFields.Controls != null)))
{
control1.DirtyNameTable();
}
}
if (this._controlState >= ControlState.ChildrenInitialized)
{
control.InitRecursive(control1);
if (((control._controlState == ControlState.Initialized) && (control.RareFields != null)) && control.RareFields.RequiredControlState)
{
this.Page.RegisterRequiresControlState(control);
}
if (this._controlState >= ControlState.ViewStateLoaded)
{
object obj1 = null;
if ((this._occasionalFields != null) && (this._occasionalFields.ControlsViewState != null))
{
obj1 = this._occasionalFields.ControlsViewState[index];
if (this.LoadViewStateByID)
{
control.EnsureID();
obj1 = this._occasionalFields.ControlsViewState[control.ID];
this._occasionalFields.ControlsViewState.Remove(control.ID);
}
else
{
obj1 = this._occasionalFields.ControlsViewState[index];
this._occasionalFields.ControlsViewState.Remove(index);
}
}
control.LoadViewStateRecursive(obj1);
if (this._controlState >= ControlState.Loaded)
{
control.LoadRecursive();
if (this._controlState >= ControlState.PreRendered)
{
control.PreRenderRecursiveInternal();
}
}
}
}
}

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
About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 292 other followers

%d bloggers like this: