Scott on Writing

Musings on technical writing...

Tip: When Adding Dynamic Controls, Specify an ID

One area where ASP.NET developers seem to have the most difficulty with is working with dynamic controls.  This difficulty is understandable, as there are a plethora of subtleties in getting everything to work right.  I've actually authored a number of articles on working with dynamic controls:

The other day I was talking to a colleague who was having some problems with dynamic controls.  His site was designed as a single “master” page that had on it navigation controls (skmMenu, to be precise) and a PlaceHolder.  Whenever a skmMenu menu item was clicked, this colleague was wanting to dynamically load in a corresponding user control into the PlaceHolder.

His first attempts at knocking this out was to do the following:

  1. In his “master“ page he had a method called LoadPageControls() that was invoked from the Page_Init event handler.  This LoadPageControls() method looked at a Session variable - if the session variable was set to a user control path, that user control was loaded into the PlaceHolder (using Page.LoadTemplate(path)- see An Extensive Examination of User Controls for more info on dynamically loading user controls).  If, on the other hand, the session variable was not set (such as on the first page load before the user had clicked an item from the skmMenu), a default user control was loaded.
  2. When a skmMenu menu item is clicked the MenuItemClicked event fires.  This colleague then created an event handler for this event, set the Session variable to the menu item's CommandName property and recalled LoadPageControls(), thereby loading in the appropriate user control based on the menu item clicked.  And since this information was saved in the Session, if the dynamically loaded user control caused a postback, for example, upon reloading the “master“ page the appropriate user control would be re-added, thereby remembering the user control to display across postbacks.  (Side note: this information could have also been serialized to view state.  I don't know if the Session option was chosen out of ignorance over using ViewState or if it was chosen because he wanted the user to be able to return to the “master“ page sometime later and have the last loaded menu item for that user brought back up.  One issue with Session, though, is that this “master“ page will not work for those who have cookies disabled in their browser.)

This approach worked well, or so it seemed.  The user could click a skmMenu menu item and its appropriate user control would be loaded up in the PlaceHolder.  If the user control caused a postback - say it contained a Button Web control - even when the postback was invoked, everything worked as expected - the page was reloaded and the appropriate user control was displayed.

There was, however, one problem.  One dynamically loaded user control contained an editable DataGrid.  If a user clicked the Edit button in the DataGrid, nothing happened.  If he clicked it a second time, the DataGrid row became editable.  There was a one-click pause, so to speak.  Setting a breakpoint in the code, my colleague was able to determine that the event handler wasn't being invoked until the second (and all subsequent) clicks.

Anytime you are working with dynamic controls and events seem to go missing, the first thing to do is a View/Source.  View the rendered HTML of the page that's sent to the browser before you do the action that doesn't trigger the server-side event (but should), and compare it to the HTML of the page right before the action does trigger the server-side event.  Specifically, pay attention to the IDs of the Web controls since its the ID is what is passed back during postback to indicate what triggered the postback...

When examining the HTML after the editable DataGrid was loaded the first time, the DataGrid's ID was something like _ctrl1_DataGridID.  After clicking the Edit button (which did not make the row editable), the returned HTML differed in that the DataGrid's ID was now _ctrl0_DataGridID.  Note the change from 1 to 0 in the ID.  Since the ID differed from the first time the DataGrid user control was loaded to all subsequent postbacks, the event wasn't picked up that first time, when the ID was still in flux.

Once we had identified that this was the problem (as it usually is when missing an event once, but not in subsequent tries), the next step was to figure out why this was happening.  The problem could be traced back to the chain of events that unfolded when the skmMenu menu item was clicked.  When the “master” page is visited for the very first time, the LoadPageControls() method fires during the Init event and, since there is no Session variable set, the default user control is loaded.  Now, when the skmMenu menu item to load the editable DataGrid is clicked, the page posts back and the LoadPageControls() method  runs first, and it says, “Hey, I still have no Session variable,” so it loads the default user control.  Then, later in the page lifecycle, the MenuItemClicked event handler runs, which sets the Session variable and then recalls the LoadPageControls() method, which says, “Ah, yes, here is this Session variable, let me load the associated user control.”

