Monday, 3 August 2009

How To Perpetuate Dynamic Controls Between Page Views in ASP.NET

One of the problems I've come across more than once in the recent past is with dynamically generated controls in ASP.NET pages and the fact that they seem to disappear in the form's post back.

The basic reason for this is that dynamically generated form controls, which are usually generated after the OnInit event of the page, don't exist in the ViewState and therefore don't perpetuate between form posts. The fix therefore, is to build, or re-build, any dynamically generated form controls in the OnInit event of the page. This can seem quite unintuitive, especially if you're posting back to another page - but it's still necessary. Let's just take a quick peak under the hood to see why...

The Page Lifecycle

As you are probably aware, the ASP.NET page passes through various stages in its lifecycle. Although you don't usually need to know much about all of these stages it is important to understand the difference between when the OnInit event fires and the OnLoad.

The OnInit event fires, as you might expect, before the page loads. At this stage controls can be added to the ViewState (the place where controls and their states are held between page views), but post back information can't be accessed.

The OnLoad event fires after the page has loaded and control states have been retrieved from ViewState. At this stage controls can't be added into ViewState, but PostBack data can be retrieved.

This understanding is fundamental to persisting the state of dynamically generated controls between page views.

Basically, the OnInit event fires every time the page responds to an event, even if you're posting back to another page... So, if you have page1.aspx, which contains a submit button which posts back to page2.aspx, the OnInit event code on page1.aspx will still run before diverting to page2.aspx.


1) page1.aspx loads for the first time, OnInit fires... OnLoad fires
2) User clicks button which posts back to page2.aspx.
3) page1.aspx OnInit event fires... page2.aspx page loads... OnInit fires... OnLoad fires.

Let's Take An Example

Let's say, for the sake of simplicity, that you have a form which dynamically generates text boxes. You want to retrieve the value of these text boxes after a submit button is clicked.

So, first things first, let's look at the simple .aspx page:

<html xmlns="">
<head runat="server">
<form id="form1" runat="server">

<asp:Panel ID="NumControlsPanel" runat="server">
<asp:Label ID="Label1" runat="server" Text="Num Controls:"></asp:Label><asp:TextBox ID="NumControls" runat="server"></asp:TextBox>
<br />
<asp:Button ID="ShowControlsButton" runat="server" Text="Show Controls"
onclick="ShowControlsButton_Click" />
<br />
<asp:PlaceHolder ID="ControlsPlaceHolder" runat="server"></asp:PlaceHolder>
<br />


So, we have a Panel control which holds a TextBox that the user can use for setting the number of text boxes that should be gererated, and a button for then showing these controls. The code behind the button could look like this:

protected void ShowControlsButton_Click(object sender, EventArgs e)
if (NumControls.Text != "")
int _numControls;
if (int.TryParse(NumControls.Text, out _numControls))

This calls a method, BuildDynamicTextBoxes, which generates the text boxes by adding them to a PlaceHolder control:

protected void BuildDynamicTextBoxes(int numTextBoxes)
for (int i = 0; i < numTextBoxes; i++)
TextBox _textBox = new TextBox();
_textBox.ID = "txt" + i.ToString();


Literal _lit = new Literal();
_lit.Text = "<br/>";


Button _submitButton = new Button();
_submitButton.Text = "Submit";
_submitButton.ID = "btnSubmit";
_submitButton.PostBackUrl = "ViewStateTest.aspx?ReloadControls=yes&NumControls=" + numTextBoxes;


As you can see, the method creates a dynamic number of text boxes and then appends a submit button to the PlaceHolder. Notice that the PostBackURL property of the submit button adds a couple of parameters to the querystring. These are important, as we shall soon see...

The Crucial OnInit Event

Let's look at the crucial OnInit event...
    protected override void OnInit(EventArgs e)
//Check the querystring
if (Request.QueryString["ReloadControls"] != null && Request.QueryString["ReloadControls"].ToString() == "yes")
if (Request.QueryString["NumControls"] != null)
int _numControls = int.Parse(Request.QueryString["NumControls"].ToString());


Here we check the querystring and, if the querystring contains the ReloadControls parameter the code then queries the number of controls from the NumControls parameter. It uses this information to call the BuildDynamicTextBoxes method.

If this code doesn't exist in the OnInit event you'll find that your dynamically generated controls disappear in the postback.

However, now, when the OnInit event fires, the controls that are added to the page dynamically enter into the ViewState, along with their state. Do this any later in the page lifecycle, such as the OnLoad event, and although you'll still see the controls on the rendered page, their state won't be stored between page views.

Why Pass Parameters in the Querystring?

Earlier I mentioned that the passing of the info in the querystring was important. Why? Well, remember that, although controls added to the page during the OnInit event do go into the ViewState, their state hasn't yet been loaded from the postback at this point. So, for example, querying the value of the NumControls textbox in the OnInit event will simply return an empty string. Do the same thing in the OnLoad event and you'll get the value that the user entered into the textbox no probs, but by then it's too late to build the controls dynamically... catch 22.

So, we need to use some other method for passing this information between page calls. I chose to use the querystring, but you could just as easily, and more transparently, use a session variable, a cookie, write to a database etc. etc.

No comments:

Post a Comment