Tuesday, September 04, 2007 4:50 PM InfinitiesLoop

TRULY Understanding Dynamic Controls (Part 4)

Part 1: Dynamic vs. Static
Part 2: Creating Dynamic Controls
Part 3: Adding Dynamic Controls to the Control Tree
Part 4: Because you don't know what to render at design time

UPDATE: Click here to download the Sample Code referenced in this article.

I started part 1 of this series so long ago. My original plan was to have all 4 parts done over four weeks. It's been a lot longer than that! Sorry folks!

The new plan is that four parts is not enough! I realized while writing this part that there's just too much to cover!

There are three main reasons I can think of why you would want to create controls dynamically instead of declaratively.

  1. Because the types and/or number of controls to be rendered is not known at design time. The form may be driven by a database or through configuration, or it is dynamically determined based on other data the user has provided.
  2. You know what controls may be rendered but there are a large number of possibilities and/or the controls are expensive to load, so you don't want to load them all.
  3. Because you are creating a custom server control and you don't have a choice.

In this part, we will cover #1 and #2. Part 5, hopefully the last part, will cover #3.

Example: You don't know "what" or "how many" controls should be rendered at design time

First and foremost -- ask yourself whether the problem is that you don't know "what" controls should be rendered, or if it's just "how many" controls should be rendered. If it's a question of how many, you can still solve the problem declaratively thanks to Templating.

This is a real scenario that someone emailed me about. They were attempting to solve the problem with dynamic controls. They were having various problems with their implementation and asked for my help. And of course, it turned out to be much simpler with templating.

The scenario is as follows:

You want users to be able to upload files to your site. They do this from a file manager page, where they can see a listing of the files that already exist. Next to each file is a button to delete the file. At the bottom of the list is a file upload control they can use to upload a new file. Any time a file is deleted or uploaded, the listing is updated.

To display the files, we will add rows dynamically to a table that is declared on the form. When a file is deleted we'll give some feedback via a label.

<asp:Label ID="lblStatus" runat="server" EnableViewState="false" />
<table id="tblFiles" runat="server" cellpadding="4" cellspacing="4" border="1"></table>

Here is the implementation I was given. In red, because it has problems!

public partial class _Default : Page {
    protected override void OnLoad(EventArgs e) {
        string[] files = Directory.GetFiles(Server.MapPath("~/uploaded"));
 
        foreach (string path in files) {
            // strips directory from path
            string fileName = Path.GetFileName(path);
 
            HtmlTableRow row = new HtmlTableRow();
            // file name cell
            HtmlTableCell cell = new HtmlTableCell();
            cell.InnerText = fileName;
            row.Cells.Add(cell);
 
            // delete cell
            LinkButton cmdDelete = new LinkButton();
            cmdDelete.Text = "delete";
            cmdDelete.CommandArgument = fileName;
            cmdDelete.Command += new CommandEventHandler(cmdDelete_Command);
            cell = new HtmlTableCell();
            cell.Controls.Add(cmdDelete);
            row.Cells.Add(cell);
 
            // add row to table
            this.tblFiles.Rows.Add(row);
        }
        base.OnLoad(e);
    }
 
    private void cmdDelete_Command(object sender, CommandEventArgs e) {
        // command argument contains the file name to delete
        string fileName = Path.GetFileName((string)e.CommandArgument);
        string path = Path.Combine(Server.MapPath("~/uploaded"), fileName);
 
        File.Delete(path);
        this.lblStatus.Text = "Deleted " + fileName;
 
        // now remove the table row for the file
        LinkButton cmdDelete = (LinkButton)sender;
        // link button's parent = cell, parent.parent = row
        HtmlTableRow row = (HtmlTableRow)cmdDelete.Parent.Parent;
        this.tblFiles.Rows.Remove(row);
    }
}

It seems pretty straight forward. The first cell shows the file name, the second shows a link we can click on to delete it. When deleting a file, we remove it from the file system and then remove the row from the table. And of course, we let the user know the file was deleted successfully. Let's see how it runs...

File Manager

Cool -- let's delete "InfinitiesLoop.gif", who wants that thing anyway?

Deleted InfinitiesLoop.jpg

So far so good! Now, let's delete "TheFamily.jpg". I was blinking during that picture, and I don't appreciate the bunny ears over my head, sister. Bah...

Deleted TheFamily.jpg

WHAT?! I swear I clicked delete on TheFamily.jpg, but it deleted my TODO list! Nooooo.....! Oh well I guess I can just go home now. Dare I try to delete something else? Who knows what it's going to do next...

So, what went wrong?