The problem is, the PlaceHolder has had two controls added to its Controls collection, hence the reason why we get the _ctrl1 in the DataGrid's ID!  (This happened even though the PlaceHolder's Controls collection was Clear()ed each time the LoadPageControls() method was called...)  When the Edit button was clicked for the first time, the page posted back and the LoadPageControls() method was called from the Init event.  This time it said, “I do have a Session variable set, so let me add the DataGrid user control.“  Note that in this sequence only one control is added to the PlaceHolder instead of the two that were added in the previous page lifecycle.  Hence we get the _ctrl0 in the DataGrid's ID the second (and all subsequent) times.  Furthermore, in this lifecycle, the _ctrl1 ID was what was sent back in the post headers, so when the Page class can't tie the event that caused the postback back to the DataGrid, and that was why the DataGrid's Edit button wasn't firing the DataGrid's EditCommand event on the first click.

The solution?  Simply give a specific, named ID to the user control being dynamically added.  That is, my colleague's code, before this change, looked like (very rough, I'm leaving out the Session variable check):

PlaceHolderID.Controls.Clear()
Dim c as Control = Page.LoadControl(Session(”pathToUC”))
PlaceHolderID.Controls.Add(c)

Everything worked fine and dandy once the code was changed to:

PlaceHolderID.Controls.Clear()
Dim c as Control = Page.LoadControl(Session(”pathToUC”))
c.ID = “someStaticString“
PlaceHolderID.Controls.Add(c)

With that change, the DataGrid's ID was always the same thing, something like someStaticString_DataGridID.  As you can see, working with dynamic controls can introduce all sorts of hard to find and diagnose subtleties.  To be able to debug dynamic control scenarios, the following things are paramount:

  • Patience!  :-)  This is probably true for all debugging but moreso for debugging dynamic controls.
  • A solid, air-tight, profound understanding of the ASP.NET page lifecycle.  You need to know what events fire when, what methods run in response, and what happens when controls are added to the control hierarchy mid-way through the page's lifecycle.  Some articles worth reading for more information on this include:
  • A good understanding of the ASP.NET event model, and the ability to dig through the rendered HTML for a page and spot differences that may be causing problems.

Happy Programming!

posted on Friday, June 03, 2005 12:32 PM

Feedback

# re: Tip: When Adding Dynamic Controls, Specify an ID 6/3/2005 4:44 PM Jeff Handley

Great disection of an interesting problem!

You mention "a plethora of subtleties in getting everything to work right", and you state 3 requirements for debugging dynamic controls, including patience, profound understanding of the ASP.NET page lifecycle and good understanding of the ASP.NET event model. I totally agree with this assessment!

To be honest though, these topics are exactly why I avoid dynamic control creation at virtually any cost. I have found that there are most often very simple alternatives to using dynamic controls. Although the alternatives many times seem cheap or boring, they can also be plugged in very quickly, without having to worry about page lifecycle or the event model.

One example is displaying a dynamic data entry field, where it could be a textbox, checkbox, dropdownlist, etc. Instead of dynamically injecting the appropriate control, you can simply have a placeholder containing each possible control (and related controls). The code then just decides which placeholder to display. Very simple and much less likely to cause strange problems that are difficult to debug.

I am also a firm believer that the design of a single ASPX for an application, with an ASCX control for each "page" is an overly-used approach. I imagine many developers have taken this approach from the IBuySpy design... which may have been appropriate for a portal site, but not necessarily appropriate for typical web applications. The dynamic control loading alone can cause more headaches than the design is worth.

Just my $0.02 :-)

Jeff Handley

# re: Tip: When Adding Dynamic Controls, Specify an ID 6/3/2005 8:03 PM Cheng Yuan Yap Ye

If the user controls that you want to add to a page are known at compile time, then you do not need to add the user controls dynamically. However, if you do not know what user controls to add to a page at compile time, you have to add them dynamically at run time (e.g. user control filenames stored in a configuration file or in a database.)

Whether or not to use a user control does not depend on the type of website you are creating. If you are going to use a set of controls on multiple pages, then place those set of controls in a user control. This way you only need to make a change in the user control and it will reflect on those pages.

