Dynamic controls, viewstate and postback

When using dynamic controls, many people will encounter all sorts of different problems that can take hours or days to solve. It is therefore essential to understand how asp.net handles events, postback and viewstate.

Dynamic control disappears on postback

If you have a user control which contains a text box, a literal control and a submit and you add the control programmatically, chances are that when the user control’s submit button is clicked, the user control will disappear instead of showing the value of the textbox in the literal. This happens because the user control (.ascx) is hosted by the page (.aspx) and the parent page does know of its existence unless you have used the declarative syntax in your markup. Here’s the html forĀ  a page called Slave.aspx


<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="~/MasterPage.master" EnableViewState="true" CodeFile="Slave.aspx.cs" Inherits="Slave" %>
<%@ Register Src="~/FirstControl.ascx" TagName="FirstControl" TagPrefix="gis" %>
<%@ Reference Control="~/WebUserControl.ascx" %>
<asp:Content ID="content1" ContentPlaceHolderID="ContentPlaceHolder1" runat="server">
<hr />
<h2>slave page</h2>
For the slave...
<gis:FirstControl ID="FirstControl" runat="server" />
<asp:HiddenField ID="hf" Value="0" runat="server" />
<asp:PlaceHolder ID="ControlHolder" runat="server" />
<hr />
</asp:Content>

<gis:FirstControl ID=”FirstControl” runat=”server” /> is the declarative syntax here but that’s not dynamic loading. FirstControl has a button and a checkbox and when the checkbox is checked and the submit button clicked, it will load dynamically another user control, in this case, WebUserControl.ascx. Below is the markup for FirstControl.ascx:


<%@ Control Language="C#" AutoEventWireup="true" CodeFile="FirstControl.ascx.cs" Inherits="FirstControl" %>
<<%@ Reference Control="~/WebUserControl.ascx" %>
<hr />
<h3>First Control</h3>
<asp:CheckBox ID="chkMaster" runat="server" />&nbsp;&nbsp;<asp:Button ID="btnFirstSubmit" Text="First Submit" OnClick="btnFirstSubmit_Click" runat="server" />
<hr />

And here’s the code behind for FirstControl.ascx:


protected void btnFirstSubmit_Click(object sender, EventArgs e)
 {
 if (chkMaster.Checked)
 {
 // show the other control
 WebUserControl wuc = (WebUserControl)LoadControl(typeof(ASP.gis), null);
 PlaceHolder holder = (PlaceHolder)FindControlRecursive(Page, "ControlHolder");
 holder.Controls.Add(wuc);
 }
 }

When the submit button on the FirstControl.ascx (provided the checkbox is checked), the user control will dynamically load the WebUserControl.ascx which contains a textbox, a submit button and a literal to print out the name in th e textbox. The WebUserControl is loaded properly but when its submit button is clicked, it will disappear on postback.

Solution: The reason for this is that the parent page (Slave.aspx) does not know of its existence. Therefore we need to make the page aware of it. When you load a dynamic control, you need to keep track of it, whether through ViewState or Hidden Form Field. I’ve used a hidden form field in this instance and here’s the code in the FirstControl.ascx file:


protected void btnFirstSubmit_Click(object sender, EventArgs e)
 {
 if (chkMaster.Checked)
 {
 // show the other control
 WebUserControl wuc = (WebUserControl)LoadControl("~/WebUserControl.ascx");
 wuc.ID = "wuc";
 PlaceHolder holder = (PlaceHolder)FindControlRecursive(Page, "ControlHolder");
 holder.Controls.Add(wuc);

 HiddenField hf = (HiddenField)FindControlRecursive(Page, "hf");
 hf.Value = "1";
 }
 }

Important thing to note here is that we need to make sure that we assign a unique ID to the dynamic control. And here’s the code behind for Slave.aspx:


protected void Page_Load(object sender, EventArgs e)
 {
 if (hf.Value == "1")
 {
 WebUserControl wuc = (WebUserControl)LoadControl("~/WebUserControl.ascx");
 wuc.ID = "wuc";
 ControlHolder.Controls.Add(wuc);
 }
 }

So to prevent the dynamic control from disappearing, you need to keep track of whether you’ve added it or not and load it if you have.

Loading a dynamic user control without specifying the path

If we want to load our control when some action is performed and not have it sit there and go through the asp.net life cycle for no reason, then we need to add a reference to the user control in the parent page. Now LoadControl is the method we’re after and it has 2 overloads. The first one requires a path to the user control and that works flawlessly. The second one takes a strong type and a parameterised object.

Casting the control to the right type

If you have properties on the control, you will need to cast it to the right type. Otherwise your properties won’t be available to you. The following code does not give me the intellisense for the public properties on WebUserControl because it’s been casted as a generic UserControl.

UserControl myUserControl = (UserControl)LoadControl(“~/WebUserControl.ascx”);

To access these public properties, you will need the following code:

WebUserControl myUserControl = (WebUserControl)LoadControl(“~/WebUserControl.ascx”);

Remember that you need to reference the WebUserControl in the parent page, otherwise you’ll get a compile time error:

<%@ Reference Control=”~/WebUserControl.ascx” %>

Dynamically loading the control by type

If you want to do the following, you will not see your control loaded:

WebUserControl myUserControl = (WebUserControl)LoadControl(typeof(WebUserControl), null);

This is because when you use the path to the user control, asp.net will fire off all the events that the control has missed until it catches with the current event. So if you’re adding a user control in the button click event, the init and load event have already passed, so the newly added control need to catch with the events until it is in sync. The workaround is to strongly type the user control and you do this by adding the ClassName attribute to the control’s markup as follows:


<%@ Control Language="C#" AutoEventWireup="true" ClassName="gis" CodeFile="WebUserControl.ascx.cs" Inherits="WebUserControl" %>
<hr />
<h3>Web User Control</h3>
<asp:Literal ID="litSomething" runat="server" /><br />
<asp:Literal ID="litText" runat="server" /><br />
<asp:TextBox ID="txtName" runat="server" />
<asp:Button ID="btnSubmit" Text="Submit from user control" runat="server" OnClick="btnSubmit_Click" />
<hr />

Notice the ClassName=”gis” in the code above. Now you would assume that you can do something like this:

WebUserControl myUserControl = (WebUserControl)LoadControl(typeof(gis), null);

But that won’t work because the ClassName “gis” cannot be found as it resides in the ASP namespace. Here’s how to do it properly:

WebUserControl myUserControl = (WebUserControl)LoadControl(typeof(ASP.gis), null);

You will get “System.MissingMethodException: Constructor on type ‘ASP.gis’ not found.” if you try to pass in parameters though:

WebUserControl wuc = (WebUserControl)LoadControl(typeof(ASP.gis), new object[] { “hey you” });

For some reason asp.net is unable to get the overloaded constructor for the user control even though it’s private. From what I’ve read, only the parameterless constructor is called.

Where to add dynamic controls? Page_Init, Page_Load?

You can add them whenever you want but if you don’t add it in Page_Init, the control will not be able to participate in ViewState as the loading of view state comes before page load.

Events available to User Control

You can use Init, Load and PreRender.

comments powered by Disqus