views:

158

answers:

4

I'm working on a questionnaire type application in which questions are stored in a database. Therefore, I create my controls dynamically on every Page.OnLoad. This works like a charm and ViewState is persisted between postbacks because I ensure that my dynamic controls always have the same generated Control.ID.

In addition to the user control that dynamically populates the questions, my questionnaire page also contains a 'Status' section (also encapsulated by a user control) which represents the status of the questionnaire (choices are 'Complete', 'Started' or 'In Progress'). If the user changes the status of questionnaire (i.e. from 'In Progress' to 'Complete'), I need to postback to the server because the contents of the dynamic portion of the questionnaire depend on the selected status.

Some questions are always present regardless of status, and yet others may not be present at all for the selected status. The point is, when the status changes, I have to postback to the page and render the right set of questions. Additionally, I need to preserve any user entered values for those questions which are 'always available'.

However, due to the page life cycle in ASP.NET, the 'Status' user control's OnLoad, which contains the correct status needed to load the right questions from the DB, doesn't get executed until after the 'dynamic questions' user control has already been populated (with the wrong/stale values).

To get around this, I raise an event from my 'Status' user control to the main page to indicate that the Status has changed. The main page then raises an event on the 'dynamic questions' user control. Since by the time this event bubbles up, the 'dynamic questions' user control has already loaded the 'wrong' questions from the DB, it first calls Controls.Clear. It then happily uses the new status to query the database for the 'correct' questions and does a Control.Add() on each. FYI, Control.IDs are consistent across postbacks.

This solution works...sorta. The correct set of questions for the selected status do get rendered; however ViewState is getting lost for those 'always available' questions. I'm guessing this is because the 'dynamic questions' user control calls Controls.Clear when responding to the status changed event. This must somehow kill the association between ViewState and my dynamic controls, even though the Control.IDs are consistent.

This seems like such a common requirement, I'm virtually certain there is a better, cleaner and less error prone approach to accomplish this. In case its not plainly obvious, I haven't been able to grok the ASP.NET page life-cycle despite working with it for the last year. Any help is much appreciated!

A: 

You could change it to just load the controls (without determining any status dependent stuff) in Page_Load, and move the status specific rendering of the controls to Page_PreRender.

The dynamic user control can read from the database in it's PreRender event (or DataBind event), if the user control is the one that has the responsibility of what to load.

I think the problem is that the user controls tries to read the status before all controls are loaded and the viewstate is loaded. By delaying the status-dependent display to a later page event, you make sure it is based on the right status.

awe
I'll give this a try tonight.
Hans Gruber
A: 

I ensure that my dynamic controls always have the same generated Control.ID

ViewState are made regadless to control`s ID. If you want process OnClick event inside your dynamic control with inputed by user data, you have to restore the structure of your controls hierarchy before RaisPostBackEvent event, that fired right after Page_load.

If you want to save your dynamic controls with the same state in other position on your page, or you want to have access to controls after creating other dynamic controls you HAVE TO save controls hierarchy.

So! Create your first portion of dynamic controls in one container and create controls on postback in other container.

For example on page request you render:

< asp:placeholder id="iHaveToBeDuringAllRequests1" >
  First dynamic cintrols
< /asp:placeholder >
< asp:placeholder id="iHaveToBeDuringAllRequests2" >
  Empty on first request 
< /asp:placeholder >

On PostBack you render:

< asp:placeholder id="iHaveToBeDuringAllRequests1" >
  First dynamic cintrols(you can clear or do not load these controls if you don't need it)
< /asp:placeholder >
< asp:placeholder id="iHaveToBeDuringAllRequests2" >
  Second dynamic controls load here
< /asp:placeholder >

And last tip:

I ensure that my dynamic controls always have the same generated Control.ID

You need to ensure this for correct firing RaisPostBackEvent(OnClick etc.) by your dynamic controls(you can use INamingContainer container instead of placeholder).

chapluck
A: 

Hmm - ASP.NET page life cycle; one of my favourites :)

I'm guessing this is because the 'dynamic questions' user control calls Controls.Clear when responding to the status changed event. This must somehow kill the association between ViewState and my dynamic controls, even though the Control.IDs are consistent.

Sounds like this is recreating everything from view state (including the "always on" questions) and then chuck it to the bin. After this the controls are recreated (without any view state because recreation of it has already been done). So yes, I too think Controls.Clear is the reason for your lost view state.

This is probably not a one-liner to solve. I could imagine different approaches.

However, due to the page life cycle in ASP.NET, the 'Status' user control's OnLoad, which contains the correct status needed to load the right questions from the DB, doesn't get executed until after the 'dynamic questions' user control has already been populated (with the wrong/stale values).

What do you mean by "the OnLoad contains the correct status"? I would say it should be possible to fetch the "current selection" from the status control during the dynamic questions "data bind" (if you happen to have something like this in your dynamic questions control). The reason for /me thinking this is that the "data bind" (in the parent) happens after point 3 mentioned in the article "The ASP.NET Page Life Cycle" provided below. So if your dynamic questions control knows about the status it has to honour, it should be able to fetch it. If this does not work, I would first check if the problem lies within the status control. If your dynamic questions control does not know about any status this will obviously not work.

Another approach would be to have your dynamic questions control not delete all it's children (Controls.Clear) but only those that are not needed anymore. You could (and I think you should) do this in PreRender. So it might help to have a mean for the dynamic questions control to distinguish "always on" questions from others. You could then stick to your current tactics and the event.

If you want to dig deeper into the ASP.NET page life cycle and view state and do not know them already these two links might provide valuable information.

The ASP.NET Page Life Cycle and TRULY Understanding ViewState.

In case its not plainly obvious, I haven't been able to grok the ASP.NET page life-cycle despite working with it for the last year.

Let me know if you find someone who has :)

scherand
Ha ha too right! Lifecycle and ViewState are in my opinion the most difficult thing about ASP.NET.
David
+2  A: 

However, due to the page life cycle in ASP.NET, the 'Status' user control's OnLoad, which contains the correct status needed to load the right questions from the DB, doesn't get executed until after the 'dynamic questions' user control has already been populated (with the wrong/stale values).

If you know the client id of the underlying status control, you can get the posted value at any point in the page life cycle by circumventing web forms and directly accessing the posted form data:

// where 'selectedStatus' is the id of the html <input> control the user clicks
string statusString = Request.Form["selectedStatus"];
Jeff Sternal
Microsoft seem to dislike people doing this, but I find it really useful.As an additional help, the Control's UniqueID property corresponds to the generated 'name' attribute on the control, so you can use it to fish the value out of the PostData collection.
David
This solution should be used at a pinch that is dictated by particularity of client-side logic. In this case it is dirtying a logic.
chapluck