# re: Tip: When Adding Dynamic Controls, Specify an ID 6/14/2005 9:37 AM Jason Christopher

So how would we address a situation where we have to put dynamic drop downlists in an editable dynamic datagrid.

So the scenario would be to populate a datagrid dynamically based on the provided dataset.

As an example, lets say there are 2 datasets (with different fields/columns) that can be used to populate that editable dynamic datagrid.

Depending on which dataset is provided there can be drop downlists within the datagrid. Lets say for dataset1 we have 2 drop columns that need to be shown as drop downlists within the datagrid. One of the dropdown is populated based on the 'SelectedIndexChange' event of the first dropdown.

So we have a situation where we have dynamic dropdown lists within editable dynamic datagrids.

# re: Tip: When Adding Dynamic Controls, Specify an ID 6/23/2005 2:50 AM Miki Watts

I'm working on a project with the exact situation, and ran into the same problems as the one that you described. My solution though was a bit different.

I turned off viewstate on the main placeholder, and essentially created a small state machine in the main page, which would determine the required control to load according to postback status, session variables and the Request.Form variables EVENTTARGET and EVENTARGUMENT. Also, in sub controls, I would do Server.Transfer to the main page, after handling a postback event.

It wasn't very elegant, but it gave me the ability to handle postbacks easily and pretty much control exactly what will happen, regardless of postback and what event took place and the bonus is that i derive all of that from a config file, so i can easily extend the user interface with just the config file.

# re: Tip: When Adding Dynamic Controls, Specify an ID 7/11/2005 12:20 PM Martin Sutherland

I was running up against exactly the same problem today, and the .ID advice sorted it out perfectly. Many thanks!

# re: Tip: When Adding Dynamic Controls, Specify an ID 7/22/2005 11:48 AM Carl Reid

Thanks Scott, got me out of yet another frustrating problem . Again! Keep up the good work!!

# re: Tip: When Adding Dynamic Controls, Specify an ID 8/16/2005 8:45 AM Gary Woodfine

Scott,
When Dynamically adding WebUsercontrols is there anything one has to bear in mind when one wants to Access a Control from the Usercontrol with Javascript?
i.e

