Manage Dynamically Created Controls
Take advantage of automatic state maintenance for dynamic controls.
October 30, 2009
CoreCoder
LANGUAGES: C#
TECHNOLOGIES: Controls | Dynamic Creation | ViewState | Postback Events
Manage Dynamically Created Controls
Take advantage of automatic state maintenance fordynamic controls.
By Dino Esposito
One ASP.NET feature that most captures the attention ofprogrammers is the full correspondence between markup elements and instances ofcontrols. Any tags in the body of an .aspx page that has the runat attributeequal to "server" are translated into instances of a .NET Framework class. Thisapparently simple fact has an important repercussion: It allows you to createand configure controls dynamically. For example, you can read profileinformation out of a data source and build the page to serve to the userdynamically; you can arrange grids of data with optional columns that userstoggle on and off at their convenience; and you can assemble composite controlsand forms on the fly.
In ASP.NET applications, you can introduce dynamiccontents in your page layout in several ways - not simply create controls whenneeded. 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 itposes for state maintenance and postback data management.
Be Dynamic
Generally speaking, you have three ways to make your pageshost a varied set of controls each time they display to the browser. You caninclude all controls you ever need in the page layout and turn them on and offusing the Visible attribute; you can load small subforms from user-controlfiles (resources saved with an .ascx extension); or, as this article describesin detail, you can use the "new" operator to create instances of controlsdynamically. Naturally, each approach has its pros and cons, and there are scenariosin which each works better than the others.
First, consider a case in which you include in the sourceof the page all possible controls the Web form might need. When the page isloaded initially, the ASP.NET runtime parses the .aspx source and creates adynamic class. This class derives, directly or indirectly, from Page, and itsprimary task is building the tree of controls that belong to the main form. Atparse time, all tags with the runat attribute equal to the server are locatedand transformed in code. For example, the tag originates this private method on the dynamicallycreated class that represents the page in action:
private Control BuildControlButton1()
{
Button ctrl = newButton();
this.Button1 = ctrl;
ctrl.ID ="Button1";
ctrl.Text ="Button";
return ctrl;
}
Similar methods are generated for all server-sidecontrols. As a result, when the ASP.NET runtime begins processing the request,it instantiates the Page class, thus initializing all contained server controlsirrespective of how many and which controls actually will be rendered. When thepage enters rendering mode, only the controls with the Visible attribute set toTrue will generate HTML. You can add or exclude controls from view simply bytoggling on and off the Visible attribute, but an instance of all declaredcontrols always is created. As you'll see in a moment, this is an importantaspect to consider.
Another way to create contents dynamically is through thePage 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 separatefile with the .ascx extension. You load it from a virtual path and bind it tothe Controls collection of a server control. This technique is useful whenportions of the user interface can be rendered according to different skins. Toensure the dynamically created control is placed in a particular positionwithin the parent form, you can use a placeholder and link the control to theplaceholder's Controls collection.
Create Controls Dynamically
As I mentioned, using the "new" operator is the principalway to create dynamic controls in ASP.NET. This code shows a page that adds anew textbox control when you click on a button:
onclick="OnClick" />
The OnClick event handler looks as it does in this codesnippet:
public void OnClick(object sender, EventArgs e) {
AddTextBox();
}
public void AddTextBox() {
TextBox ctl = newTextBox();
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 theresomething wrong with this code? Not until you add another control to the pagethat can initiate a postback. Add another button and bind it to someserver-side method:
onclick="OnPostBack" /> If you click on this new button after creating the dynamiccontrol, you might be unpleasantly surprised: The dynamic control disappearswhen the page refreshes in the browser (see Figure 1).
Figure 1. The dynamically created control disappears when the page postsback. There's no code outside the event handler of the Create button to createthe control upon postbacks caused by other controls. Because the textbox is created only by the server eventbound to the Create button, nothing guarantees it will be created again whenanother element causes the round trip (for example, the PostBack button). Inparticular, when the page posts back because someone clicked on the PostBackbutton, 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 ofthe application, though, this is not always possible. So how can you ensurethat 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 apersistent data store, such as the view state. A simple trick you can employconsists of adding some information to the page's view state whenever a dynamiccontrol is created. The syntax and semantics of what you add to the view stateare completely up to you. The code in Figure 2 shows you how to extend the previouscode to write a note in the page's view state when a particular control isadded. Whenever the page posts back, the code in the Page_Load handler checksthe page's view state and re-creates the control if needed. For this pattern towork, you must insulate the control factory code in a method that can beinvoked from various places such as AddTextBox. public void Page_Load() { if (IsPostBack) { if(ViewState["AddedControl"] != null) { // Re-create thecontrol but do not // restoresettings configured outside // the proc(i.e., MaxLength and BackColor) TextBox t =AddTextBox(); } }}public void OnClick(object sender, EventArgs e) { TextBox t =AddTextBox(); // Modify propertiesoutside the proc to // simulate genericchanged to the // control's view state t.MaxLength = 5; t.BackColor =Color.Yellow; }public TextBox AddTextBox() { TextBox ctl = newTextBox(); ctl.Text ="Hello"; placeHolder.Controls.Add(ctl); // Records that adynamic control has been added ViewState["AddedControl"] = "true"; return ctl; }Figure 2. The OnClick event creates a textbox wheninvoked and stores a value in the view state to track it. Whenever the pageposts back, the code in Page_Load re-creates the control if needed. When I first tested this code, I was quite surprised thatit worked. I was sure the control would be re-created correctly, but I wasdoubtful about the correct restoration of the control's view state and postbackdata. To ensure no path in the code could reiterate settingssuch as MaxLength and BackColor programmatically (both are stored in theTextBox view state), I placed them outside the AddTextBox method deliberately.As a matter of fact, in Figure 2, the BackColor and MaxLength properties areset only if you choose to run the OnClick method. So, what was happening tomagically restore the view state and the postback data of a dynamically createdtextbox? Give It a Second Try A common misconception among ASP.NET programmers is thatthe page restores the state completely and processes postback data when thePage_Load event fires. Well, the code in Figure 2 is proof that, for somecontrols, something else happens after Page_Load fires to restore the viewstate and postback data. I also tried moving the code of Page_Load intoPage_Init, only to get a runtime error. As expected, the view state is null atthat time, so the code can't work. While testing the sample page, I had theTrace option turned on. I can't say how many times I had a trace output beforeme, but I always failed to make sense of the pair of begin/end messagesdescribed as the "ProcessPostData Second Try." It turns out that the ASP.NET runtime processes postedvalues in two steps. The first step occurs immediately after restoring the viewstate, as the documentation clearly states. Then, the Load event fires to thepage and recursively to all child controls. By now, you might think the page ispretty much done and only needs to raise server changes and postback events.Nevertheless, before raising events, the runtime gives a second try to controlsto process postback data. You can consider this feature to be specific ASP.NETsupport for dynamically created controls. Basically, during the first step, theruntime processes the values in the query string or the Request.Formcollection, depending on the HTTP method used to post the form. If no match isfound between one of the posted IDs and controls in the page, the ID/value pairis copied into a temporary collection that will be processed again during thesecond try - guess what happens now? During the first step, the Request.Form collectioncontains a reference to the dynamically created textbox, but no control withthat ID exists at the time. For this reason, the ID of the control is set asidefor later. Between the first and second steps, Page_Load fires, and in theexample, I re-create the dynamic control just in time for the second try tocatch it and process postback data correctly. This is really, really cool! But my enthusiasm for this discovery was short-lived: It'sOK for the posted values, but what about the view state? Who is restoring theview 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'tbeen re-created yet. Loading the view state also has nothing to do withrestoring postback data. Nevertheless, the view state is managed correctly. Asmy mother used to say, "If you don't know something, just ask." So, I asked thetextbox to trace when the view state actually was restored (see Figure 3). namespace AspNetPro{ public class TextBox :System.Web.UI.WebControls.TextBox { public override intMaxLength { get { Page.Trace.Warn("get MaxLength"); returnbase.MaxLength; } set { base.MaxLength = value; Page.Trace.Warn("set maxLength"); } } protected overridevoid LoadViewState(object state) { base.LoadViewState(state); Page.Trace.Warn("Loading state for " + UniqueID); } }} Figure 3. Here is a customTextBox class that traces the view-state restoration. I derived a class from TextBox exclusively to overrideLoadViewState 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 factthat a call was made. This is a debugging technique I sometimes use to stepinto the internals of system classes. The new AspNetPro.TextBox class is used in Figure 2 inlieu of the original TextBox class. Figure 4 shows the results and reveals themystery.
Figure 4. A derived class can help you determine internal behaviors ofsystem classes. The view state of a dynamically created control isrestored within Page_Load automatically, particularly when the control is addedto the parent's Controls collection. In fact, the Controls.Add method alwaysmakes a call to load the view state recursively for controls that are addeddynamically. In ASP.NET, there's only one way to create controls: Usethe "new" operator. The runat=server attribute is simply its declarativecounterpart. In light of this, dynamically created controls are no exception. Iwas pleased to discover, however, that the ASP.NET framework also has effectivesupport 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 controlsin the Page_Load event. The sample code in thisarticle is available for download. Dino Esposito is a trainer and consultant who specializesin ASP.NET, ADO.NET, and XML. The author of Building Web Solutions with ASP.NET and ADO.NET andthe upcoming ProgrammingASP.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 sendany comments about this article to [email protected] include the article title and author.
About the Author
You May Also Like