Dev Basics: ASP.NET Page Life Cycle, Part 2 [WebControl Execution Order]

The first installment of this series goes back to the beginning and describes each of the events within ASP.NET Page Life Cycle. Understanding the basic fundamentals of the ASP.NET Page Life Cycle, including the order and scope of influence for each of the Page Life Cycle events, will help ensure that you are executing your custom code at the right time, and in the right order, rather than stepping on yourself by conflicting with core ASP.NET framework functionality. But this is only part of the story, since there is more to the ASP.NET Page Life Cycle than just the page, itself.

ASP.NET Page & WebControl Event Execution Order

Pages would be nothing but a sea of crazy peach gradient backgrounds without the controls to display content and to interact with the user. In addition to the order of the various page events, it is often helpful to know the order in which a page and its controls execute a single event. Does Page.Load execute before Control.Load? Does Page.Init execute before Control.Init? Does myTextBox.TextChanged fire before myButton.Click? And what about myTextBox1.TextChanged versus myTextBox2.TextChanged?

Knowing the execution order of events within the control tree will make you a better ASP.NET developer. If you cannot answer each of those questions above (and maybe even if you can), keep reading.

About the Series

When a request occurs for an ASP.NET page, the response is processed through a series of events before being sent to the client browser. These events, known as the ASP.NET Page Life Cycle, are a complicated headache when used improperly, manifesting as odd exceptions, incorrect data, performance issues, and general confusion. It seems simple when reading yet-another-book-on-ASP.NET, but never when applied in the real world. What is covered in a few short pages in many ASP.NET books (and sometimes even just a few short paragraphs), is much more complicated outside of a "Hello, World!" application and inside of the complex demands of the enterprise applications that developers create and maintain in their day-to-day work life. As close to the core as the life cycle is to any ASP.NET web application, the complications and catches behind this system never seems to get wide coverage on study guides or other documentation. But, they should.

Posts in this Series

Part 1: Events of the ASP.NET Page Life Cycle
Part 2: ASP.NET Page & WebControl Event Execution
Part 3: Getting Started with ASP.NET DataBinding
Part 4: Wiring Events to Your Page

Event Execution Order within the Control Hierarchy

At the core of the control-level event execution order is where the events fire with respect to the page. The majority of the events in the ASP.NET Page Life Cycle execute from the top, down, which is also referred to as outside-in. That is, the event is first executed on the page, such as Page.Load, then executed recursively through each of the page's controls, Control.Load, to the controls within controls, and so on. The two exceptions to this rule are Initialization and Unload. With these two events, the event is fired first on the child control, then on the container control, and finally on the page, known as a bottom-up or inside-out order.

But what if a control is dynamically added to the page during a later event? In this case, a control will fire events to catch up to the page (though a control will never exceed beyond what Page Event is currently executing). In other words, if a control is dynamically added during the PreInit page event, the control will immediately fire its own PreInit. However, if a control is dynamically added during the PreLoad event, it will fire PreInit, Init, InitComplete, and PreLoad, all in quick succession.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private void Page_PreInit(object sender, EventArgs e)
{
    Trace.Write("Executing Page PreInitialization");
    var textbox = new TextBox();
    textbox.Init += Control_Init;
    textbox.Load += Control_Load;
    textbox.ID += "TextBoxFromPreInit";
    form1.Controls.Add(textbox);
}

private void Page_Init(object sender, EventArgs e)
{
    Trace.Write("Executing Page Initialization (Should occur after controls)");
}

private void Page_Load(object sender, EventArgs e)
{
    Trace.Write("Executing Page Load (Should occur before controls)");
    var textbox = new TextBox();
    textbox.Init += Control_Init;
    textbox.Load += Control_Load;
    textbox.ID += "TextBoxFromLoad";
    form1.Controls.Add(textbox);
}

private void Control_Init(object sender, EventArgs e)
{
    Trace.Write("Executing Control Init for " + ((Control)sender).UniqueID);
}

private void Control_Load(object sender, EventArgs e)
{
    Trace.Write("Executing Control Load for " + ((Control)sender).UniqueID);
}

/*
Output: 

Begin PreInit		
Executing Page PreInitialization
End PreInit
Begin Init
Executing Control Init for TextBoxFromPreInit
Executing Page Initialization (Should occur after controls)
End Init
Begin Load
Executing Page Load (Should occur before controls)
Executing Control Init for TextBoxFromLoad
Executing Control Load for TextBoxFromPreInit
Executing Control Load for TextBoxFromLoad
End Load
*/

Event Execution Order for Sibling WebControls

The event execution order of parent and child controls is simple and straightforward. As if to maintain balance in The Force, the event execution order for sibling controls is a bit complicated. For siblings, this order is governed by three main and cascading criteria: the type of event that is being executed, the Page Event executing when the control was added to the page, and the index of the control within the parent's (or page's) Controls collection.

