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, i.e.:
- page1.aspx loads for the first time,
OnInit
fires... OnLoad
fires - User clicks button which posts back to page2.aspx
- 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="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<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" />
</asp:Panel>
<br />
<asp:PlaceHolder ID="ControlsPlaceHolder" runat="server"></asp:PlaceHolder>
<br />
</div>
</form>
</body>
</html>
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))
{
BuildDynamicTextBoxes(_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();
ControlsPlaceHolder.Controls.Add(_textBox);
Literal _lit = new Literal();
_lit.Text = "<br/>";
ControlsPlaceHolder.Controls.Add(_lit);
}
Button _submitButton = new Button();
_submitButton.Text = "Submit";
_submitButton.ID = "btnSubmit";
_submitButton.PostBackUrl =
"ViewStateTest.aspx?ReloadControls=yes&NumControls=" + numTextBoxes;
ControlsPlaceHolder.Controls.Add(_submitButton);
}
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)
{
if (Request.QueryString["ReloadControls"] != null &&
Request.QueryString["ReloadControls"].ToString() == "yes")
{
if (Request.QueryString["NumControls"] != null)
{
int _numControls = int.Parse(Request.QueryString["NumControls"].ToString());
BuildDynamicTextBoxes(_numControls);
}
}
}
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.
CodeProject