CodeProjectOne 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 LifecycleAs 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.
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 ExampleLet'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:
<br /><span class="kwrd"><</span><span class="html">html</span> <span class="attr">xmlns</span><span class="kwrd">="http://www.w3.org/1999/xhtml"</span><span class="kwrd">></span><br /><span class="kwrd"><</span><span class="html">head</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></span><br /><span class="kwrd"><</span><span class="html">title</span><span class="kwrd">></</span><span class="html">title</span><span class="kwrd">></span><br /><span class="kwrd"></</span><span class="html">head</span><span class="kwrd">></span><br /><span class="kwrd"><</span><span class="html">body</span><span class="kwrd">></span><br /><span class="kwrd"><</span><span class="html">form</span> <span class="attr">id</span><span class="kwrd">="form1"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></span><br /><span class="kwrd"><</span><span class="html">div</span><span class="kwrd">></span><br /><br /> <span class="kwrd"><</span><span class="html">asp:Panel</span> <span class="attr">ID</span><span class="kwrd">="NumControlsPanel"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></span><br /> <span class="kwrd"><</span><span class="html">asp:Label</span> <span class="attr">ID</span><span class="kwrd">="Label1"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">Text</span><span class="kwrd">="Num Controls:"</span><span class="kwrd">></</span><span class="html">asp:Label</span><span class="kwrd">><</span><span class="html">asp:TextBox</span> <span class="attr">ID</span><span class="kwrd">="NumControls"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></</span><span class="html">asp:TextBox</span><span class="kwrd">></span><br /> <span class="kwrd"><</span><span class="html">br</span> <span class="kwrd">/></span><br /> <span class="kwrd"><</span><span class="html">asp:Button</span> <span class="attr">ID</span><span class="kwrd">="ShowControlsButton"</span> <span class="attr">runat</span><span class="kwrd">="server"</span> <span class="attr">Text</span><span class="kwrd">="Show Controls"</span><br /> <span class="attr">onclick</span><span class="kwrd">="ShowControlsButton_Click"</span> <span class="kwrd">/></span><br /> <span class="kwrd"></</span><span class="html">asp:Panel</span><span class="kwrd">></span><br /> <span class="kwrd"><</span><span class="html">br</span> <span class="kwrd">/></span><br /> <span class="kwrd"><</span><span class="html">asp:PlaceHolder</span> <span class="attr">ID</span><span class="kwrd">="ControlsPlaceHolder"</span> <span class="attr">runat</span><span class="kwrd">="server"</span><span class="kwrd">></</span><span class="html">asp:PlaceHolder</span><span class="kwrd">></span><br /> <span class="kwrd"><</span><span class="html">br</span> <span class="kwrd">/></span><br /><br /><span class="kwrd"></</span><span class="html">div</span><span class="kwrd">></span><br /><span class="kwrd"></</span><span class="html">form</span><span class="kwrd">></span><br /><span class="kwrd"></</span><span class="html">body</span><span class="kwrd">></span><br /><span class="kwrd"></</span><span class="html">html</span><span class="kwrd">></span>
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:
<!-- code formatted by http://manoli.net/csharpformat/ -->
<br /> <span class="kwrd">protected</span> <span class="kwrd">void</span> ShowControlsButton_Click(<span class="kwrd">object</span> sender, EventArgs e)<br /> {<br /> <span class="kwrd">if</span> (NumControls.Text != <span class="str">""</span>)<br /> {<br /> <span class="kwrd">int</span> _numControls;<br /> <span class="kwrd">if</span> (<span class="kwrd">int</span>.TryParse(NumControls.Text, <span class="kwrd">out</span> _numControls))<br /> {<br /> BuildDynamicTextBoxes(_numControls);<br /> }<br /> }<br /> }This calls a method, BuildDynamicTextBoxes, which generates the text boxes by adding them to a PlaceHolder control:
<!-- code formatted by http://manoli.net/csharpformat/ -->
<br /> <span class="kwrd">protected</span> <span class="kwrd">void</span> BuildDynamicTextBoxes(<span class="kwrd">int</span> numTextBoxes)<br /> {<br /> <span class="kwrd">for</span> (<span class="kwrd">int</span> i = 0; i < numTextBoxes; i++)<br /> {<br /> TextBox _textBox = <span class="kwrd">new</span> TextBox();<br /> _textBox.ID = <span class="str">"txt"</span> + i.ToString();<br /><br /> ControlsPlaceHolder.Controls.Add(_textBox);<br /><br /> Literal _lit = <span class="kwrd">new</span> Literal();<br /> _lit.Text = <span class="str">"<br/>"</span>;<br /><br /> ControlsPlaceHolder.Controls.Add(_lit);<br /> }<br /><br /> Button _submitButton = <span class="kwrd">new</span> Button();<br /> _submitButton.Text = <span class="str">"Submit"</span>;<br /> _submitButton.ID = <span class="str">"btnSubmit"</span>;<br /> _submitButton.PostBackUrl = <span class="str">"ViewStateTest.aspx?ReloadControls=yes&NumControls="</span> + numTextBoxes;<br /><br /> ControlsPlaceHolder.Controls.Add(_submitButton);<br /> }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...
<span class="kwrd">protected</span> <span class="kwrd">override</span> <span class="kwrd">void</span> OnInit(EventArgs e)<br /> {<br /> <span class="rem">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.