Learning to play "catch-up"
Note: this entry has moved.
The control execution lifecycle has proven to be one of the most difficult things to grasp by letting beginners totally disconcerted about how the webform model works. About every control and page developer that is messing with dynamically created controls has already played –knowingly or unconsciously- the “catch up” game. Playing it knowing its rules will always make you a winner, on the contrary, playing it without that knowledge will cause you anything but trouble.
Here is some code that dynamically creates a textbox control and adds it to the form control:
TextBox tb = new TextBox ();
tb.Text = “blah”;
tb.ID = “mytb”;
Control frm = FindControl (“FormID”);
frm.Controls.Add (tb);
As you can see there is nothing extraordinary about it, on the contrary it’s pretty darn simple; the difficult part is answering the question: “where can I put that code?”
Reading the docs is just not enough
By looking at the docs we can see that the Load phase is happening past the Load viewstate which would imply that viewstate data is always loaded before the Load event. If this is true, adding our previous snippet of code at Page_Load should cause our textbox to not load its viewstate properly, right? Wrong. The textbox, when added to a ControlCollection collection (in our example it’s the one for our form control), will immediately play “catch-up” and perform every action that it has missed due to its late insertion in the control tree (Init and LoadViewState in our example) thus properly restoring its viewstate data.
If you’re into control development chances are that you already own Nikhil’s book. It’s the only source I’ve found that briefly touches this subject with the following paragraph on page 179:
“…what if a control is created in an event handler and dynamically added to the control tree? In that case, the control plays catch-up. As soon as it is added to the control tree, it starts to execute its phases until it reaches the current phase of the page…”
It is also the source from where I had stolen the “catch-up” term for my post’s title. J
Now, where is this “catch-up” game being played…?
Entering the “Catch-up” Stadium
The catch up happens at the internal protected Control.AddedControl method which –after removing the control from any previous parent and taking care of its ID generation- will examine the current status of the control owning the ControlCollection and based on it determine all the steps the added control has already missed forcing it to “play” them at once. So following our previous example, it means that when we add tb to the ControlCollection of the form control (through the Controls property), Control.AddedControl will immediately call tb’s InitRecursive and LoadViewStateRecursive methods. But who is calling AddedControl in the first place? It’s the ControlsCollection.Add method which makes that call as its last step.
What exactly can be “catched up”?
The internal ControlState enumeration defines the five possible statuses a control may have during its lifetime:
Status |
Value |
Constructed |
0 |
Initialized |
1 |
ViewStateLoaded |
2 |
Loaded |
3 |
PreRendered |
4 |
So, depending on the current status of the parent control, Control.AddedControl will call the following internal methods on the control being added: Control.InitRecursive (if parent is already initialized), Control.LoadViewStateRecursive (if parent’s viewstate is already loaded), Control.LoadRecursive (if parent loading phase has been already executed) and Control.PreRenderRecursiveInternal (if parent pre-rendering phase has been already executed)
When is this game played?
This “catch up” game is not always played. If the parent’s status is Constructed meaning it has not been even initialized yet the Control.AddedControl will just return without playing any games because there is nothing to catch up for the child control.
A curiosity
If you were paying real attention to the method names listed in the above paragraph you may have noticed that they all follow the naming pattern “phase in the control execution lifecycle” + the word “Recursive” to clearly denote they’re recursive methods. The only exception is PreRenderRecursiveInternal that happens to include its access modifier of internal as part of its name. But all of the above mentioned methods are internal so why only this one got such a postfix?