ControlID being the Ctl.ClientID being passed in
function browseControlAction(ProjID,ControlID)
{
//alert('Hello Gary!');
strPrompt = 'Select a Previous control action..';
strOptions = 'dialogWidth:25;dialogHeight:30;status:no;resizable:yes';
selectedAction = window.showModalDialog('browseControlActions.aspx?ProjID=' + ProjID + '&Control=' + ControlID, strPrompt, strOptions);
var sControl = ' + ControlID + ';
if (selectedAction != null)
{

alert(ControlID.toString());
alert(selectedAction);
var sTest = ControlID + '_txtControlAction'; //Appends the ControlID and the Texbox Control I would like to access
alert(sTest);
var sTest2 = 'txtSummary';
alert(sTest2);
document.getElementById('txtCurrentControls').innerText=selectedAction; //this works I explicity state the Textbox control on the Page itself not the USer control

document.getElementById(sTest2).innerText=selectedAction; //This Works I explicity state the Textbox control on the Page itself not the USer control

document.getElementById(sTest).innerText=selectedAction; //I get a Null Exception
//__doPostBack('','');

}

# re: Tip: When Adding Dynamic Controls, Specify an ID 8/16/2005 9:35 AM Gary Woodfine

Scott,

When Dynmically adding Usercontrols, I have added them using a Panle control, then adding the Controls in Page_init, when I view the Source the Panel is Empty, no control exist?, but when I view the Page in the Browser the controls are there and I can interact with them. Where do these control reside if they are not in the HTML?

# re: Tip: When Adding Dynamic Controls, Specify an ID 12/20/2005 3:15 AM Thomas Koch

You're the man Scott. :-)

The ID trick solved my problem.

# re: Tip: When Adding Dynamic Controls, Specify an ID 3/20/2006 8:46 PM Steve

Scott, I've been looking for this solution for a long time! THANK YOU!!!

# re: Tip: When Adding Dynamic Controls, Specify an ID 3/24/2006 2:49 PM Ashish

Thanks Scott. I've been pulling my hair over loading dynamic user controls along with Atlas and this finally did it for me.

# re: Tip: When Adding Dynamic Controls, Specify an ID 7/6/2006 3:56 AM andy

Scott... if you could help me with this one.... I am going nuts.

Similar problem:

I have made a test page where I dynamically create two listboxes and two buttons (in my real project this is more complex but since I am trying to get this thing to work I brought the problem back to a minimum). The listboxes are filled with data from a database; listbox 1: all items available, listbox 2: items selected by somebody.



In my page load

Create dynamic controls

Button1 click:
add item to database on somebody’s id


Button2 click:
delete item from database on somebody’s id

What actually happens is that when I click a button the item is added or deleted (I checked this by test with response.redirect or just close the browser after one click an open the page again) but it doesn’t show in my listboxes until I click the button for a second time. I find this very confusing since I create the dynamic stuff in pageload and would expect to reload (why would I place it in the onpage load otherwise…)

I looked at the id's of the listboxes and buttons the buttons are on any postback
ctl00$ContentPlaceHolder1$ctl00 and 01,
the listboxes I give specific names so they don't change either.

I read all articles on viewstate I could find but I guess I'm too stupid or something...




# Dynamically Loading Controls in ASP.NET Is A Pain 7/19/2006 7:44 PM K. Scott Allen

Q: I'm using LoadControl to dynamically place objects into my web form. I've struggled with missing events...

# re: Tip: When Adding Dynamic Controls, Specify an ID 2/5/2007 12:16 PM Mark Phalen

Very good post. I had the same problem that you described and this fixed it for me. Thanks for posting your problem and solution.
Thanks again!

Title:  
Name:  
Url:
Protected by Clearscreen.SharpHIPEnter the code you see:
Comments   

Add To Your Reader

My Links

Archives

Post Categories

 

I am a Microsoft MVP for ASP.NET.
I am an ASPInsider.
<May 2008>
SMTWTFS
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

Comment Stats

DayTotal% of Total
Sunday 1866.8%
Monday 37913.9%
Tuesday 45316.7%
Wednesday 50418.5%
Thursday 53519.7%
Friday 49418.2%
Saturday 1666.1%
Total 2717100.0%

Hour1Total% of Total
12:00 AM 652.4%
1:00 AM 682.5%
2:00 AM 622.3%
3:00 AM 742.7%
4:00 AM 572.1%
5:00 AM 1033.8%
6:00 AM 1084.0%
7:00 AM 1585.8%
8:00 AM 1716.3%
9:00 AM 1475.4%
10:00 AM 1716.3%
11:00 AM 1816.7%
12:00 PM 1886.9%
1:00 PM 1696.2%
2:00 PM 1605.9%
3:00 PM 1324.9%
4:00 PM 1073.9%
5:00 PM 923.4%
6:00 PM 913.3%
7:00 PM 963.5%
8:00 PM 833.1%
9:00 PM 782.9%
10:00 PM 792.9%
11:00 PM 772.8%
Total 2717100.0%

Comments by Blog Entry Date/Time

Day Entry MadeAvg.Total
Sunday 5.54144
Monday 5.22339
Tuesday 4.28419
Wednesday 7.67637
Thursday 6.90607
Friday 5.48411
Saturday 5.33160
Total 5.842717

Hour1 Entry MadeAvg.Total
12:00 AM 5.0035
1:00 AM 1.002
5:00 AM 0.000
7:00 AM 7.0035
8:00 AM 5.35107
9:00 AM 6.32278
10:00 AM 6.47246
11:00 AM 4.41181
12:00 PM 6.88330
1:00 PM 3.00111
2:00 PM 5.41222
3:00 PM 8.64285
4:00 PM 4.0589
5:00 PM 5.92154
6:00 PM 4.52113
7:00 PM 9.67174
8:00 PM 9.80147
9:00 PM 5.05111
10:00 PM 5.4265
11:00 PM 4.5732
Total 5.842717

Learn More About Comment Stats
1 - All times GMT -8...


Blog Stats

Favorite Web Sites

My Books

My MSDN Articles