First, the event type is the primary governor of when an event is fired. Just like the order of Page Events, Initialize events always occur before Load events, and Load events always occur before Render events. The complication surrounds the several control-specific "PostBack Events," such as Click or TextChanged, as there are three PostBack event types: Changed Events, Validation Events, and actual PostBack Events. The first that fire are Changed Events, which include any event where the value changes, such as TextBox.TextChanged or DropDownList.SelectedIndexChanged. Changed events should include any custom Value manipulation for each of your form controls. Once the values are defined, Validation events are executed to assist with ensuring data integrity. Finally, once all values are defined and validated, PostBack Events, such as Button.Command or Button.Click, are executed. In most cases, these PostBack events will include the form submission logic, such as sending the email, transmitting data through a Web Service, or saving data to a database. The Changed Events type of events always fire before Validation events, which always fire before the PostBack Events types; TextBox.TextChanged before Validator.Validate before Button.Click.

If the events are the same, such as two TextBox controls that are both executing TextChanged, the second criteria to determine sibling event execution is when the control was added to the page. If a control was added in any of the Initialization events (PreInit, Init, InitComplete), it is executed first. If a control was added in any of the Load events, it is executed second. So, for the two TextBoxes, the TextChanged event for the TextBox added during Initialization will be fired before the same event for the TextBox added during Load. (txtAddedDuringInit.TextChanged will fire before txtAddedDuringLoad.TextChanged.)

If the executing event is the same, and the controls were added during the same Page Event, the final criterion for sibling execution is the index within the Controls collection. After the above two criteria are considered, events that still have equal weight are executed according to their index in their parent's Controls collection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private void Page_Init(object sender, EventArgs e)
{
    TextBox textbox;
    textbox = new TextBox();
    textbox.TextChanged += Control_TextChanged;
    textbox.ID += "TextBoxFromInit1";
    form1.Controls.Add(textbox);
    textbox = new TextBox();
    textbox.TextChanged += Control_TextChanged;
    textbox.ID += "TextBoxFromInit2";
    form1.Controls.Add(textbox);
    textbox = new TextBox();
    textbox.TextChanged += Control_TextChanged;
    textbox.ID += "TextBoxFromInit3At0";
    form1.Controls.AddAt(0, textbox);
}

private void Page_Load(object sender, EventArgs e)
{
    TextBox textbox;
    textbox = new TextBox();
    textbox.TextChanged += Control_TextChanged;
    textbox.ID += "TextBoxFromLoad1";
    form1.Controls.Add(textbox);
    textbox = new TextBox();
    textbox.TextChanged += Control_TextChanged;
    textbox.ID += "TextBoxFromLoad2";
    form1.Controls.Add(textbox);
    textbox = new TextBox();
    textbox.TextChanged += Control_TextChanged;
    textbox.ID += "TextBoxFromLoad3At0";
    form1.Controls.AddAt(0, textbox);
}

private void Control_TextChanged(object sender, EventArgs e)
{
    Trace.Write("Executing Control TextChanged for " + ((Control) sender).UniqueID
                + " / Position: " + form1.Controls.IndexOf((Control) sender));
}

/*
Trace Output: 

Begin Raise ChangedEvents
Executing Control TextChanged for TextBoxFromInit3At0 / Position: 1
Executing Control TextChanged for TextBoxFromInit1 / Position: 2
Executing Control TextChanged for TextBoxFromInit2 / Position: 3
Executing Control TextChanged for TextBoxFromLoad3At0 / Position: 0
Executing Control TextChanged for TextBoxFromLoad1 / Position: 4
Executing Control TextChanged for TextBoxFromLoad2 / Position: 5
End Raise ChangedEvents
*/

So, to address the questions from above: Page.Load does execute before Control.Load, as the Load event is executed outside-in, however, Page.Init executes after Control.Init, as the Init event is executed inside-out. The TextChanged event on myTextBox is fired prior to myButton.Click, as control ChangedEvents are executed before control PostBackEvents. And finally, regarding myTextBox1.TextChanged versus myTextBox2.TextChanged, it depends; the order is dependent upon where the controls exist within the entire hierarchy, when the controls were created, and upon their position within the Controls collection.

The execution order of control events within the page life cycle is a complicated mess, and fortunately does not come in to play often. But for when it does, it is important to know how everything plays together. I find that most often, the order is important when dynamically adding controls to the page outside of DataBinding (though I would consider this a design smell), when creating custom WebControls, or when working with control Changed Events and validation. Still, as with before, committing this to memory (or at least a link to a reference, such as this post) will help with making you a better ASP.NET developer and with creating higher quality applications.

So what's next? Part 1 covered the base ASP.NET Page Life Cycle, and this post covers the execution order of events on the page. As this series continues, we will discuss the details of the DataBinding events, and will dig in to some tips, tricks, and traps when developing ASP.NET applications.