When the page first renders, there are 4 rows with 4 link buttons. We never specified an ID for those link buttons when we created them. That means they get automatically generated IDs. The rows and cells do, too, but only the link button actually requires an ID to be rendered, because it must cause a postback via javascript (since links don't naturally do postbacks). If you view the html source, you will see the link button's href looks like "javascript:__doPostBack('ctl01', '')". In this case, ctl01 is the LinkButton ID.

So let's say the link buttons have IDs ctl01, ctl02, ctl03, and ctl04 (the actual IDs are different because of the controls between them, but you get the idea). We click on ctl02 to delete "InfinitiesLoop.jpg". When the postback is processed, first we recreate the table with all 4 rows again. The link buttons will still have the same IDs they did last time. ctl02 raises its command event, we delete the file, and then remove the row from the table. But the link buttons have already determined what their IDs are going to be -- the rendered table will now have link buttons with these IDs: ctl01, ctl03, ctl04. Hmmm. ctl03 is the link button the represents TheFamily.jpg. It will now appear 2nd in the table.

Now we click to delete TheFamily.jpg (ctl03). First the table gets recreated -- only, this time there is no InfinitiesLoop.jpg in the Upload directory. So we only create 3 link buttons this time -- ctl01, ctl02, ctl03. At this stage, ctl03 is the link button that corresponds to TODOList.txt. See the problem yet?

ASP.NET knows who raised the postback event via its ID. But we've pulled the rug out from under it -- the controls on this request have different IDs than they did on the previous request. There happens to be a control with the ID that was posted, but it isn't the one we intended it to be. ASP.NET finds ctrl03, and we delete TODOList.txt. Oops.

This problem can manifest itself in many different ways. If we had textboxes in this table, you might find that they lose their values after certain postbacks, or that their values seem to shift in position.

What to do about it

It's important that the structure of the control tree be the same after a postback as it was before the postback. We actually were not really violating that rule in this case -- we rendered out 3 rows when InfinitiesLoop.jpg was deleted, and we created 3 rows after the postback. But we cheated -- we created 4 rows and removed one, and then on the next request we just created 3 right off the bat. One way we could solve this is by giving each LinkButton a specific ID that will never change. In this case the simplest way would be to make it's ID equal to the file name. There's no way you can confuse one link button with another then. But it's far from ideal to have to stuff the file name into the control ID. The name might be really long. And if we were accessing multiple directories, we could have duplicate file names, so now we'd have to include path information, too. There may be other controls in each row, such as checkboxes that show the file's attributes like whether it is marked as ReadOnly. We'd have to apply this trick to those controls, too. Yuck. Yuck. Yuck.

Really what you want is to be able to "reset" the ID scheme of all the controls in the table. When we remove a row, all the controls after it should 'shift up' with their IDs, so that they will render with the same ID they will have on the next postback. So, what if when we deleted a row, we completely rebuilt the table from the start? Just clear out all the rows and then rebuild it like we originally did...

protected override void OnLoad(EventArgs e) {
    BuildTable();
    base.OnLoad(e);
}
 
private void cmdDelete_Command(object sender, CommandEventArgs e) {
    // command argument contains the file name to delete
    string fileName = Path.GetFileName((string)e.CommandArgument);
    string path = Path.Combine(Server.MapPath("~/uploaded"), fileName);
 
    File.Delete(path);
    this.lblStatus.Text = "Deleted " + fileName;
 
    // removed a row, throw away the table and rebuild it
    this.tblFiles.Rows.Clear();
    BuildTable();
}
 
private void BuildTable() {
    string[] files = Directory.GetFiles(Server.MapPath("~/uploaded"));
 
    foreach (string path in files) {
        // strips directory from path
        string fileName = Path.GetFileName(path);
 
        HtmlTableRow row = new HtmlTableRow();
        // file name cell
        HtmlTableCell cell = new HtmlTableCell();
        cell.InnerText = fileName;
        row.Cells.Add(cell);
 
        // delete cell
        LinkButton cmdDelete = new LinkButton();
        cmdDelete.Text = "delete";
        cmdDelete.CommandArgument = fileName;
        cmdDelete.Command += new CommandEventHandler(cmdDelete_Command);
        cell = new HtmlTableCell();
        cell.Controls.Add(cmdDelete);
        row.Cells.Add(cell);
 
        // add row to table
        this.tblFiles.Rows.Add(row);
    }
}
 

"A" for effort (affort?), but there's still a problem. Now when we click to delete TheFamily.jpg after deleting InfinitiesLoop.jpg, this is what we get.

Delete TheFamily.jpg again

Nothing happens except the postback. If you examine the control IDs this time, you will see that instead of resetting the IDs by clearing out the table and rebuilding it, the rows simply continued the ID sequence. That's because Controls.Clear() does not reset the automatic ID counter unless the control implements INamingContainer. HtmlTable does not implement INamingContainer.


Aside: What is INamingContainer?

INamingContainer is a marker interface, meaning it has no methods to implement. A control "implements" this interface to let the framework know that it plans on giving it's child controls really specific IDs. It's important to the framework, because if a two instances of the same control are on the same page, and the control gives its child controls some specific ID, there'd end up being multiple controls with the same ID on the page, which is going to cause problems. So when a Control is a naming container, the UniqueID for all controls within it will have the parent's ID as a prefix. This scopes it to that control. So while a child control might have ID "foo", its UniqueID will be "parentID$foo" (where parentID = the ID of the parent). Now even if this control exists twice on the page, everyone will still have a UniqueID.

INamingContainer also has the property that any controls within it that do not have a specific ID will have its ID automatically determined based on a counter that is scoped to it. So if there were two naming containers, foo and bar, they might have child controls with UniqueIDs foo$ctl01 and bar$ctl01. Each naming container gets its own little counter.

Note that the ID "foo$ctl01" does not necessarily imply that ctl01 is a direct child control of foo! All it means is that foo is ctl01's naming container (control.NamingContainer). It's parent might be another control which is not a naming container.


So we can solve this problem once and for all by using a custom HtmlTable control that implements INamingContainer. I won't bother showing that... because there's an even better solution.

Remember this scenario is that you don't know "how many" controls there should be at design time. We do, however, know "what" the controls are. You can do it dynamically, if you manage the control tree correctly. But when you know what the controls will be ahead of time, Templating can step in and take care of all of this for you automatically!

<asp:Label ID="lblStatus" runat="server" EnableViewState="false" />
 
<asp:Repeater ID="rptFiles" runat="server" EnableViewState="false">
    <HeaderTemplate>
        <table cellpadding="4" cellspacing="4" border="1">
    </HeaderTemplate>
    <ItemTemplate>
        <tr>
            <td><%# Path.GetFileName((string)Container.DataItem) %></td>
            <td>
                <asp:LinkButton runat="server" Text="delete"
                    CommandArgument="<%# Path.GetFileName((string)Container.DataItem) %>" />
            </td>
        </tr>
    </ItemTemplate>
    <FooterTemplate>
        </table>
    </FooterTemplate>
</asp:Repeater>

Using a repeater, we declare what each row will look like. This is what I mean by "what" vs "how many". We know what, so we can express "what" via the repeater ItemTemplate. The "how many" will come from databinding the repeater, which will repeat the template once for each item we bind to it.

protected override void OnLoad(EventArgs e) {
    this.rptFiles.ItemCommand += new RepeaterCommandEventHandler(rptFiles_ItemCommand);
    BindRepeater();
    base.OnLoad(e);
}
 
private void rptFiles_ItemCommand(object source, RepeaterCommandEventArgs e) {
    // command argument contains the file name to delete
    string fileName = Path.GetFileName((string)e.CommandArgument);
    string path = Path.Combine(Server.MapPath("~/uploaded"), fileName);
 
    File.Delete(path);
    this.lblStatus.Text = "Deleted " + fileName;
 
    // removed a row, rebind the repeater
    BindRepeater();
}
 
private void BindRepeater() {
    string[] files = Directory.GetFiles(Server.MapPath("~/uploaded"));
    this.rptFiles.DataSource = files;
    this.rptFiles.DataBind();
}

This is what Databound controls are good at. Let them do their job. Doing things dynamically when you don't really need to only complicates things. This design is so much better in so many ways. For one, notice we really have no UI related code in the code-behind (except for the 'status' message). ASP.NET's code-behind model is meant to separate code and UI. Also, notice that there's considerably less code! Why? Because the repeater takes care of the following things for us: (1) it implements the foreach loop. We give it something to enumerate over and call DataBind, it does the rest. (2) it implements the creation of the controls for us. Controls are still being created dynamically at runtime, but we've handed that responsibility to the repeater by describing for it what an item should look like. (3) it implements INamingContainer. Calling DataBind on a databound control throws away its contents and rebuilds it from scratch, just like we had to do. All we need to worry about is maintaining the data and letting the repeater know when the data has changed.

Example: You don't know "what" controls should be rendered at design time, or you want to avoid loading controls you don't need because of performance or because there are too many possibilities.

If you don't know what controls will be rendered in the first place, you have to use dynamic controls, right? Well, even then, it depends. Maybe you don't know what control you will need, but the possibilities are limited. In that scenario, you can avoid dynamic control complexities by simply declaring every possible control with Visible=false, then switch the one you want on by making it visible. Like the repeater example above, this takes the burden of being responsible for the control tree out of your hands. It will also work well if it's possible for the control that is loaded to change during a postback due to a change in state. Since you're loading them all anyway, it doesn't matter.

What about performance?

I've suggested this solution a number of times to readers who sent me their problems. A lot of the time, they were doing it dynamically instead of using the visibility trick because they considered it better for performance. True, loading two controls is more work than loading one, especially when you can know one isn't needed early on. But you're splitting hairs, my friend! Controls, whether they be built in controls, custom server controls, or user controls, are efficient. It doesn't take many resources to instantiate one. It doesn't take many resources to add it to the control tree.

The only time I'd be worried about the performance of a control on the page that doesn't need to exist is if the code in the control is going to do something it doesn't need to do, or if it has a lot of ViewState associated with it. Most of the time, that's not the case. If all the control does is render some html and maybe process some postback data, it isn't worth the effort. If the problem is the amount of viewstate it contains... well, then turn it off, and just enable it for the one control that you do need. If the problem is the operations the control is going to do, like query a database, then that control isn't coded correctly. Control's typically shouldn't go off and do things on their own. They should be told when to do things by the page it lives on. Controls should almost never call databind on themselves, unless they are designed for a really specific purpose where you just want the control to do its own thing. Refactor the control so it has to be told when to do that expensive operation, such as by putting the logic in the DataBind method.

Ok. Let's move on. Say you can't just load them all ahead of time. Maybe the control to load depends on a setting in your database. Maybe there are hundreds of possibilities based on input. The next question to ask is whether the control to load is dependent on state information on the page.

For example, if your database holds the path to a user control which acts as the footer to your layout, that's probably pretty static. It probably doesn't depend on any state information. So all you have to do is load the control and add it to the tree every request. No issues. Just make sure you do it as soon as you can in the page life cycle. OnInit preferably.

If the control(s) to load depend on state data, then that's another story. In this example, we have two radio buttons and a place holder. The dynamic control is loaded into the placeholder, but which control we load depends on which radio button is selected. If you wish, you can also imagine that the control we load is dependent on a setting in web.config. But for this example we'll just use two hard controls.

First we'll define two user controls, UserControl1.ascx and UserControl2.ascx.

<%@ Control Language="C#" %>
<script runat="server">
    private void ButtonClick(object sender, EventArgs args) {
        this.lbl.Text = "Clicked UserControl1";
    }
</script>
UserControl1<hr />
<asp:Label ID="lbl" runat="server" /><br />
<asp:Button ID="cmd" runat="server" Text="Click" OnClick="ButtonClick" />

UserControl2 looks the same except has "UserControl2" instead of "UserControl1". Each has a label and a button. When the button in control 1 is pressed, it updates the label in control 1. When the button in control 2 is pressed, it updates the label in control 2. Keep in mind that each control has its very own button and label. There is no label on the form:

<%@ Page Language="C#" %>
<script runat="server">
    protected override void OnLoad(EventArgs e) {
 
        if (this.opt1.Checked) {
            ph.Controls.Add(LoadControl("~/UserControl1.ascx"));
        }
        else if (this.opt2.Checked) {
            ph.Controls.Add(LoadControl("~/UserControl2.ascx"));
        }
 
        base.OnLoad(e);
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Pick-a-control</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:RadioButton ID="opt1" runat="server" Text="Number 1"
            AutoPostBack="true" GroupName="g" Checked="true" />
        <asp:RadioButton ID="opt2" runat="server" Text="Number 2"
            AutoPostBack="true" GroupName="g" />
        <hr />
        <asp:PlaceHolder ID="ph" runat="server" />
    </div>
    </form>
</body>
</html>

The devil is in the details. Let's load it up. UserControl1 will load by default, so lets just go ahead and make sure it's working by clicking on the button:

Clicked on UserControl1

So far so good. It says "Clicked UserControl!" like we expected. It loaded, we posted back, it reloaded, and it successfully processed the click as if it were a declared control. Beautiful. Let's swap over to UserControl2 now... but we won't click on its button yet...

Switched to UserControl2

What? That's weird. According to this, I've loaded UserControl2, but it's label has the data I put into UserControl1's label! Voodoo!

That's nothing. Now let's have some fun. Change the label control in UserControl2 to a TextBox. Don't even give it the same ID.

<%@ Control Language="C#" %>
<script runat="server">
    private void ButtonClick(object sender, EventArgs args) {
        this.txt1.Text = "Clicked UserControl2";
    }
</script>
UserControl2<hr />
<asp:TextBox ID="txt1" runat="server" /><br />
<asp:Button ID="cmd" runat="server" Text="Click" OnClick="ButtonClick" />

Now what happens when we switch from control 1 to control 2?

Voodoo!!

Ohhh yah... we're hacking now. We've successfully loaded the ViewState for the Label in UserControl1 into the TextBox in UserControl2. They both have a "Text" property, which both happen to use "Text" as the ViewState key to remember the value in. *High five*

I mean this to drive home an important point. The control tree into which viewstate is loaded must basically match the control tree which was used to save that viewstate. Coming from more procedural web frameworks (like ASP), you might tend to think of the life of a control to be over once it renders itself. But really, I like to think of it as if a control's lifecycle straddles the request/response boundary. It does indeed get re-instantiated upon every request, but because ASP.NET manages state data for us, the control created on a postback is intimately connected with its "predecessor", for lack of a better word. This more "logical" lifecycle ends after the control has loaded its ViewState and its postback data from its previous life, if any. After that, but before it begins its next life in PreRender, you can make persistent changes to the control tree with no worries.

So here is the solution to the above problem. Every scenario is different, so this isn't necessarily a real general pattern you should follow to the tee, but it shows how we do things the right way for this scenario. Hopefully you can adapt the solution to your specific needs.

<%@ Page Language="C#" %>
<script runat="server">
    protected override void OnLoad(EventArgs e) {
        if (!Page.IsPostBack) {
            // no viewstate on initial request, load the default control
            ViewState["state"] = opt1.Checked ? 1 : 2;
            LoadUserControl();
        }
        base.OnLoad(e);
    }
 
    protected override void LoadViewState(object savedState) {
        base.LoadViewState(savedState);
        // viewstate loaded, now we know which control to show.
        LoadUserControl();
    }
 
    private void CheckChanged(object sender, EventArgs args) {
        // state has changed. Remove the loaded control and load the new one.
        ViewState["state"] = opt1.Checked ? 1 : 2;
        ph.Controls.Clear();
        LoadUserControl();
    }
 
    private void LoadUserControl() {
        Control c;
        int state = (int)ViewState["state"];
        if (state == 1) {
            c = LoadControl("~/UserControl1.ascx");
        }
        else {
            c = LoadControl("~/UserControl2.ascx");
        }
        // id assigned to avoid shifting IDs when control changed on a postback
        c.ID = "foo";
        ph.Controls.Add(c);
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Pick-a-control</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:RadioButton ID="opt1" runat="server" Text="Number 1"
            AutoPostBack="true" GroupName="g" Checked="true"
            OnCheckedChanged="CheckChanged"  />
        <asp:RadioButton ID="opt2" runat="server" Text="Number 2"
            AutoPostBack="true" GroupName="g"
            OnCheckedChanged="CheckChanged" />
        <hr />
        <asp:PlaceHolder ID="ph" runat="server" />
    </div>
    </form>
</body>
</html>

The idea is to load the control that existed on the previous request by utilizing a ViewState field to remember which control was active. We override LoadViewState, then immediately after calling base.LoadViewState, we can look for the ViewState value to tell us which control existed previously. On the initial request, there is no ViewState and therefore no call to LoadViewState, so we detect this in OnLoad and make sure the default control is loaded at first. Then, we listen for the CheckChanged event on the radio buttons to tell us when the control to be loaded has changed (note: I actually really dislike the CheckChanged event, but that's a different discussion. It works well for this simple scenario).

At the time the CheckChanged event fires, we will have already loaded a user control -- whichever one was active previously. So we have to remove it before adding the new one. That is why we call Controls.Clear() on the placeholder. And finally, we assign the control a specific ID so there's no way we can run into the ID problem mentioned earlier, which would happen when removing the old control and adding the new one in response to the CheckChanged event. There's no way we could accidentally post data from one control into another as you switch from one control to another, because we're loading the correct control prior to the loading of post data. Post data is loaded right after LoadViewState. That is why we don't do the logic from OnLoad -- the user control would miss that phase. Actually, ASP.NET loads post data in two phases, one before OnLoad / after LoadPostData, and one right after OnLoad. The purpose of the 2nd pass after OnLoad is to load postdata for any controls that may have been dynamically added (or created through databinding) during OnLoad. That would actually work just fine for our scenario, but there are consequences to this late loading that are best avoided if possible. Normally, you can rely on post data to have been completely loaded from the OnLoad phase. But for late-comers, that isn't the case. It's best to provide consistent behavior if you can, so LoadViewState is where it's at!

Actually, this very closely emulates what data bound controls do. If you were to examine the code for the Repeater, for example, you would find that from its CreateChildControls method it examines a ViewState field. If it exists, it calls its CreateControlHiearchy method, which rebuilds its control tree based on ViewState. When it is DataBound, it clears the control collection and rebuilds it again, calling the same CreateControlHiearchy method.

THAT'S ALL FOLKS. In the next part we'll cover custom server controls and some of the things to watch out for there.

UPDATE: Click here to download the Sample Code referenced in this article.

One more thing...

Many of you apparently were so anxious to read part 4 of the series, you cleverly deduced that the url to the article must be the same as Part 3, only with a "4". You URL HAXXOR, you!!!!111... You see, I've been working on a draft of this article for a long time now (which as it turns out has been completely redone), and I unknowingly had the article saved in a state that would allow you to access it if you happened to know the address to it!

In all, by the time I realized what was happening, this article received 121 hits to it even before it was published! All you hackers got to read my embarrassingly terrible draft. I thwarted you by renaming the article temporarily. I added "abc" to the end. I was waiting for someone to guess that, too. If you did, there would have been a surprise in it for you. But no winners. Oh well....

Until next time.... part 5 will come much sooner than part 4 did, I promise.

Filed under: , ,

Comments

# ASP.NET Dynamic controls

Sunday, September 02, 2007 6:31 AM by primoz.tech.blog

ASP.NET Dynamic controls

# re: TRULY Understanding Dynamic Controls (Part 4)

Sunday, September 02, 2007 8:42 AM by AndrewSeven

I like the fact that you often come back to alternatives to dynamic controls.

I've answered a lot of questiong on aspmessageboard.com about dynamic controls and most of the time the best answer is to use one of the alternatives.

In a recent project, I needed tables where you could add and remove rows so I extended the gridview so I could extract its data, remove a row from the data, then rebind.

# re: TRULY Understanding Dynamic Controls (Part 4)

Sunday, September 02, 2007 12:22 PM by Joseph A. Habdank

well good article - It is a great pity that you did not publish it earlier (like a month ago). Due to the problem which you described under example 2 I had to disable the ViewState - and I have to go with it now. If I had known that solution before life could have been easier.

Btw. to all very abiscious ASP coders:

forget about using dynamic controls unless there is NO OTHER WAY (and I mean it). ASP dynamic controls show a *lot* of problems if you do not know very well the ASP (inner structure, and very good understaing of databinding process, event flows etc).

e.g. - even if you have to switch between 5 GridViews with their SQLDataSources etc - forget about it. Just have them all statically and change a Visible property accordingly to your needs. I did not do it that way, AND I REGRET IT BADLY now.

# re: TRULY Understanding Dynamic Controls (Part 4)

Sunday, September 02, 2007 10:35 PM by Md Sarker

I am recently having a similar problem with a datalist. No events from any control inside the EditItemTemplate getting fired. I have EmableViewState=false for page. Any solution? Please email info@webcosmo.com

# re: TRULY Understanding Dynamic Controls (Part 4)

Monday, September 03, 2007 4:31 AM by liggett78

I'm really having hard time trying to read all these dark-blue, grey, dark-green etc. words on the black background. You should consider switching at least the background color.

# re: TRULY Understanding Dynamic Controls (Part 4)

Tuesday, September 04, 2007 4:48 PM by Dani

Hi

I've read a lot of your articles and they have been very helpful!

So, here is my issue that i have not been able to resolve. I have custom controls (.ascx) being added dynamically to statically placed placeholders on the page. I need to add the controls dynamically beause their location, which placeholder they belong in, is driven by xml files configured by the some administrator.

I've followed the implemention you have with loading the dynamic controls using the Init() and LoadViewstate. But, for some reason, when a postback occurs, i lose all entered data for Textbox, state of Checkbox, state of Combobox and basically every control who's state can change. If i make the Textbox Readonly=True, then the values remain after each postback. I see you had to update ViewState for the checkboxes in your example above to maintain the state the user selected. I can't do that since my controls are outside the scope of my page. i've tried doing FindControl but with no success.  So, if you have any suggestions for preserving the state of these controls (textbox, checkbox, combo) i would really appreciate it!

Thanks again for your articles!

# re: TRULY Understanding Dynamic Controls (Part 4)

Tuesday, September 04, 2007 5:04 PM by InfinitiesLoop

Dani -- I would have to see some code, or at least get some more details. Exactly when and where are you loading the control? And are you giving it an ID?

# re: TRULY Understanding Dynamic Controls (Part 4)

Tuesday, September 04, 2007 6:32 PM by Dani

Hi,

Thanks for the quick response.

I have a LoadCustomControls() function tha is being called by before base.OnInit(e) and if it's not a postback and also after base.LoadViewState(savedState). Every control has an ID and i am also clearing call controls from each placeholder by calling the PlaceHolder.Controls.Clear().

Also, inside each control i am loading the data on the condition that is not a postback. So if(!IsPostback){LoadControlData();}

I can email you some code examples of exactly what i'm doing if that helps.

Thanks again.

# re: TRULY Understanding Dynamic Controls (Part 4)

Tuesday, September 04, 2007 7:40 PM by InfinitiesLoop

Why do you only load in OnInit when it is not a postback? Does the control that you are loading depend on any state data? You said it depends on a configuration file, so unless there's more to it, the answer is no. And if the answer is no, you should just load it from OnInit every request and forget about LoadViewState.

# re: TRULY Understanding Dynamic Controls (Part 4)

Wednesday, September 05, 2007 10:49 AM by linus

hi, i have a similar problem as in example 2.

however, i have textbox in the ascx for the user to enter. there is a 'save' button in the aspx page. the save button is suppose to capture the textbox entry and save it to the database. how do i capture the entry by the user? i know that you need to cast it and access the expose the property set in the ascx page. but, i want to know how would u implement it.

cheers and thanks

# re: TRULY Understanding Dynamic Controls (Part 4)

Wednesday, September 05, 2007 11:51 AM by InfinitiesLoop

linus -- so if I understand correctly, you just need to get the value in the textbox from the page that is hosting the user control? You could use FindControl to get to the textbox if you know it's ID. But I wouldn't do it that way. I would do as you say -- create a property on the user control that exposes the textbox value. Then on the aspx, cast the user control to the type of the code behind (depending on the project model you are using, you may need to add a @reference directive) and access the property. That method allows the communication you need without hard coding the control ID, or even hard coding the fact that it's a textbox. You are free to change those details in the ascx without breaking the page.

# re: TRULY Understanding Dynamic Controls (Part 4)

Wednesday, September 05, 2007 9:22 PM by linus

ok thanks for the guidance... my next question would be...

if my dynamic ascx is only to be loaded at runtime or should i say on postback, do i have to do the creation of the dynamic content in the init event as adviced in this article, aspnet.4guysfromrolla.com/.../092904-1.aspx. ?

i know that i can't do the creation on the button event as it would have reset the textbox entries in the ascx when i click the 'save' button. hmm what should i do?

cheers and thanks

# re: TRULY Understanding Dynamic Controls (Part 4)

Thursday, September 06, 2007 10:37 AM by CurlyFro

what if you had Labels and label update Buttons in UserControl1 & 2.  would the events get fired and correctly update their associated label?

# re: TRULY Understanding Dynamic Controls (Part 4)

Thursday, September 06, 2007 11:15 AM by InfinitiesLoop

CurlyFro -- The controls will behave just as they normally would.

# re: TRULY Understanding Dynamic Controls (Part 4)

Tuesday, September 11, 2007 8:07 AM by charlotte

Great articles, keep up the good work :)

I just started building a solution with dynamic controls, but stopped immediately after reading your part 1-4 above.

Though, after reading, I am all confused about what to do - I am new to .NET so that might be why I cannot figure it out myself.

I have to create a - dynamic!? - form builder with anywhere from 0 to N groups, each group with 1 to N checkboxes or 2 to N radiobuttons inside. And each grouped list of either checkboxes or radiobuttons can be displayed in 1-5 columns. The number of groups, checkboxes, radiobuttons, and columns vary from form to form. Though the form is not dynamically in the sense, that user input can change it. Whenever the form has been build (by web editors), that's what it looks like.

If not creating dynamic controls to build the groups of checkboxes/radiobuttons, what could I do instead?

# re: TRULY Understanding Dynamic Controls (Part 4)

Tuesday, September 11, 2007 4:30 PM by InfinitiesLoop

charlotte --

Nice scenario. I think you should be able to do it without anything dynamic. Even though your data is dynamic on a couple different dimensions, you always know WHAT controls would be rendered. Here's a rough outline that would get you close...

<asp:Repeater DataSource="<%# GetGroups() %>" ...>

   <ItemTemplate>

        <%# Eval("GroupName") %>

         <asp:CheckBoxList Visible="<%# Eval("IsCheckBoxList") %>" DataSource='<%# Eval("Items") %>' />

         <asp:RadioButtonList Visible="<%# !Eval("IsCheckBoxList") %>" DataSource='<%# Eval("Items") %>' />

   </ItemTemplate>

</asp:Repeater>

The GetGroups method returns a list of group objects, each which contains info about the group as well as a 'Items' property or method that returns a list representing the choices within that group. They also contain a property IsCheckBoxList which returns true if it should be a checkbox list or false if it should be a radio button list (a more UI-agnostic name might be better, like AllowMultiple).

Then you simply build both a CheckBoxList and a RadioButtonList, but their visibility is mutually exclusive based on the boolean value of IsCheckBoxList/AllowMultiple.

That control itself allows you to specify the number of columns the options should be rendered in, which you databind with another reference like RepeatColumns="<%# Eval('Columns') %>". Note that even the datasource of these controls is set declaratively. All you have to do is call DataBind() on the root repeater and everything is set into motion automatically.

Note that it may be that RadioButtonList/CheckBoxList do not render in the way you would require it, but the concept would be the same even if you had to create your own custom control that renders them in TDs or something.

# re: TRULY Understanding Dynamic Controls (Part 4)

Wednesday, September 12, 2007 5:24 AM by charlotte

wow - thanks !!! for your quick reply - I'll go ahead right away - you are a darling :))

# re: TRULY Understanding Dynamic Controls (Part 4)

Wednesday, September 12, 2007 8:13 AM by Nicola

I need to put two controls with the same id in two different panels.

I understood that it would be possible since I gave to the two panels two different id so that, say, the uniqueids of my two controls would be something like:

Panel1$Textbox1 and

Panel2$Textbox1

VS2005 doesn't allow me to do this.

Isn't panel implementing INamingContainer ? Maybe I misunderstood the meaning of INamingContainer ....

# re: TRULY Understanding Dynamic Controls (Part 4)

Wednesday, September 12, 2007 12:30 PM by InfinitiesLoop

Nicola -- Panel does not implement INamingContainer. That would be way too much for the main scenario panel is used for.

INamingContainer is really meant for situations where you know for sure there's going to be duplicate IDs, or its a possibilty and you don't know.

For example, repeater implements INamingContainer, because it is going to be repeating the declared item template, which likely has a control within it with an ID.

UserControls implement INamingContainer, because they might contain a control with a specific ID and someone might put multiple instances of the user control on the same page.

You don't have either of those risks with a panel.

Even if it did implement it, ASP.NET assigns controls to the fields named after the ID. So you can't expect "this.foo" to refer to one of the panels, because how does asp.net know which one it should refer to? For that reason you generally can't have multiple controls with the same ID declared within the same markup on the same page or user control.

Why do you require this? Perhaps there's a better way.

# re: TRULY Understanding Dynamic Controls (Part 4)

Friday, September 14, 2007 9:05 AM by Nicola

I don't understand this:

"ASP.NET assigns controls to the fields named after the ID"

But, if I understand the rest, I can create a user control with a panel inside and use it! I will try.

I need to do this because I am involved in this fool project: we have a win32 form designer which stores form definitions in a database.

A client application (again win32) can read this form definitions (along with lot of other data dictionary informations: user profiles, data catalogues and so on) and renders the UI dynamically, actually running a complete application. It is a kind of framework, something similar to Access (we called this app of ours "Ouverture"). Now I am trying to port this logic to asp.net to have a web based client. The form definitions are hierarchical, one form could be placed inside another, and so I can have components with same name, provided that they are in *different containers* (if it wouldn't be so, it would be a problem even for the win32 client).

I sometimes feel frustrated but I keep fighting :-) (and your articles are a great support for me indeed..)

Thanks

Bye

Nicola

# re: TRULY Understanding Dynamic Controls (Part 4)

Friday, September 14, 2007 10:31 AM by Nicola

With a user control containing just a panel it works. But perhaps are there any risks / dangers in this approach ? You seem quite careful about user controls.. or not ?

# re: TRULY Understanding Dynamic Controls (Part 4)

Friday, September 14, 2007 12:10 PM by InfinitiesLoop

Nicola -- UserControls implement INamingContainer, that's why it works. If all you need is to be able to contain the item with a naming container, then you should implement a simple Container control that implements INamingContainer and use that instead. It would be much simpler. And easy too.. here's the entire class

public class NamingContainer : Control, INamingContainer

{

}

done :)

# re: TRULY Understanding Dynamic Controls (Part 4)

Monday, September 17, 2007 11:41 AM by Joao

Hi.

I'm trying to build a custom server control for creating a datagrid.

And I can't solve the following problem:

I created a custom template where I build these LinkButton controls: Edit, Delete/Update,Cancel to represent all those actions but when I click on a button the ItemCommand Event isn't raised. I only occurs when I press the button a second time.

Any help would be apreciated.

Joao

# re: TRULY Understanding Dynamic Controls (Part 4)

Monday, September 17, 2007 1:25 PM by InfinitiesLoop

Joao -- sounds like exactly what would happen if your IDs were different on postbacks. The first time its ignored because the id changes on the postback, then the second time it works because it was a postback before and after. You'll have to send me some code to tell you exactly why that might be happening. Turn on tracing and watch the control tree as you click the button -- if its id changes, find out why by thinking about how and when it is created.

# re: TRULY Understanding Dynamic Controls (Part 4)

Tuesday, September 18, 2007 5:57 AM by Joao

Hi,

I'm trying to create a custom server control and I can't make it work.

It's a dynamic datagrid, I created several ITemplate classes and one of them has the buttons: Edit,Delete/Update,Cancel. When I click in one of the buttons for the first time it doesn't fire the ItemCommand event, the event only occurs when I click a second time in the same button.

Any idea why?

Joao

# re: TRULY Understanding Dynamic Controls (Part 4)

Tuesday, September 18, 2007 1:27 PM by InfinitiesLoop

Joao -- see above comment

# re: TRULY Understanding Dynamic Controls (Part 4)

Tuesday, September 18, 2007 6:27 PM by julianmj

ok ok, I already know that is not a very good idea use OnLoad, I don't want to commit a ViewState crime!!

How can I dynamically populate a control (DropDown, CheckBoxList)??

# re: TRULY Understanding Dynamic Controls (Part 4)

Tuesday, September 18, 2007 6:49 PM by InfinitiesLoop

julianmj -- databinding?

cbl.DataSource = GetMyData();

cbl.DataBind();

I think you can add to the items collection manually if you'd rather. Whether you do that from OnLoad or elsewhere depends on your scenario. Earliest you can do it, is when you should, and disable viewstate on the thing if you can do it every request.

# re: TRULY Understanding Dynamic Controls (Part 4)

Wednesday, September 19, 2007 9:26 AM by julianmj

Sorry but my first comment does not appears.

I wrote:

I have made some custom controls(Dropdownlist, Checkboxgroup) with new properties, I use this properties to create a query and populate the control dynamically. I do this sending to a method this collection: Form.Controls, then  search for every custom control and do the databind for each one.

Now, after I read your articles I modify the custom control class and add this method:

public class GrupoRadio : RadioButtonList,IControlBind

   {

...

      protected override void OnInit(EventArgs e)

       {

           //IdOrigenDatos is a new propertie

           this.DataSource = GetData(this.IdOrigenDatos);

           this.DataBind();

           base.OnInit(e);

       }

...

}

Is this correct??

Again sorry and thanks !!! for your quick reply.

# re: TRULY Understanding Dynamic Controls (Part 4)

Wednesday, September 19, 2007 1:13 PM by InfinitiesLoop

julianmj -- Is it correct... well is it working or are you having any problems? You might want to disable viewstate on that control since you're binding it every request anyway.

# re: TRULY Understanding Dynamic Controls (Part 4)

Thursday, September 20, 2007 12:27 PM by julianmj

Thanks again. It is working and I already disable ViewState. I'm leaving that way, it worked for me.

I will read again all your articles to find a way to improve my controls...

(sorry for all the typos, I'm still learning English) :)

# re: TRULY Understanding Dynamic Controls (Part 4)

Saturday, September 22, 2007 1:35 PM by Alexander Sher

Hi Dave!

Several month ago in one of the topis on gotdotnet.ru forum we got similar question about 2 dynamically loaded user controls. But my solution was a bit different (adapted to your example):

private bool optChanged;

protected override void OnLoad(EventArgs e) {

   base.OnLoad(e);

   if (IsPostBack) {

       ((IPostBackDataHandler)opt1).RaisePostDataChangedEvent();

       LoadControls(opt1.Checked ^ optChanged);

       ph.Controls.Clear();

   }

   LoadControls(opt1.Checked);

}

private void LoadControls(bool loadFirst) {

   Control ctl;

   if (loadFirst) {

       ctl = LoadControl("~/UserControl1.ascx");

   } else {

       ctl = LoadControl("~/UserControl2.ascx");

   }

   ctl.ID = "foo";

   ph.Controls.Add(ctl);

}

protected void CheckChanged(object sender, EventArgs e) {

   optChanged = true;

}

What can you say about it?

# re: TRULY Understanding Dynamic Controls (Part 4)

Sunday, September 23, 2007 6:23 AM by Alexander Sher

Oops, sorry! My last piece of code will not work ;) Correct variant might look like this:

private bool optChanged;

protected override void OnLoadComplete(EventArgs e) {

   base.OnLoadComplete(e);

   if (IsPostBack) {

       LoadControls(opt1.Checked ^ optChanged);

       ph.Controls.Clear();

   }

   LoadControls(opt1.Checked);

}

private void LoadControls(bool loadFirst) {

  Control ctl;

  if (loadFirst) {

      ctl = LoadControl("~/UserControl1.ascx");

  } else {

      ctl = LoadControl("~/UserControl2.ascx");

  }

  ctl.ID = "foo";

  ph.Controls.Add(ctl);

}

protected void CheckChanged(object sender, EventArgs e) {

  optChanged = true;

}

# re: TRULY Understanding Dynamic Controls (Part 4)

Sunday, September 23, 2007 4:13 PM by InfinitiesLoop

Alexander -- events like CheckChanged occur after OnLoad so I'm not following what the code is doing... it seems like optChanged would always be false. It also seems that on postbacks you're always going to load the control(s) twice even if the active one has not changed.

If you use L