Why do subcontrols initialize before their containers?

3.5k views Asked by At

Despite working with WebForms for years I still find myself getting confused about the event lifecycle from time to time. This isn't so much a problem that needs resolving, as hoping to get a better understanding of why things work the way they do.

Suppose you have a form:

Default.aspx:

<form>
  <MyControls:UserControl1 runat="server">
</form>

UserControl1:ascx:

<MyControls:UserControl2 runat="server">

The OnInit events occur in this order:

UserControl2_OnInit
UserControl1_OnInit
Default_OnInit

Isn't this just bass-ackwards? Shouldn't the Init code be run in the order that controls are created? Shouldn't a parent control be able to initialize properties of a child before its OnInit runs? That is, while you can initialize properties of subcontrols in markup, there's no direct way to have a parent control be able to dynamically set properties of the child control that will be available to its OnInit event.

What I've ended up doing is stuff like this:

override void UserControl2_OnInit()
{
    NamingContainer.OnInit += new EvenHandler(UserControl1_ActualInit);
}
protected void UserControl2_ActualInit(..) {
  // do actual init code here, which will occur before OnLoad but after it's parent
  // OnInit
}

So it's not an insurmountable problem. I just don't understand why it's a problem in the first place.

I realize that perhaps you might want to be able to have all your child controls initialized in your OnInit code. So fine, you should be able to call base.OnInit first, instead of after, your own initialization code, which should cause all the child control OnInit events to get run. But the event lifecycle doesn't work that way. The Init events are not chained recursively, they seem to run independently the parent events, and the innermost one always gets run first. But seems life would be a lot easier if they were simply chained recursively so you could either call the base event (or not) before you do your thing in any given situation. Is there something I'm missing that makes this seemingly counterintuitive situation desirable or even necessary?

2

There are 2 answers

0
Kasey Speakman On

This document should be the main source of truth for your lifecycle questions.

Basically, OnInit fires after a control's internal initialization in finished. Since the page control is the first control initialized, and during it's internal initialization it initializes all sub-controls (perhaps in the order that the Designer.cs file gives), then it makes sense for the Page's OnInit event to be the last one called, since it's not finished initializing until all it's sub-controls are initialized and their OnInit events fired. Graphically, it looks like this:

Page internal init
    Sub-control1 internal init
        Sub-sub-control3 internal init
        Sub-sub-control3 init finished / OnInit fired
    Sub-control1 init finished / OnInit fired
    Sub-control2 internal init
    Sub-control2 init finished / OnInit fired
Page init finished / OnInit fired

So order of inits in this case is:

  1. Sub-sub-control3 OnInit
  2. Sub-control1 OnInit
  3. Sub-control2 OnInit
  4. Page OnInit

Load also work similarly. In general you should treat most of the events as though the control will go through it's internal process first (which includes calling the same event on all sub-controls), and then fire your custom event handling code afterwards.

Most examples you find use Page_Load specifically because that should be the last event called in that phase (and it's after post back data is loaded). It wouldn't work very well for Page_Load to be called first and risk having controls not in a fully loaded state for your custom event handling code.

5
Chris F Carroll On

The mindset for asp.net parent & child controls is:

Parents know all about their children, but children know nothing about their parent.

This mindset makes sense for re-usable server controls. Re-usability needs the custom child control making no assumptions about the page it gets used on.

The snippet you give makes me guess that your child user controls are not aimed at re-usable as such; but rather are specialized controls which you use to break down the complexities of a large & tricky UI?

In this case I would still try to work with the 'children known nothing about their parent' mindset. Think http://www.google.co.uk/search?q=gof+mediator+pattern where the parent page is the mediator between your children (the wikipedia page is good).

But your children still need to know something about the parent right, because they are doing complex UI interactions? You can address this with interfaces. Each child depends not on the parent, but on an interface that defines exactly what the children need access to. As http://en.wikipedia.org/wiki/SOLID puts it, 'depend on abstractions, not on concretions'. DO one interface per child control: 'many client specific interfaces are better than one general purpose interface'

But it all ends up looking over-engineered, doesn't it? It turns out that a componentised UI where the components must interact, just is complex, and the components may turn out big n clunky. This was, imho, one of the reason for MS web forms ajax controls losing out to jQuery &c. even before MVC came along.

Add to this that web forms ui is very hard to unit test; and your confidence in your software quality dives.

I recommend: If you can, escape to a rewrite in MVC. If you can't, consider abandoning server-side controls which do clientside behaviour, and use jQuery instead. If you can't do that, simplify simplify simplify the UI. Even if that makes it less functional. If you don't want that, then your choices are: pay the expense of engineering the UI well; or pay the expense of not engineering it well.