Wednesday, October 28, 2009

Viewstate, Composite Controls, and Event handlers

I’ve seen a number of posts where individuals are finding that the events in their composite controls are not firing (i.e. the handlers for those events are not executed). I ran into this as well because I had certain sub-controls in a composite control which depended on viewstate for their existence. I was using parameters in viewstate to rebuild certain controls – controls which could raise events. Also, the parameters of the raised events were used to determine which event-raising controls existed.

Of course, viewstate is not available until the Load event.

viewstate reloads after OnInit and before the Load event. Because of this, viewstate will never be available in the OnInit phase. Any code that depends on viewstate must execute after viewstate is loaded. You must code your page around this requirement.

Now, that’s because the TrackViewState method is responsible for initializing view state, and it takes place after the OnInit is executed and before the OnLoad method.

The TrackViewState method executes immediately after initialization and marks the beginning of ViewState processing, and state tracking, in the control life cycle.

Anything done before TrackViewState will not come from viewstate, or be persisted to viewstate. As for the Load Event:

The Load event should be quite familiar to you, because we have leveraged this convenient location for common page code in our web forms in previous examples. It is a handy place to put page initialization logic, because you are guaranteed that all controls in the Page’s control tree are created and all state-loading mechanisms have restored each control’s state back to where it was at the end of the previous request/ response page round-trip. This event also occurs before any controls in the Page’s control tree fire their specific events resulting from value changes in postback data. To customize control behavior in this phase, override the OnLoad method.

Also, controls must be added as child controls in composite controls before their state can be persisted. See here:

However, what if the control tree hierarchy depends on view state?

Say we have a pager control, for which the properties are stored in view state. This pager initiates events which alter its own contents. If you click the last element, the pager needs to change in the next render. But, if you’re using a postback linkbutton, or any postback, a button with the same ID as the emitter needs to be recreated to raise and capture the event.

Barring the CompositeDataBoundControl, the place to insert the first recreation of the object tree is at the end of LoadViewState. MSDN reads at this point:

At the end of this phase, the ViewState property of a control is automatically populated as described in Maintaining State in a Control. A control can override the default implementation of the LoadViewState method to customize state restoration.

So the Viewstate is restored at the end of this method. This also happens BEFORE any postback is processed. You need the PREVIOUS control hierarchy built to process the linkbutton event, or it will just go missing. So it might look like this:

protected override void LoadViewState(object savedState)

   // We need all controls to be recreated in
   // the same state as the previous life-cycle so
   // that events can be processed. Those events
   // might require a change in the hierarchy, and
   // if so, the whole control hierarchy must be
   // regenerated before rendering.

This creates the control hierarchy, and restores the necessary parameters from view state (in this case, the pager might have the current page and the number of items on a page).

The event will bubble up from the linkbutton, and can now be intercepted.

But, the pager has changed, and so has the visible data. The control hierarchy can be rebuilt (again) in OnPreRender according to the new parameters.

protected override void OnPreRender(EventArgs e)
   if (doesControlHierarchyNeedToBeRecreated || Page.IsPostBack == false)
      // Recreate all child controls - something changed  
      // that makes the control hierarchy stale.


Notice that we also need to create it during the first GET operation, since the LoadViewState method is not called. The doesControlHierarchyNeedToBeRecreated boolean flag would be set in the event handler or OnBubbleEvent as triggered by the child controls in the pager or the listed data.

Hence, the flow is:

1. Init event (no viewstate available)

2. LoadViewState (viewstate restored by the end of this method; call the base).

3. At the end of LoadViewState, create the control hierarchy (i.e. CreateChildControls) and restore its state using the parameters in view state. The control hierarchy will now mirror the exact state at the point of the most recent rendering of the control.

4. The process postback data phase happens here. We must have the control hierarchy rebuilt by this point in the cycle.

5. OnLoad is triggered.

6. Event handling happens here. Catch any bubble events from the child controls. Since the control hierarchy has been created in step (3) exactly like the hierarchy that was rendered in the last cycle, from which the user triggered the event, ASP.NET will be able to find the originating control and fire the event. The viewstate parameters used in (3) can be updated here based on the events. If so, flag that the control hierarchy needs to be rebuilt, or the render will be a full cycle behind.

7. Prerender occurs. If an event in (5) has changed the output of the control, rebuild the control hierarchy as necessary at this point and restore its state.

This sequence enables you to (a) capture events raised using child controls in a composite control, (b) use view state to store the parameters that are used to construct the very child controls that can raise those events, and (c) update the rendered web control based on the latest state received through those child controls.

These three are otherwise in tension, since you need the controls to exist so that their events can be raised, yet those events themselves require that the controls be rebuilt. This requires the control hierarchy to be restored AFTER view state is populated but BEFORE the events are processed; i.e. in LoadViewState.

If there is a better way to deal with such scenarios (such as minimizing the amount of data you place in viewstate, using query strings, etc), I certainly want to hear it.

Various References

Control Building and ViewState Lesson for the Day

Composite Controls and the Missing View State

ASP.NET page lifecycle:

Control execution lifecycle:

No comments: