CoreCoder
LANGUAGES: C#
TECHNOLOGIES: Controls | Dynamic Creation | View State | Postback Events
Manage Dynamically Created Controls
Take advantage of automatic state maintenance for dynamic controls.
By Dino Esposito
One ASP.NET feature that most captures the attention of programmers is the full correspondence between markup elements and instances of controls. Any tags in the body of an .aspx page that has the runat attribute equal to "server" are translated into instances of a .NET Framework class. This apparently simple fact has an important repercussion: It allows you to create and configure controls dynamically. For example, you can read profile information out of a data source and build the page to serve to the user dynamically; you can arrange grids of data with optional columns that users toggle on and off at their convenience; and you can assemble composite controls and forms on the fly.
In ASP.NET applications, you can introduce dynamic contents in your page layout in several ways - not simply create controls when needed. In this article, I'll provide a brief overview of these techniques, then I'll focus on the dynamic creation of control instances and the issues it poses for state maintenance and postback data management.
Be Dynamic
Generally speaking, you have three ways to make your pages host a varied set of controls each time they display to the browser. You can include all controls you ever need in the page layout and turn them on and off using the Visible attribute; you can load small subforms from user-control files (resources saved with an .ascx extension); or, as this article describes in detail, you can use the "new" operator to create instances of controls dynamically. Naturally, each approach has its pros and cons, and there are scenarios in which each works better than the others.
First, consider a case in which you include in the source
of the page all possible controls the Web form might need. When the page is
loaded initially, the ASP.NET runtime parses the .aspx source and creates a
dynamic class. This class derives, directly or indirectly, from Page, and its
primary task is building the tree of controls that belong to the main form. At
parse time, all tags with the runat attribute equal to the server are located
and transformed in code. For example, the tag
private Control BuildControlButton1()
{
Button ctrl = new Button();
this.Button1 = ctrl;
ctrl.ID = "Button1";
ctrl.Text = "Button";
return ctrl;
}
Similar methods are generated for all server-side controls. As a result, when the ASP.NET runtime begins processing the request, it instantiates the Page class, thus initializing all contained server controls irrespective of how many and which controls actually will be rendered. When the page enters rendering mode, only the controls with the Visible attribute set to True will generate HTML. You can add or exclude controls from view simply by toggling on and off the Visible attribute, but an instance of all declared controls always is created. As you'll see in a moment, this is an important aspect to consider.
Another way to create contents dynamically is through the Page class's LoadControl method:
// Create an instance of the user control
Control c = LoadControl(urlOfAscx);
// Link it to a particular position in the layout
thePlaceHolder.Controls.Add(c);
The source code of the control must be saved in a separate file with the .ascx extension. You load it from a virtual path and bind it to the Controls collection of a server control. This technique is useful when portions of the user interface can be rendered according to different skins. To ensure the dynamically created control is placed in a particular position within the parent form, you can use a placeholder and link the control to the placeholder's Controls collection.
Create Controls Dynamically
As I mentioned, using the "new" operator is the principal way to create dynamic controls in ASP.NET. This code shows a page that adds a new textbox control when you click on a button:
The OnClick event handler looks as it does in this code snippet:
public void OnClick(object sender, EventArgs e) {
AddTextBox();
}
public void AddTextBox() {
TextBox ctl = new TextBox();
ctl.Text = "Hello";
placeHolder.Controls.Add(ctl);
}
The newly created control is added to the placeholder, giving you full control over its position, even in a flow layout. Is there something wrong with this code? Not until you add another control to the page that can initiate a postback. Add another button and bind it to some server-side method:
onclick="OnPostBack" /> If you click on this new button after creating the dynamic
control, you might be unpleasantly surprised: The dynamic control disappears
when the page refreshes in the browser (see Figure 1). Because the textbox is created only by the server event
bound to the Create button, nothing guarantees it will be created again when
another element causes the round trip (for example, the PostBack button). In
particular, when the page posts back because someone clicked on the PostBack
button, there's no path in the code that actually re-creates the textbox. To ensure dynamic controls are created upon each postback,
you should create them in the Page_Load event. Depending on the requirements of
the application, though, this is not always possible. So how can you ensure
that a control created by a particular postback event is always re-created?
Furthermore, how can you ensure that the view state is restored correctly? Remember the Controls You Created To track the controls created dynamically, you must use a
persistent data store, such as the view state. A simple trick you can employ
consists of adding some information to the page's view state whenever a dynamic
control is created. The syntax and semantics of what you add to the view state
are completely up to you. The code in Figure 2 shows you how to extend the previous
code to write a note in the page's view state when a particular control is
added. Whenever the page posts back, the code in the Page_Load handler checks
the page's view state and re-creates the control if needed. For this pattern to
work, you must insulate the control factory code in a method that can be
invoked from various places such as AddTextBox. public void Page_Load() { if (IsPostBack) { if
(ViewState["AddedControl"] != null) { // Re-create the
control but do not // restore
settings configured outside // the proc
(i.e., MaxLength and BackColor) TextBox t =
AddTextBox(); } } } public void OnClick(object sender, EventArgs e) { TextBox t =
AddTextBox(); // Modify properties
outside the proc to // simulate generic
changed to the // control's view state t.MaxLength = 5; t.BackColor =
Color.Yellow; } public TextBox AddTextBox() { TextBox ctl = new
TextBox(); ctl.Text =
"Hello"; placeHolder.Controls.Add(ctl); // Records that a
dynamic control has been added ViewState["AddedControl"] = "true"; return ctl; } Figure 2. The OnClick event creates a textbox when
invoked and stores a value in the view state to track it. Whenever the page
posts back, the code in Page_Load re-creates the control if needed. When I first tested this code, I was quite surprised that
it worked. I was sure the control would be re-created correctly, but I was
doubtful about the correct restoration of the control's view state and postback
data. To ensure no path in the code could reiterate settings
such as MaxLength and BackColor programmatically (both are stored in the
TextBox view state), I placed them outside the AddTextBox method deliberately.
As a matter of fact, in Figure 2, the BackColor and MaxLength properties are
set only if you choose to run the OnClick method. So, what was happening to
magically restore the view state and the postback data of a dynamically created
textbox? Give It a Second Try A common misconception among ASP.NET programmers is that
the page restores the state completely and processes postback data when the
Page_Load event fires. Well, the code in Figure 2 is proof that, for some
controls, something else happens after Page_Load fires to restore the view
state and postback data. I also tried moving the code of Page_Load into
Page_Init, only to get a runtime error. As expected, the view state is null at
that time, so the code can't work. While testing the sample page, I had the
Trace option turned on. I can't say how many times I had a trace output before
me, but I always failed to make sense of the pair of begin/end messages
described as the "ProcessPostData Second Try." It turns out that the ASP.NET runtime processes posted
values in two steps. The first step occurs immediately after restoring the view
state, as the documentation clearly states. Then, the Load event fires to the
page and recursively to all child controls. By now, you might think the page is
pretty much done and only needs to raise server changes and postback events.
Nevertheless, before raising events, the runtime gives a second try to controls
to process postback data. You can consider this feature to be specific ASP.NET
support for dynamically created controls. Basically, during the first step, the
runtime processes the values in the query string or the Request.Form
collection, depending on the HTTP method used to post the form. If no match is
found between one of the posted IDs and controls in the page, the ID/value pair
is copied into a temporary collection that will be processed again during the
second try - guess what happens now? During the first step, the Request.Form collection
contains a reference to the dynamically created textbox, but no control with
that ID exists at the time. For this reason, the ID of the control is set aside
for later. Between the first and second steps, Page_Load fires, and in the
example, I re-create the dynamic control just in time for the second try to
catch it and process postback data correctly. This is really, really cool! But my enthusiasm for this discovery was short-lived: It's
OK for the posted values, but what about the view state? Who is restoring the
view state of a dynamically created control, and when? The view state cannot be restored in the traced
"LoadViewState" timeframe because, at that time, the dynamic control hasn't
been re-created yet. Loading the view state also has nothing to do with
restoring postback data. Nevertheless, the view state is managed correctly. As
my mother used to say, "If you don't know something, just ask." So, I asked the
textbox to trace when the view state actually was restored (see Figure 3). namespace AspNetPro { public class TextBox :
System.Web.UI.WebControls.TextBox { public override int
MaxLength { get { Page.Trace.Warn("get MaxLength"); return
base.MaxLength; } set { base.MaxLength = value; Page.Trace.Warn("set maxLength"); } } protected override
void LoadViewState(object state) { base.LoadViewState(state); Page.Trace.Warn("Loading state for " + UniqueID); } } } Figure 3. Here is a custom
TextBox class that traces the view-state restoration. I derived a class from TextBox exclusively to override
LoadViewState and one of the properties known to be stored in the view state
(such as MaxLength). The overrides are minimal and limited to tracing the fact
that a call was made. This is a debugging technique I sometimes use to step
into the internals of system classes. The new AspNetPro.TextBox class is used in Figure 2 in
lieu of the original TextBox class. Figure 4 shows the results and reveals the
mystery. The view state of a dynamically created control is
restored within Page_Load automatically, particularly when the control is added
to the parent's Controls collection. In fact, the Controls.Add method always
makes a call to load the view state recursively for controls that are added
dynamically. In ASP.NET, there's only one way to create controls: Use
the "new" operator. The runat=server attribute is simply its declarative
counterpart. In light of this, dynamically created controls are no exception. I
was pleased to discover, however, that the ASP.NET framework also has effective
support for restoring state and postback data for dynamically created controls.
In the end, you have only one trick to take care of: Re-creating the controls
in the Page_Load event. The sample code in this
article is available for download. Dino Esposito is a trainer and consultant who specializes
in ASP.NET, ADO.NET, and XML. The author of Building Web Solutions with ASP.NET and ADO.NET and
the upcoming Programming
ASP.NET (both from Microsoft Press), Dino also is a co-founder of http://www.VB2TheMax.com.
E-mail him at mailto:[email protected]. Tell us what you think! Please send
any comments about this article to [email protected].
Please include the article title and author.
Figure 1. The dynamically created control disappears when the page posts
back. There's no code outside the event handler of the Create button to create
the control upon postbacks caused by other controls.
Figure 4. A derived class can help you determine internal behaviors of
system classes.