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.

152 Comments

  • 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.

  • 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.

  • 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

  • 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.

  • 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?

  • 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.

  • 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.

  • 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

  • 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.

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

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

  • 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?

  • 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="" ...>


    <asp:CheckBoxList Visible="" DataSource='' />
    <asp:RadioButtonList Visible="" DataSource='' />



    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="". 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.

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

  • 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 ....

  • 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.

  • 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

  • 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 ?

  • 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 :)

  • 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

  • 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.

  • 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

  • Joao -- see above comment

  • 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)??

  • 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.

  • 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.

  • 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.

  • 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) :)

  • 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) {
    &nbsp; &nbsp;base.OnLoad(e);
    &nbsp; &nbsp;if (IsPostBack) {
    &nbsp; &nbsp; &nbsp; &nbsp;((IPostBackDataHandler)opt1).RaisePostDataChangedEvent();
    &nbsp; &nbsp; &nbsp; &nbsp;LoadControls(opt1.Checked ^ optChanged);
    &nbsp; &nbsp; &nbsp; &nbsp;ph.Controls.Clear();
    &nbsp; &nbsp;}
    &nbsp; &nbsp;LoadControls(opt1.Checked);
    }
    private void LoadControls(bool loadFirst) {
    &nbsp; &nbsp;Control ctl;
    &nbsp; &nbsp;if (loadFirst) {
    &nbsp; &nbsp; &nbsp; &nbsp;ctl = LoadControl("~/UserControl1.ascx");
    &nbsp; &nbsp;} else {
    &nbsp; &nbsp; &nbsp; &nbsp;ctl = LoadControl("~/UserControl2.ascx");
    &nbsp; &nbsp;}
    &nbsp; &nbsp;ctl.ID = "foo";
    &nbsp; &nbsp;ph.Controls.Add(ctl);
    }
    protected void CheckChanged(object sender, EventArgs e) {
    &nbsp; &nbsp;optChanged = true;
    }
    What can you say about it?

  • 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;
    }

  • 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 LoadViewState instead you are able to get the value of opt1 before it loads its postdata, which means it will always be whatever it was on the last request. You load the appropriate control there. The the changed event, if it has indeed changed, is raised. You clear the existing control then load the new control.

  • > events like CheckChanged occur after OnLoad
    Yes, that's why in second example I've used OnLoadComplete instead. So it works.
    But I don't understand about LoadViewState. As I know, page LoadViewState will be called before opt1.LoadViewState. Surely, I can create new control, inherited from RadioButton, override LoadViewState method and create something like WasChecked property or add event that will occur after LoadViewState and before Page.ProcessPostData. But can I do it without creating new controls?

  • >> As I know, page LoadViewState will be called before opt1.LoadViewState

    Yes, thats why you call base.LoadViewState from your override first. It's recursive, so by then the checkbox/radiobutton will have loaded its ViewState.

  • > It's recursive, so by then the checkbox/radiobutton will have loaded its ViewState.
    Well, please correct me if I'm wrong. LoadViewState is called recursively for all controls that have something in viewstate, but it doesn't produce this recursion. As I understood, recursion is produced by three internal methods - LoadViewStateRecursive, LoadChildViewStateByID and LoadChildViewStateByIndex. And LoadViewState is called before LoadChildViewStateByID or LoadChildViewStateByIndex. That's why I think opt1 viewstate will be loaded after Page.LoadViewState is completed, and LoadViewState override will not help, especially when page viewstate is empty.
    You see, I'm trying to reduce ViewState size and avoid adding data that already in there (radiobuttons already store their old values). May be itis better to look on the problem from another angle - disable opt1 and opt2 viewstates and manage changes manually?

  • No you're absolutely right about LoadViewState -- child state is loaded after it is completed. My bad. But my lapse of reason had a purpose because I was intending for it to be used as part of the "manual" tracking (when you'd always have a key to load, and it would always be loaded after calling base). Doing that and disabling the opt's ViewState would probably result in smaller state, slightly. Kind of splitting hairs at that point though.

    I still think you're doing too much with calling LoadControls() twice even when the state didn't change. Why not add an IF around it...

    if (optChanged) {
    LoadControls(opt1.Checked);
    }

  • I understand that, using dynamic controls, I have to rebuild the control tree at each postback. Would it be possible to save the control tree (once generated the first time) somewhere in some format and restore it on next postbacks ? Doing so may save me some processing..
    Thanks bye Nicola

  • Nicola -- Say you did somehow save the control tree. Where would you put the data? On the client for postback? How would you process that data? Parse it, and rebuild the tree? So, you're going to be processing something anyway. Why not cut out the middle man and just process it the way you normally do? One way or another something has to rebuild the tree -- even if asp.net did it for you, it would be using resources that you can eliminate.

  • I got it figured out. The dataset and the repeater was not quite in sync. So I caught it in the Page_Load and updated the information in the dataset with the information in the Repeater, foreach repeateritem item in Repeater.Controls, then a FindControl("txtboxes") on each of the controls and saved them to the datatable using the index of item.ItemIndex. If anyone is lost on what I'm saying, a link to my source is in the post above. I had just got done converting a mess of dynamic controls to use the repeater control and was losing my information between postbacks. The actual code I added is below. Not beautiful, but it works. Your article series was GREAT and allowed me to refactor a large unwieldy code base to something a lot more comfortable to deal with.

    // Fill the ViewState Datasource with information.
    foreach (Control item in Redux1.Controls)
    {
    if (item is RepeaterItem)
    {
    if (((RepeaterItem)item).ItemIndex > -1)
    {
    // Find if the controls are in this Repeater Item
    TextBox txtName = (TextBox)(item.FindControl("fldName"));
    DropDownList ddlMonth = (DropDownList)(item.FindControl("iMonth"));
    TextBox txtDay = (TextBox)(item.FindControl("fldDay"));
    TextBox txtYear = (TextBox)(item.FindControl("fldYear"));
    TextBox txtPerc = (TextBox)(item.FindControl("fldPercent"));
    // if the controls are found save them to the dataset.
    if (txtName != null){
    ((DataSet)ViewState["dsetOwners"]).Tables[0].Rows[((RepeaterItem)item).ItemIndex]["fldName"] = txtName.Text;}
    if (ddlMonth != null)
    {
    ((DataSet)ViewState["dsetOwners"]).Tables[0].Rows[((RepeaterItem)item).ItemIndex]["iMonth"] = ddlMonth.SelectedValue;}
    if (txtDay != null){ ((DataSet)ViewState["dsetOwners"]).Tables[0].Rows[((RepeaterItem)item).ItemIndex]["fldDay"] = txtDay.Text; }
    if (txtYear != null)
    { ((DataSet)ViewState["dsetOwners"]).Tables[0].Rows[((RepeaterItem)item).ItemIndex]["fldYear"] = txtYear.Text;}
    if (txtPerc != null)
    { ((DataSet)ViewState["dsetOwners"]).Tables[0].Rows[((RepeaterItem)item).ItemIndex]["fldPercent"] = txtPerc.Text; }
    }
    }

  • Stephen --

    I love that you converted to repeater and it's working well. It's definitely the way to go...

    As for your problem... well, when you are rebinding the repeater you have to think of it as if you are starting over. Anything not in the data you are binding is going to be thrown away.

    But -- you probably have a way to get the data out of the repeater, correct? Like a save button that goes into each item and updates the corresponding row? You can use that exact same logic to aid you here. It's the reverse of setting the DataSource on the repeater -- you just need a method that gives you the DataSource back again from the state of the repeater. You do that, then you add or remove the row and rebind that. When it finally comes time to save, you use the same method to get back the dataset from the repeater and do what you want with it.

  • Stephen -- nevermind you read my mind!

    One thing that concerns me with the code is that you're saving the DataSet in ViewState. You shouldn't need to do that -- you can always rebuild the DataSet from scratch using the data in the repeater. By storing the DataSet you're really storing the data twice -- once in the dataset, once in the repeater.

  • > I still think you're doing too much with calling LoadControls() twice even when the state didn't change. Why not add an IF around it...
    Yes, you absolutely right and I'm really ashamed of this mistake.

  • Well I was thinking that, at the end, the result of all this process is "simply" some html/javascript. My idea was: why don't you save the html result somewhere (in a database ?) and then re-send it "as it is" from the second request on ? (just trying to understand better..)

  • Nicola -- the result of rendering controls is just html/javascript, but thats not all they are good for. They react to postbacks, and change their state. They may render different data each time.

    If you're rendering the same thing all the time and want to optimize for that, take a look at OutputCaching.

  • How would you solve the issue of dynamic Controls being loaded inside different containers than the last time the page posted back. For example if you look at Web 2.0 startpages like iGoogle or PageFlakes you will notice that you can move around widgets entirerly using clientscript (without PostBack) & a callback to the server saves their new location. However, with the default ASP.Net ViewState management, the ViewState will never be able to get applied to the control because now the control was dynamically added to a different container than in the previous postback. Sure, you can re-add the control back to the same container first - so that ViewState can get applied - and then move it but that seems like such a hack and awfully slow. Would you recommend building custom ViewState in the Load/SaveViewState methods of the page, similar to the DynamicPlaceHolder?

  • Arif -- the structure of the control tree does not have to dictate the structure of the rendered html. In this case I'd recommend a container control that knows how to selectively render its child 'modules' into the right locations, without physically moving them in the control tree. See my post on "Rendering ASP.NET Controls out of place" for an example of doing something like that. This particular solution can be more specific though without the use of that specialized Renderer control.

    Another solution is to just position the 'modules' with css. It wouldnt matter what order they were in the html, the style would just put them in the right location. You'd have to have some client side positioning logic anyway if you're going to support moving them client side.

  • hi, thanks for your article. i'm currently chewing on some of the things you said about viewstate.

    my situation: i make a webservice call, from which i asynchronously receive a rendered dropdown, which gets placed on the page that makes the call. the dropdown is a customized control, which has overriden events (eg. prerender) that conditionally add rows to the dropdown. i'm instantiating the custom control in the webservice to bind it and render the html for the return. what i'm noticing is that the prerender event of the control does not fire. why would this be?

    the thing that's different here from your explanations is that the control is not being added to the page control tree, as it's not exactly participating in the page life cycle.

    i've been looking for some resource to speak to this area but haven't found any yet. your thoughts would be appreciated.

  • g --

    Events like Init and PreRender are driven by asp.net itself, not each individual control. That's why PreRender doesn't occur... no one told it to. Controls weren't designed to be 'hosted' independently so I don't think there's a great workaround for you there. You certainly wouldn't be able to participate in postback events with that control (which you may not care about anyway).

    But... if what I feel about the control is right, then you dont need to do what you're doing from PreRender anyway. If you dont care about viewstate and postbacks then it wouldnt make any difference if you just did your dynamic item additions from Render instead of PreRender. Just keep in mind that since the control isnt in a control tree, it isn't going to have a fully unique ID either. Render this thing twice on the same page, and you've got two dropdowns with the same "name".

  • dave,
    thanks for the prompt response! your comments help fit some of the pieces together. do you think the fact that a control doesn't go through its events is a design oversight?

    in the interim i've started looking at script callbacks as well, which may be a good middle ground to settle in (vis-a-vis webservice calls). we'll see.

  • g -- no problem. No I don't think its a design oversight. It's a limitation of the design I suppose. Controls going through lifecycle events without being in a page just doesn't make sense in asp.net because of how viewstate and postback data are processed, and because of INamingContainer.

    Do you know about UpdatePanel? Allows you to make partial updates to the page without opting out of the page lifecycle.

  • a prompt response again - thanks :). re: your comments on controls going through events - i'll chew on that. what are the implications of inamingcontainer?

    i do know about updatepanel. i'm currently exploring my options for asynchronous page renderings - i'd like to know how the plumbing works. using the updatepanel means subscribing to the ajax.asp framework, which means a couple of things that make me pause: 1) going through the whole page lifecycle on asynch postbacks; 2) leaving too much of the functionality to the black box (though maybe wonderful) of ajax.asp and resting on my laurels.

  • Thank you for such a great article. It has taught me a lot around dynamic controls. I have fought most of these issue on a project where we used dynamic user controls. I am playing around using templates to replace some of our very complex dynamic control code and I have a question. I am using the following code to add my controls to the page:


    <gv:Fees ID="fee" runat="server" Amount=
    FeeID=
    PaidBy=
    Type= />



    This works great and the right number of gv:Fee user controls get added. The problem comes in with post back events. This user control has a button that when clicked needs to call a function back on the server.

    When I run the page, the controls get added but when I click the button, the server side code is never called. Is there something extra you have to do to get the repeater to allow for this?

    Thanks,
    Josh

  • Is it possible to "reverse-engineering" a control collection to a .aspx page ? I have a function that dynamically builds at runtime a page. I am thinking about changing it in an offline process to statically build pages to be compiled, instead of build them on the fly. Would it be possible ? I looked at HtmlWriter class but it seems suited for pure html, what if you need to write an .aspx file ?
    Thanks bye nicola

  • Nicola -- I'm afraid nothing built-in is gonna help you here. But you can use an xml writer or xml document. Determining all the attributes and converting the values to strings won't be a trivial task though :) Don't forget about dynamically writing the @register directives.

  • Hi, I read (skimmed) all the 4 parts. Lots of good knowledge, thanks. I have a problem where I'm using dynamic controls, and I don't know whether I can use a repeater like you showed in this part.

    I have a page which allows the user to look up products. To begin with, that page has 10 rows, which have a textbox for entering item num and a textbox for entering qty.

    When the user runs out of the rows, they can click the "More" button to give them more rows.

    My Draw() method adds rows and cells dynamically, to a statically declared table, and adds a textbox to each one of the cells dynamically. For my textboxes, I do declare IDs, based on the numrow, and I keep count of current number of rows, so when I need to read in the user input values, I can do that.

    Finally when the user is done, they click "Validate" which validates the items and quantities against a database by first reading in all the values and adding them to an object and calling object.validate(). The object then contains the validation info, basically text description for an item if it was valid, and a valid status, and error message if it was invalid, and an invalid status.

    Based on this info, I get an enumerator into that object's list and while drawing my textboxes dynamically, I input the values from the list into the textboxes, and highlight the rows appropriately (red for invalid, green for valid).

    I have to call my Draw() method from Pre_render event handler, which is a problem when I want to do validation using validators. I'd love to call it from Pre_Init, but that doesn't work because firstly, I don't know how many rows I have till I get to the button More's event handlers, and secondly I don't have the validated item list from the database till I get to the Validate's event handler. So as far as I know it, it's impossible for me to get the validators to work.

    Any ideas on how to solve this?

  • I understand, thanks. Another doubt: what about thread synchronization ? If I am dynamically building controls into a web page do I need to stick around some "lock(this)" statements ?
    Or does the framework handle this process automatically ?
    Thanks again! Bye Nicola

  • Nicola -- there's only one thread that processes the page. No worries.

  • Really ? it is quite different from what I was used to with isapi development. What about static class or Application / Session properties ?
    I guess my question is a little off topic... Perhaps may you point me to a good resource about this topic ? thanks again again. Bye Nicola

  • Nicola -- yes really. There's only one thread processing the request. The page, the instances of the controls, the entire control tree and all its state -- these are all unique to each request which is served by only one thread, so you're safe to go willy nilly. There are many requests occuring for different users, of course, so accessing static members or shared instance members still needs to worry about thread sync. Session doesn't, because unless you put it into read only mode, only one request from the user can access session at a time (it is internally syncrhonized already).

  • Stev -- the fact you are creating the controls from ItemDataBound worries me a bit :) That's because unless you are databinding the repeater every request, the control isn't going to exist on a postback. ItemDataBound only occurs when you DataBind the repeater.

    What you'd need to do is store in ViewState information about which controls were created during databinding, then from the ItemCreated event you lookup that value and add the appropriate control. That way they can continue to exist even if no databinding occurs on a postback.

    One test you must always put your controls through is this -- put a button that does a postback on the page. Don't have it do anything, it just posts back. You should be able to click that button at any stage in your page progression and everything should remain the same. If things disappear, or revert their state, you have a problem.

  • Jenny -- Just create a control that does nothing except load the user control dynamically.



    Where MyPlaceHolder overrides OnInit and loads the user control into its control collection, nothing more. It's just a level of indirection so you can still approach the case where you have a user control nested within itself.

  • rodchar -- whether you use the FormView or not depends on whether you want some of the features it provides. Either way, you'd be building controls into a hiearchy that meets the needs of the table in question. For each, you'd be hooking into its DataBinding event, and from the handler you'd be assigning to the control the data from the bound data. You just need to ensure that you create these controls every request, such as by saving the selected table (its name or id, NOT the table itself!) into viewstate then from OnLoad or OnLoadViewState you look for that entry. It wouldn't be that different from the example in this article.

  • Hi InfinitiesLoop,

    Firstly thank you for such a great article, i have read lot of article in these issue but this was the best. You have given very thorow explanation of the topics.

    But still i am confused while creating one of the logic for my project, could you please tell me the reason, i am sending you a small demo code, where i am providing a simple functionality to provide the language known option for user according to their choice, for which i have provided a add more button, when that clicks and new textbox appers in the form, i managed to restore its view state but when i again click the add more button, the text box is added two times, i know i could add the invisible text boxes, but my problem is little bigger where i have to add a usercontrol with lot of control in it, it is just a simple demo, which if you could given me explanation i will be thankful.


    public partial class DemoCommonControl : System.Web.UI.Page
    {
    private bool IsButtonClicked = false;
    protected void Page_Load(object sender, EventArgs e)
    {

    }
    protected override void OnInit(EventArgs e)
    {
    base.OnInit(e);
    }
    protected override object SaveViewState()
    {
    return base.SaveViewState();
    }
    protected override void LoadViewState(object savedState)
    {
    base.LoadViewState(savedState);
    CreateTextBox();

    }
    protected void Button1_Click(object sender, EventArgs e)
    {
    if (ViewState["counter"] == null)
    ViewState["counter"] = 0;
    else
    ViewState["counter"] = Convert.ToInt32(ViewState["counter"]) + 1;

    IsButtonClicked = true;
    CreateTextBox();
    }
    private void CreateTextBox()
    {
    int x = Convert.ToInt32(ViewState["counter"]);
    TextBox txtName;
    if (x == 0)
    {
    for (int ctr = 0; ctr <= x; ctr++)
    {
    txtName = new TextBox();
    PlaceHolder1.Controls.Add(txtName);
    }
    }
    else
    {
    for (int ctr = 0; ctr <= x - 1; ctr++)
    {
    txtName = new TextBox();
    PlaceHolder1.Controls.Add(txtName);
    }
    }
    }
    }

    Thanks,
    Digamber

  • Digamber -- your CreateTextBox method is going to create textboxes based on your ViewState counter each time it is called. But you are calling it twice -- once from LoadViewState and once from Button Clicked. Say the counter is 2. You call CreateTextBox. Then the button click event is raised and you change the counter to 3. Then you call CreateTextBox again, which creates the same number of textboxes as before plus 1 more.

  • Hi, and thanks for a great series of articles.

    What if the control you load in LoadUserControl depends on ControlState (maybe in addition to ViewState)?

    LoadControlState occurs before LoadViewState - doesn't that mean that your controls need to have been created before LoadControlState? And adding controls from within LoadControlState is not possible - you get a "Collection was modified after the enumerator was instantiated".

    Thanks!
    /Fredrik

  • Fredrik -- I'd try to stick to thinking of Control State as ViewState. Its really the same thing, just segregated out. The fact the event occurs whenever doesn't matter, controls added later still go through the essential event sequence.

  • But if I add controls after LoadControlState, will these controls have their control state loaded? I thought that if the control is not in the hierarchy at the time when LoadControlState occurs, the control state would be lost.
    If I add controls in LoadViewState (occuring after LoadControlState), how can these controls ever receive their control state?
    Or am I missing something here?

    Thanks!
    /Fredrik

  • Fredrik -- when a control is added to the tree, it plays 'catch up' with the event sequence. If you add a control dynamically from Load, for example, which is after LoadViewState, it will still load its viewstate.

  • Hello InfinitiesLoop (Newman, if your a Seinfeld fan),

    Regarding the following quote:
    "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."

    I have adapted your CheckChanged example and depending on which radio button is selected i retrieve a DataTable from the database and manually display the fields in an html table.

    my question is when CheckChanged is fired, say it was opt1 and now it's opt2, will opt1's data routine run as well, then clear the controls, then run opt2's data routine?

    thanks,
    rodchar

  • Helly Jerry -- err, Rodney,

    Yes.

    Does that answer your question? :)

  • Do you think this is ok to do? to go out to database on each postback twice?

  • It's only twice on postbacks in which the radio button selection has changed.

    I think that's better than storing data in viewstate and rebuilding the controls based on that. If you're really concerned about perf/db contention then you can cache the data in asp.net cache pretty easily, and even use a SqlCacheDependency to make it automatically invalidate when the database changes. It doesn't get much better than that.

  • Great!! Thank you for your article and follow-ups. This has been educational.
    Rod.

  • Thanks for the articles! I thought after much goggling I might find the answer here. I'm much wiser now, but I am still searching for a solution to a problem.

    I can send/post code if needed, but first let me just say that there appears to be a few people like me out there with this problem (and without a resolution -- and the code involved is rather lengthy): I am creating dynamic user controls, each containing one particular type of input field (dropdownlist, radio list, text field, checkboxes, etc), all driven by field meta data stored in a database.

    I can successfully process all field types I'm using, except for a dropdownlist -- I cannot fetch the SelectedValue from the dropdownlist (within a user control) after the user clicks save.

    I have worked hard at understanding the points at which I should be creating and repopulating the control. This article and another led me to start overriding LoadViewState and recreating and repopulating the list after a call to base.LoadViewState. It simply doesn't seem to work... The frameworks via Viewstate just doesn't seem to be able to set the selectvalue of the dropdownlist; it's empty, even though via a trace I can see the the value was posted back correctly.

    I am fairly certain that I am creating my controls in the same order, etc., etc., etc. Any thoughts are appreciated!

  • Never mind the above; I believe I have it... I just listened to what I was saying and decided that I might not be recreating the option list in time, and sure enough...

    Looking forward to part 5!

  • Rod -- I'm not exactly sure what is going on based on your code snippet. The DataTable is assigned in LoadUserControl. When is it null and how?

  • I'm sorry for the confusion. What I'd like to do is extract the LoadDataTable methods from the new UserControl (from your LoadUserControl procedure) and let the code-behind do the LoadDataTable methods and just pass in the datatable.

    so, when i try this any postback will cause the datatable to be null.

    hope this is a little clearer.

    rod.

  • Rod -- so I think what you are saying is that you'd just have a DataTable property, which is assigned by the page using this user control. And your problem is that it is null on a postback.

    It's going to be null unless you assign a value to it. Remember posts represent an entirely new life for the page. Everything is reinstantiated from scratch. So the property won't have a value unless you assign it.

  • In the override LoadViewState the DataTable property is null on postback. now, i'm assigning a value to the DataTable everytime inside the code-behind page_load and i notice when i debug it and cause a postback from the usercontrol the code-behind page_load doesn't even run. am i doing the assigning of the datatable in the wrong place?

  • Rod -- LoadViewState comes before Load. You'll have to either do it sooner like Init.

  • So instead of using viewstate["state"] i'm going to have to use session? because when i checked in OnInit viewstate wasn't available, which i guess makes sense since LoadViewState hasn't occurred yet. am i thinking right? and is this ok to do this way?

  • Rod -- why would you use session? Apparently I really don't understand what you're trying to do. Why don't you send me some actual code privately?

  • Hi InfinitiesLoop,
    Thanks once again for taking time out to follow-up on all our comments. This has TRULY been helpful and I appreciate it very much. Also, thank you for the long and short ways to handle my specific issues, I now feel I have better direction.
    Rod.

  • Hey all,
    So if i'm using dynamic controls and i'm hitting a database on every postback could i turn off viewstate for the individual controls that are being dynamically generated? could i disable it for the entire page except for the viewstate mentioned in the article's context?

    thanks,
    rodchar

  • Rodney -- in general, yes. Sometimes even if you are providing data on every request you need viewstate enabled because of other features of the control, but you have to take that on a case by case basis, and even then there's usually a way to work around it.

  • Rodney, thanks for taking the time to help people. It is greatly appreciated by many of us!

    I'm trying to do something simliar but a little different and can't seem to get it working right.

    I've got a gridview that has a template field with a place holder in it. In the rowdatabound event I'm finding the placeholder control and adding a dynamic control to it based on a switch conditional. Generates the control fine.

    In the updating row event, I'm trying to store the user inputed data to the database. When I use findcontrol for the placeholder, it finds it. However, the dynamically added controls don't exist. How can I fix it? I'm fine with using a statically creating it (if it will work) and setting visible = false. However, i'm trying to save the results in a database, so I'm wondering if i'm going to have problems binding multiple controls to a single field? Here's some code in how i'm doing it now... perhaps you could give me some guidance in how to approach this better. Thank you for your help... it is much appreciated!

    protected void EditableGrid_RowDataBound(object sender, GridViewRowEventArgs e)
    {

    if( e.Row.RowType == DataControlRowType.DataRow ) { // we are binding a "data" row, as opposed to a header or footer or empty row.

    GridView gv = sender as GridView;
    // parse the data column, using a regex
    string answer_type = DataBinder.Eval( e.Row.DataItem, "answer_type" ).ToString();
    PlaceHolder p = e.Row.FindControl( "answer_placeholder" ) as PlaceHolder;
    if( p != null ) { // ...and we found the panel to hold auction links
    switch(answer_type) {
    case "r":
    break;

    default:
    //build radio buttons and mark them according to answer in db.
    RadioButtonList rbl = new RadioButtonList() ;

    rbl.ID = "rbl";
    rbl.RepeatDirection = RepeatDirection.Horizontal;
    string answer_int = DataBinder.Eval(e.Row.DataItem, "answer_int").ToString();

    rbl.Items.Add(new ListItem("Yes", "1"));
    rbl.Items.Add(new ListItem("No", "0"));

    if (answer_int != "")
    {
    rbl.Items.FindByValue(answer_int.Trim()).Selected = true;
    }

    p.Controls.Add(rbl);

    break;


    }
    }
    }


    protected void EditableGrid_RowUpdating(object sender, GridViewUpdateEventArgs e)
    {
    GridView gv = sender as GridView;
    if (gv.Rows[e.RowIndex].RowType == DataControlRowType.DataRow)
    {
    PlaceHolder p = gv.Rows[e.RowIndex].FindControl("answer_placeholder") as PlaceHolder;
    RadioButtonList rbl = p.FindControl("rbl") as RadioButtonList;
    if (rbl != null) //HERE - RETURNS NULL!!!
    {
    if (rbl.SelectedIndex > -1)
    {
    e.NewValues["answer_int"] = rbl.Items[rbl.SelectedIndex].Value.ToString();
    }
    }
    }
    }

  • Hmm...

    I fixed it, but I have no clue how. Maybe I just had a bad binary someplace... If I find out what went wrong I'll post, but it goes into the unsolved mystery pile for now.

    Thanks for this blog, anyway, it rocks!

    BriaN

  • BriaN -- glad you fixed it, no help from me! I don't know what was wrong without really getting into it, but perhaps these tidbits of info will help you.

    1. Databinding from PreRender is fine -- in fact thats when binding occurs for controls hooked up declaratively to a datasource control.
    2. DataBind is recursive -- it databinds the control it is called on and all of its child controls.
    3. DropDownList will throw that error if you set the SelectedValue before binding, and then bind to it a list that doesn't contain that value. Binding without setting the datasource first would fall under this too.
    4. PreRender is top-down... so when you bind from PreRender from parent, it is before PreRender in child.

  • Thanks for replying so fast, and thanks again for the great article.

    I haven't fixed it, I had commented out the DataBind() statement in the parent - but just as was going to test it an unrelated database problem happened and I simply forgot I had made the change...

    If I don't call base.DataBind() in the base class of my custom control (which extends panel), I don't get the error, but then I can't set the SelectedValue of the dropdown properly because it's passed in a databinding expression in the markup (as the Text property), which is kind of the whole point of the custom control - hiding the internal differences between different kinds of input controls, heavy with client-side code, behind a uniform markup syntax.

    I can't figure this out, and I need to find the answer in under 48 hours or punt and just disallow databinding in the prerender. Is there a chance of you being able to help me in that time frame?

    Thanks again,

    BriaN

  • PHEW!!!
    I found it, it had nothing directly to do with any of this, but was instead caused by expecting a Items.Add(String.Format("{0:00}",i) to add an item with text and value both of just a regular 2 character string with 2 digit numbers - instead, in the PreRender, the formatting stuff was still in the values, so the SelectedValue I set before of "00" was invalid.
    The funky thing is, if I only ran DataBind once, I could treat them as "00", "01", etc. to no ill effect. It was only going back to DataBind() without a round-trip to the browser that broke it... very, very odd.
    I now add the items this way instead:
    Items.Add(new ListItem(String.Format("{0:00}", i),i.ToString()))
    and it seems to be working everywhere.
    Can't decide whether to feel smart for finding it or dumb for doing it in the first place now...
    I really appreciate having had this public forum to force me to be more rigorous in defining my problem, it helped a lot.
    Thanks, and I can't wait for part 5,
    BriaN

  • Hi Dave!
    Few days ago I've found a custom control, where child contols were created and added to Controls collection in constructor. Certainly, that isn't good because Controls property and AddedControl method are virtual. But - is that the only reason why we must not add child controls in constructor?

  • Alexander -- I'd at least create them from CreateChildControls and call EnsureChildControls from the constructor. There's no technical reason why you should avoid doing it that way though, other than the virtual wierdness you are referring to.

  • Hi

    Just stumbled upon your blog yesterday, looking for information on dynamically creating controls. Some very excellent posts!

    I realised that what I was doing was better done with a repeater and a template. So far so good.

    I'm creating DropDownLists dynamically based on the user's choice in a first DDL. This DDL is static and the selected index determines the DataSource which determines the number of dynamically created DDLs.

    The problem occurs because DropDownList1_SelectedIndexChanged runs after OnInit/OnLoad. If I understand it correctly, the new selected index of the first DDL is not set until DropDownList1_SelectedIndexChanged is invoked, which means my DataSource for the repeater is not setup until after OnInit has run.

    Is the only solution to check in OnInit whether the DataSource is ready, skip setting up the DDLs and then call the method to set up the DDLs from DropDownList1_SelectedIndexChanged?

    Cheers and thanks for a great article series.
    Neil

  • Neil -- the selected index should be available in OnLoad (before the event).

    Try this -- always create the child DDLs from OnLoad, based on the SelectedIndex of the first DDL (forget about SelectedIndexChanged, you don't need it). The child DDLs are within a repeater, which has ViewState disabled.

    This means binding the repeater every request and therefore getting your data every request. As long as your data is relatively cheap to retrieve that should be better than using ViewState.

    But if you'd rather ViewState then just binding the repeater from SelectedIndexChanged should do the trick. You didn't really say whether it was working, just that you seem not to like the order of things?

  • Thanks for your reply. I see your points and it's working now. I realize I was doing a bit of unnecessary extra work.
    My only concern now is that the dynamically created DDLs will be too difficult to setup when they are created in the Repeater. I have to run through each DDL and setup their DataSources. The DataSources also depend on the Selected Index of the first DDL. I'm guessing this will have to be setup the hard way on every postback anyways. So I don't know "how many" controls and I only sort of know "what" controls. I know it's DDLs, but not what to DataBind them to. I'm in doubt whether I should still use the Repeater...

  • I still think you should use a repeater. You know you can set the DataSources of the child DDLs declaratively?



    <asp:DropDownList DataSource="" />

    GetDataSource would be a method you define in the code-behind (of at least 'protected' protection). The parameter will be the data item for the repeater at the time. If you can manage to squeeze it all in the expression then you wouldn't even need the extra function.

  • "Until next time.... part 5 will come much sooner than part 4 did, I promise." (c)

    So where it is? ;)

  • It's coming it's coming! I still have time to live up to that promise, I think. I've been working on something else lately,
    http://www.infinity88.com/images/sky/sky.html

  • The problem (wrong file is deleted) can also be avoided by simply deleting the file in event handler and reloading the page:
    Response.Redirect(Request.Path);

    Of course no message for the user can be displayed (at least without effort).

  • I just wanted to say thank you. Not only is the article excellent, but your responses to the questions throughout the series clear up the issues even more.

    Again. thanks!

  • Rodney -- try giving the GridView a specific ID, preventing it from assuming an automatically generated ID a 2nd time when the tab changes.

    Also -- you don't have to do everything so dynamic. Much of what you have in that dynamic code is always the same. Consider declaring the grid and everything about it that is always the same. Then create everything else dynamicaly, like your fields, etc. Might side-step the whole issue.... :)

  • Thanks Dave as usual.
    rod.

  • Dave,
    Could you see this semi-Dynamic GridView working as a UserControl like in the Part4SampleCode?

    In the UserControl Page_Load I would:
    1. Access any db table based on a condition
    2. Build the template columns from db fields
    3. Databind

    could this be a possible workflow?

    thanks,
    rod.

  • Rodney -- sure, in general creating a user control isn't any different than creating a page, if you're making it a self-contained system that doesn't require any data or method calls from the page its hosted on. If you do need to give the control data from the page, such as your tabindex or something, then make it a property on the control and allow the page to call databind when it wants the data reconstructed.

    uc1.SomeInformation = info;
    uc1.DataBind();

  • Yeah you could make it a property, or you could have a method like SetSelectedIndex or ClearSelection, either way.

    Keep in mind since you are rebinding the grid each time you may be wasting viewstate. Normally I'd just say you should disable viewstate on the gridview, but I don't think its selected and edit indexes work without it... not sure... try it. If not then you should only perform the databinding and template construction when the view changes, so you are at least utilizing the viewstate you are storing.

  • Hi Dave,

    Scenario: Multi-tab UI with different info/modules presented on each tab. Tabs are dynamically generated depending on user action (e.g., selecting menu command which exists outside of any tab or clicking on an edit link in a list from a generated tab). My initial approach was to create a user control to encapsulate each info/module. On user action, I create a tab, then Page.LoadControl the associated user control to embed it within the associated pageview of the tab. The problem is that the loaded control disappears upon postback. I can re-load the user controls on postback but I have to maintain the state of the user controls. For example, think of a data entry module that is loaded onto one of the tabs. User enters information but keeps the tab "open". User then selects a menu command which posts back and launches a new module in a new tab (which is set as the currently active tab). User switches back to the previous data entry tab. This tab should maintain any info that was previously entered by the user. Any thoughts?

  • watson -- as long as you are loading the 'module' and putting it in the control tree, it doesnt matter whether it is visible or not, it will maintain its state perfectly. So as long as a tab is active in the sense that the user may not be done with what is on it, you should be loading the control that belongs to it. Its just that the 'active' tab is the only one whose module is actually visible.

  • Rodney -- I assume ViewState isn't turned off? When you say it won't persist what do you mean exactly... does it do anything or is clicking the button just causing a postback with no observable effect?

    Don't apologize I do enjoy helping people... I only regret that I'm not more responsive. I will eventually answer most inquiries, even if its weeks later.

  • Thanks Dave for your patience and efforts...
    When I click on the button to show the calendar that part will work. However, it's when i click the button again to hide the calendar is what's wrong. The calendar doesn't disappear from that point on.

  • Great post(s). Long time ago passed from Part 4...when we can expect Part 5 which covers server controls?

  • Stephan --

    ItemCreated is called on every postback, even when you aren't databinding again. So on a postback you'll end up creating the UC and then setting its text to something not from the data, since the data isn't available anymore.

    What you want to do is either just put the UC in the ItemTemplate statically, or break it up and use the ItemDataBound event. You create the UC from ItemCreated, but you only assign its public property from the ItemDataBound event (alternatively you could hook into the DataBinding event from ItemCreated, and set it from the handler).

    As for your second question... yes I recommend you retrieve all the data and rebind!

  • Andy --

    These are all excellent questions. So excellent in fact I plan on a blog entry to answer some of them, as soon as I can...

    To give you a quick answer to #5... you can't find it, because that's not where it happens. The framework builds methods on the fly when it compiles the markup, and they are what assign the property values, which is even before OnInit.

  • Michael -- I suggest that when one of these operations are needed (adding, deleting, etc) and you want to save data already entered by the user, you first 'save' the data -- but not the database. You probably have a method already that saves the data to the database by basically copying it directly from the form to the database. Rather than that, refactor it into two parts. The 1st part creates an array of data items representing all the data (using a custom type or whatever), the 2nd part saves it to the database FROM that array of custom types. This provides a clean separation of UI and business logic. And it also gives you for free a way to save the existing data TEMPORARILY. So when the user wants to add/delete, etc, you first save it into the array, using the 1st part of the two methods I described, then you manipulate the array, then re-bind it to the repeater. No need to save anything in session.

  • I sent a message to you just the other day, but I'll make this one public.
    I really only want to do a simple thing:)

    I have and update panel with a placeholder inside. I Have a button outside the UdPanel that acts as an AsyncPBack Trigger.

    I have designed a UserControl (a ) which contains a few DDLs and Text boxes. It also contains a button called "Remove".

    The idea is that clicking "add" adds rows dynamically and obviously, Remove, removes the row from the PlaceHolder.

    Adding works to an extent but seems to muck up the Control hierarcy. Removing also does this. Data appears in the wrong controls etc. A control exists on the page when it shouldn't etc.

    Added to this, I wanted to have OnSelectedIndexChanged events working on the DDLs in the UserControl.

    Its quite annoying. I have done this with Javascript but I wanted to use server controls to simplify the posting of data plus I wanted partial postbacks to make it quicker.

    Anyone have any ideas?

  • ezrider -- they key is to make sure the controls are added each request. For example if you click add, and then do an async postback via some other button, the control should remain there. It won't be if you aren't adding it, despite the add button not being clicked the 2nd time. But that's not the only thing you would need to worry about. You are probably getting 'messed up' data/hiearchy because you're getting a shifting-id problem, as explained in this article. If you have 3 rows and you remove the 2nd one, the 3rd row is going to have its IDs shift. You can solve that using the techniques I outlined. The simplest way for you may be to just make removed rows invisible rather than actually remove them.

    Since you are adding the same control each time, it would be much cleaner if you just used a repeater:







    You would databind to this repeater a collection of data items that could contain data required by the user controls (or just a blank array if you only care about the number of them). The tricky part is that you must rebind the repeater whenever you add or remove an item, so you must be able to re-create the collection from the repeater's items, in order to preserve the data the user has already put into it so far.

  • Hey Thanks mate!

    My "add row" functions were working fine. It was really only the shifting Ids problem with the Remove that was giving me a head ache.

    I'm not sure how I would solve that problem despite the solutions in your article( I had problems with NamingContainer and fixed id names).

    I had written a custom object that has the same structure as the control, allowing the values in each row(filter clauses) to be stored in the DB and then recreated when a query was loaded for editing.

    Since I already have a Collection of objects maintaining the row values, the repeater approach would be best. It certainly looks cleaner!

    I really appreciate your helping me. Great Blag too.

  • hi.
    I was just reading through ur articles. It all good, but I think I need the #5 for my problem.
    I'm trying to create dynamic "multi row edit" gridview with template. Why do I want it ? cause I would use the gridview as main source for lookup data for user.
    well, of course most of it I knew what columns that I would need, but I would need the same grid view to look up different data (which of course had lots of different columns on size and format).
    And i was thinking to even take it further to make some of the columns editable (and it is multirow in a sense).

    I have successfully created it to be loadable, and editable.
    what I need now, I wanted to get the data after the user edit it, while the controls would always gone after the postback.
    what should I do to retrieve the data from the dynamically made columns on gridview ?

  • Fire -- columns are a StateManagedCollection. That means if you dynamically add or remove a column, it will persist that way on its own without you having to redo that operation on a postback. So I'm not really following what your problem is. You say you want to get the data the user entered, but the controls aren't recreated? They should be created by the grid view automatically based on the columns and their templates. Perhaps you are rebinding it or something?

  • I have solved it already.
    So, it seems it was just my misunderstanding of asp.net state.
    After reading ur viewstate article, and discussing with my friend, I have solved the problem.
    So, dynamically created column template should always be recreated on gridview_init event (and the cool thing is their value persisted cause of the magic viewstate from the .net framework).

    after, that, then I can access the data following the postback event because the controls had been recreated from the init event.

    so, things are good for now.. Still perfect-ing my custom gridview though..
    yeah, I know that u and some people here said (DONT USE CUSTOM CONTROL)...... :P
    but this custom gridview would helped me alot on my projects cause I would definitely need it.

    thx anyway for the great article :)

  • hi infinities,

    no answer for the 1st question yet ?
    as for the 2nd question, sorry, again, it's part of my fault of my (still) misunderstanding of viewstate ordering process.

    thx
    regards,

    Fire.

  • Fire -- the control is kind of confusing! The format text is set in so many different places. If you format the text once, then submit again, you're going to end up trying to format it twice. I'm not sure if the Parsing will work or not. Ideally you'd just output the formatted text from Render or AddAttributesToRender instead of actually CHANGING the text value. I wish I could help more than that at the moment but I gotta go! We might be having a baby today...

  • Hi,

    Your articles are so in-depth and have really pulled through for me, great work.

    Could I get some pointers on using OnLoad and OnInit, I've read your onload-vs-page-load-vs-load-event article.

    My Server Control consists of a GridView which is built in the OnInit() method. However the databinding for the gridview is called from the OnLoad() method which in turn calls a method RunQuery();

    RunQuery() returns a DataTable which is used by a method named BindData() to bind the gridview but also create a Table using the DataTable column names for the headings and a TextBox control for each gridview column.

    As RunQuery() will need to be called multiple times, it will return different sets of data, I'm clearing the Table and recreating the Table Header and Rows. However this occurs on every postback due to the RunQuery method being called from OnLoad()

    Sorry if this seems a bit incoherent, I've probably confused my self ;-)

    Just read your comment about having a baby, all the best and take care.

    Regards,

  • I have a rather odd problem

    I am loading an external (skin template) and applying it to my server control when I dynamicly load it onto my page.

    ( it's a plugin based site editor, I can't use static controls )

    The wierd thing that is happening is that all of the controls will not persist thier state unless they are visible on the page, meaning if I have them in a multiview in my template file, only the visible controls persist thier values, from post back to post back event.




    protected override void OnInit(EventArgs e)
    {
    this.EnsureChildControls();
    base.OnInit(e);
    }

    protected override void CreateChildControls()
    {
    if (!_skinLoaded)
    {
    _skinLoaded = LoadDefaultThemedControl();
    }

    if (_skinLoaded)
    AttachChildControls();

    }


    protected virtual bool LoadDefaultThemedControl()
    {

    if (this.Page != null && this.DefaultSkinFileExists)
    {

    Control defaultSkin = this.LoadControl(this.DefaultSkinPath);
    defaultSkin.ID = "_";
    this.Controls.Add(defaultSkin);

    return true;
    }

    return false;

    }


    In my server control class I am finding the controls and wireing them up prior to the host controls onInit so the order should not be screwing me up, additionally the controls do work when visible ???

    I am not really sure, but I am sure it is something stupid I am doing :)

    Any help would be greatly appreciated.


  • You know after 5 hours of looking 5 minutes after I submited my question I figured out that I failed to specify the ID of my host control. :-(

    Well the good news is it works just like it should and I thought it would, the bad news is I wasted 5 hours :)

    Oh well I am just glad I found it.

  • I've read these articles and you seem like the best person to ask for this.

    I've got a web page that holds a user control, which is really a factory that might load up one of several different user controls inside it. The web page doesn't really know what kind of control is getting loaded, and it doesn't care. It just passes the data in to the factory/container control and trusts that guy to load up the right control for the content provided.

    This all works great, and I've used it in multiple projects. But I've always just had the child controls hold literals. They never held any interactive controls like a textbox. And now, I need them to do that, and I've discovered something that doesn't work the way I expected.

    I'll post the code below, but basically, here's what's happening:

    The default page has a textbox, a button, and a factory control. You enter text into the textbox and hit the button.

    What should happen is that the page reloads. Then the page takes the text from the textbox and hands it to the factory control. The factory control loads the child control, and passes that text to the child control to display. The child control then displays that text twice - once in a textbox and once in a literal.

    If you set a breakpoint in the child control, you can even see that the textbox.text shows the correct value. BUT, when the page loads, only the literal will actually display the value. The textbox itself is blank. Why is it doing that?

    Here's the code:

    ------------------------------------------------------------------------------------------------------------














    ------------------------------------------------------------------------------------------------------------
    using System;

    namespace Dirtbox
    {
    public partial class _Default : System.Web.UI.Page
    {
    protected void Page_Load(object sender, EventArgs e)
    {
    containerControl.PutStuffIntoUserControl(sourceText.Text);
    }
    }
    }
    ------------------------------------------------------------------------------------------------------------
    ------------------------------------------------------------------------------------------------------------
    ------------------------------------------------------------------------------------------------------------


    ------------------------------------------------------------------------------------------------------------
    namespace Dirtbox
    {
    public partial class FactoryControl : System.Web.UI.UserControl
    {
    public void PutStuffIntoUserControl(string useThis)
    {
    MyUserControl subControl = (MyUserControl)LoadControl("~/MyUserControl.ascx");

    subControl.myText = useThis;

    subControl.PopulateInteriorControl();

    myPanel.Controls.Add(subControl);
    }
    }
    }
    ------------------------------------------------------------------------------------------------------------
    ------------------------------------------------------------------------------------------------------------
    ------------------------------------------------------------------------------------------------------------




    ------------------------------------------------------------------------------------------------------------
    namespace Dirtbox
    {
    public partial class MyUserControl : System.Web.UI.UserControl
    {
    public string myText;

    public void PopulateInteriorControl()
    {
    textWithinControl.Text = myText;
    literalWithinControl.Text = myText;
    }
    }
    }
    ------------------------------------------------------------------------------------------------------------

  • Thank you for all the time you put into this, helping us out with things that we can't really find in any book. You should write one, by the way:). Looking forward to your next articles.
    And congratulations for the baby!

  • Great series! It's answered alot of my questions about controls.

    One thing I've discovered in my recent testing is that if you add a control dynamiclly to a part of the control tree (in OnInit) that is further down the line, it doesn't play catch-up. Which, if you think about it, makes sense since as InitRecursive() works through the tree it will eventually get to that newly added control.

    The example I was working on had two content place holders on a master page. The first content place holder loaded several user controls onto the second content place holder control. I was expecting the dynamically loaded controls to init right away but they didn't.

    Sorry if this is a restatement of something in this series but I don't think it was there.

  • Jon -- yes, if you manage to add a control to one that hasn't init'd yet, the added control won't suddenly init either, but will eventually when the normal init phase reaches it. The 'catch up' feature only comes into play if the parent you are adding the control to has already been through a phase.

  • Cristian -- my article on understanding dynamic controls by example might help. It sounds like you have the right idea. You just need to remove the old control when moving on to the next one, with Controls.Remove or Controls.Clear. You also then should give the control you are loading an ID based on something short and concise in the db, such that each possible control will have a different ID but one that is always the same. This avoids the 'shifting id' problem I've talked about.

  • Hi, thanks for the quick reply. There seems to be an issue when using this type of functionality with Ajax. Turning off ajax makes everything work as expected but with ajax, my labels(Literals thanks to another one of your article saved me loads of time on css) don't render upon an async postback. The other controls that have id's such as textboxes render properly....well except validator controls which also don't seem to work. I tried giving them unique id's such as lblMyTitle + new Random().Next(100) but didn't help. Any ideas.

    In my page, i am removing the user control where the questions are created using the Controls.Clear -- controls is mapped back to a placeholder control. After Clear i'm calling a LoadControl to load back that user control which should now have the new set of controls.

    Cristian

  • Cristian -- I'd have to see code or a minimized version of the code. Simply turning on ajax shouldnt cause problems like that if everything is done correctly. By 'turn off' I assume you mean setting EnablePartialRendering="false"? could you provide more details?

  • Hi Dave,
    Man, you are still getting hits and I'm not surprised. Hey, I just wanted to run something by you and get your take on it.
    I'm beginning to understand when you said that a lot of the times when people want dynamic controls the solution doesn't really require the degree of dynamic control one thinks (if that makes any sense).
    anyways, i'm taking advantage of the ITemplate interface by creating my own class and implementing the interface. and my idea is that i load an xml file to determine the presentation of the fields. what do you think about that idea?
    for example, as the dynamic template is being created, there's this xml file pre-loaded keyed by fieldname. And when it's that fields turn to go and look up the instructions in the xml file like whether it's a textbox in edit mode, if it needs a format string {0:d} for dates, if it needs javascript appended? stuff like that.
    thanks,
    rodchar

  • rodchar -- sure, could be useful as a generic form generation control. But be careful that you aren't actually reinventing asp.net markup with your xml. In other words, why create custom XML, and a custom parser, when the output is simply a control tree -- why not let the xml be actual asp.net markup, and your 'parser' is simply LoadControl()? Remember you can load an ascx file and use it as an ITemplate with LoadTemplate.

  • dude! that was good read.

    i've been struggling with loading a custom web control through an XML config file.

    i've tried a ton of stuff, but just can't stuff to work right. you clued me in on a few important points that i will attempt.

    i'll let you know how it goes...if you're interested that is...i'm sure you get slammed with more questions than you care to talk about.

    at any rate....

    THANKS!!!!

    -
    cT

  • Can I pick your brain for a minute?

    I have a number of the same usercontrol(which consists of a checkbox and an image) that I need to load at runtime. I declare a default control(of the same usercontrol type) in the .aspx page initially because the default always needs to be there.

    By default, the default control is checked and once all the other controls are loaded, if the user clicks one or more of the loaded user controls, the default control should uncheck, etc.....on the same note, if none of the dynamically loaded controls are checked, the default should check, etc...

    I almost have this working but I can't quite get the functionality I've described above. I suspect I have the event handlers wired up wrong or not implemented the correct way.

    The reason I feel I have to do it like this, is that if we declare all of the control types in the .aspx page, we have to republish the site code...yuck! So, I have an XML file that describes the control and I load them dynamically....that way we can add or remove from the XML file as we please.

    At the moment, I load all of the controls(including the default control) in a panel/placeholder.

    Can you possibly shed some light for me and/or is there a better way to approach this?

    Thanks a lot...I appreciate it!

    -
    cT

  • CrazyTasty --- hilarious name, by the way. Its making me hungry every time I see one of your comments!

    Anyway -- sounds like you're on the right track. I dont know exactly what is wrong without more detail. The way I imagine doing it is having a helper method like SetCheckedImage(Control) where the passed in control is the one that should be checked. The method would first enumerate all the loaded controls and set their checkboxes to false, and then finally set the passed-in control to true. If the passed in control was null then it sets the default control to checked.

    Then, in the user control, whatever it is that causes a postback (checkbox autopostback?), calls that method passing itself when the checkbox changes to true, and null when it changes to false.

    Theres probably a faster way to do it -- this will mean enumerating all the controls whenever anything happens, but if you only have a dozen or so of them I doubt its worth trying to optimize too much.

  • Thanks man. I will try a few things tomorrow when I go in and let you know.

    -
    cT

    p.s. you remember the commercial product that had the tagline, Crazy Tasty!!! ...don't you? :-)

  • Bloody vikings!

  • Thanks for this article.Its an excellent article on Dynamic Controls.

    But i have some trouble in working with dynamic controls.Here is the situation I have one dynamic Web user control named WebUserControl1.ascx.Its type name is same (i.e WebUserControl1) in the code behind file and its public , but when i want to access this type in default.aspx page then compiler gives error : "Type or namespace WebUserControl1 could not be found.are you missing an assembly reference?"

    Why i am not getting this public type in default.aspx while both user control and default page are on the same level in web site and both have no namespace(means no namespace is specified for both).

    Once i tried to access this type using ASP.webusercontrol_ascx , at one time this worked but at other time it does not work.What can be the problem with this .Remember i am working using VS.NET 2008

  • He Zee,

    I had this problem, but when I put the


    at the top of my page, everything works fine. If you put it at the top you can use it in the code behind as a typed control and access it's properties. Well maybe you should cast it first.


  • Hi,

    Thanks again for the update. Sorry i've been away for a while and i just saw your message.
    I have attached some my code so you can see what i'm talking about.
    By taking ajax off, i meant literally removing the update panels and all that.

    Here's a smaller version of the code i'm using.
    ////////////////////////////////////////////////////////////////////////////
    protected void Page_Load(object sender, EventArgs e)
    {
    ((HtmlGenericControl)Master.FindControl("SiteMasterBodyTag")).Attributes.Add("onunload", "javascript:__doPostBack('PostBackLeavingPage', '');");
    BindRepeater();
    //set validation group of the currently loaded section
    lnkContinue.ValidationGroup = "Section" + loadSectionID + "Validation";
    LoadQSControl(loadSectionID);
    }

    //this is not working that great
    protected void Page_SaveStateComplete(object sender, EventArgs e)
    {
    if (IsPostBack)
    {
    object eventTarget = Request["__EVENTTARGET"];
    object eventArgument = Request["__EVENTARGUMENT"];
    if (eventTarget != null && eventTarget.ToString().Trim().Equals("PostBackLeavingPage"))
    {
    SectionQuestions sq = (SectionQuestions)QuestionsPlaceHolder.Controls[0];
    //sq.saveAnswers(false);
    }
    }
    }

    private void LoadQSControl(byte sectionID)
    {
    lnkContinue.ValidationGroup = "Section" + sectionID + "Validation";
    SectionQuestions control = (SectionQuestions)LoadControl("/profile/controls/SectionQuestions.ascx");
    control.ID = "SQ" + sectionID;
    control.sectionID = sectionID;

    QuestionsPlaceHolder.Controls.Clear();
    QuestionsPlaceHolder.Controls.Add(control);

    }

    protected void listSections_ItemCommand(object source, DataListCommandEventArgs e)
    {
    if (e.CommandName.Equals("GoToSection"))
    {
    byte sectionID = Convert.ToByte(e.CommandArgument);
    lnkContinue.ValidationGroup = "Section" + sectionID + "Validation";
    LoadQSControl(sectionID);
    }
    }



    ////////////////////////////////IN THE CONTROL ITSELF///////////////////////////////////
    protected void Page_Init(object sender, EventArgs e)
    {
    if (sectionID.Equals(0)) return;
    RenderControls();
    }

    //RenderControls simply does a Controls.Add of the various controls such as checkbox, textbox etc.
    //cutoff version of the saveMethod.
    public void saveAnswers(bool continueToNextSection)
    {
    ArrayList relatedQuestions = new ArrayList();
    if (Controls.Count != 0)
    {
    for (int i = 0; i < Controls.Count; i++)
    {
    if (Controls[i].ID != null)
    {
    if (Controls[i].ID.Contains("AnsForQID"))
    {
    short questionID = Convert.ToInt16(Controls[i].ID.Replace("AnsForQID", ""));
    UserAnswersCollection uaColl = new UserAnswersCollection();
    if (uac.Count != 0)
    {
    UserAnswersCollection.RemovePreviousAnswers(accountID, questionID);
    }
    string typeName = Controls[i].GetType().Name;
    switch (typeName)
    {
    case ("RadioButtonList"):
    RadioButtonList radList = Controls[i] as RadioButtonList;
    AddUAEntityToColl(uaColl, radList.SelectedValue, accountID, questionID,false);
    break;
    case ("DropDownList"):
    DropDownList ddList = Controls[i] as DropDownList;
    AddUAEntityToColl(uaColl, ddList.SelectedValue, accountID, questionID,false);
    break;
    }
    }
    }
    }
    }
    }
    //////////////////////////////////////////////////////////////////

  • I am not very experienced with .net.

    I want to build a form where the meta data in the datbase dictates whether to display a checkbox, radio button, textfield, text area or select box? The exact labels/questions and the number of each type of elements is determined based on the specific survey or question set.

    Does this have to be done using dynamic controls? Can this be done using repeaters/templates as described above?

    Mike

  • Great article! In the case of sharepoint, specifically an editor for a web part, is it possible to load a .ascx control within that control? There is no page file; only .cs files.
    Consider the following: I have a web part that has a variable number of properties that depend on a couple of other properties that are alway present.

    Very similar to the property grid in visual studio. In visual studio when you focus on an item, the property grid binds to that object and dynamically populates the grid based on the items properties. Some of these properties are complex and require the use of type converters.

    In sharepoint when you develop a web part and the properties of the web part are not simple types (string, int, etc.) you often times need to create an editor. The editor has a method named CreateChildControls. This is where you typically and dynamically would add your controls using Control.Add(myControl). I would like to convert my dynamic model to a templated model that a .ascx control would provide. By seperating the GUI and backend code it would provide me with a model that is easier to maintain, the ability to use databinding, and better unit testing. What is your recommeded approach?

    ~Ted

  • Great article very helpful.
    I'm looking to for a way to solve the following problem without using dynamic controls but just can not figure it out.
    I have a form for editing multiple XML node groups at once, each node group will be edited using a customised user control. My problem is that the frequency and type of node groups can vary, there can be 1 to n and of any type of node group.

  • Hi...HOw to create multiple rows and columns of textboxes and enter the values in the database....

  • Good stuff!! Eagerly awaiting part 5...

  • Hello. Really great post. Helped me learn a little more about how much I don't know :) I understand that this post has been up for a few years and that you may have moved on, but just incase you are still responding to posts, I think I have a question that is different from any of the previous posts. So, here it goes:

    I am working on a simple messenging service for a web system. I currently have the message controls in an update panel on the master page. This allows me to call a master.DisplayMessage("title", "body") method to show dynamic messages based on the specific situation. This works great, however, I would like to extend this control to be able to prompt users and allow the code to react as appropriate.

    For example, (the system is for registering students for classes) an administrator could be registering a student for a class for which they do not meet the prerequisite requirements. In this case, I would like to display the message prompting the user "Would you like to override these requirements?" and react their response.

    So, based on the advice of the 4th article, I decided to create the necessary buttons on the master page and show/hide them as necessary. I also created event handlers for each button on the code file for the master page. Using this method, before I call DisplayMessage(...), I set master.OnYesButtonClicked += yada_yada (to play on the earlier Seinfield comment). However, as I am sure you can guess, when the page reloads, the OnYesButtonClicked event handler is null.

    Can you please suggest what you think might be a better way to handle this? I feel like I am an in an infinant loop here.

    Thanks,
    Vinney

  • Very interesting article, providing an insight into the asp.net workings. I from my part, however, follow a simple approach that seems to work fine with dynamic controls. I simply don't access their values programmatically but through the Request.Params collection. If necessary, a recreate the controls by passing the Request.Params value to the routine creating them, instead of for example the original db value.
    Regards Nick

  • A very enlightning to a newbie for Dynamic controls like me...

  • Thanks very much for your article.
    Carefully reading through parts 1-4 have enlighened me in many areas - much of it about "approach".

    The info about Controls.Add() playing "catch up" (if rooted) on earlier events was very insightful.

    As I say, I've managed to build a Templated Control with Template Items for inserting "parts" of the template, but driven by the datasource. Had problems with bindings on postback but your article has helped me through.

    Cheers,
    Chris.

  • Hello,

    Thanks again for this valuable article. I finally solved all the ViewState problem :)

    Just in case somebody is looking for an online working example of deeply nested dynamic controls, here it is :
    http://www.manitra.net/blog/dev/csharp/how-to-create-deeply-nested-asp-net-dynamic-controls/

    Manitra.

  • this series was a huge help! now i (somewhat) understand asp.net :D

    thank you!

  • Great article! I'm just starting out with Asp.Net, and this helped me a lot in understanding how things work. I hate books and articles that just describe how to use it, real developers want to know how it works :-)

Comments have been disabled for this content.