Adding controls to an UpdatePanel through code
[Update: Thanks to Cyril, I've update the code to use the new AsyncPostbackSourceElementID]
In the final version the UpdatePanel has a new cool property (ContentTemplateContainer) which really helps you if you want to add controls to an UpdatePanel through code.
Lets illustrate the usage of this property with a simple page that changes the content of an UpdatePanel when the user clicks on a button. Let's start by presenting the 2 user controls used in this example:
control1.ascx
<%@ Control Language="C#" ClassName="control1" %>
<script runat="server">
void HandleClick1(object sender, EventArgs e)
{
lbl.Text = txt.Text;
}
</script>
This is user control 1.
Name: <asp:TextBox runat="server" id="txt" />
<asp:Button runat="server" id="bt" Text="Print name" OnClick="HandleClick1" />
<br />
<asp:Label runat="server" id="lbl" />
control2.ascx
<%@ Control Language="C#" ClassName="control2" %>
This is user control 2: <%= DateTime.Now.ToString() %>
<asp:Button runat="server" id="bt" Text="Refresh" />
page.aspx
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if( manager.IsInAsyncPostBack )
{
string info = manager.AsyncPostbackSourceElementID;
if( string.CompareOrdinal(info, bt.UniqueID) == 0)
{
CurrentControl = CurrentControl == "control1.ascx" ? "control2.ascx" : "control1.ascx"
}
string info = this.Request.Params[manager.UniqueID];
if( info.IndexOf("|") > 0 )
{
string ctlId = info.Substring(info.IndexOf("|") + 1);
if( string.CompareOrdinal(ctlId, bt.UniqueID) == 0)
{
CurrentControl = CurrentControl == "control1.ascx" ? "control2.ascx" : "control1.ascx"
}
}
}
LoadControl();
}
protected string CurrentControl
{
get
{
return ViewState["CurrentControl"] == null ? "control1.ascx" : (string)ViewState["CurrentControl"];
}
set
{
ViewState["CurrentControl"] = value;
}
}
void LoadControl()
{
Control ctl = LoadControl(CurrentControl);
panel1.ContentTemplateContainer.Controls.Clear();
panel1.ContentTemplateContainer.Controls.Add(ctl);
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager runat="server" id="manager" />
<asp:UpdatePanel runat="server" id="panel1">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="bt" />
</Triggers>
</asp:UpdatePanel>
<asp:Button runat="server" id="bt" Text="Change control" />
</form>
</body>
</html>
Yep, that's it. the tricky part is remembering that you must add dynamicaly generated controls until the end of the Load event or you might not get the expected results. Doing it on the Init event won't do you any good either because we're maintaining the current selected item on the viewstate (which is loaded after that event has fired). So, the Load even will be just fine for this.
Unfortunately, the ScriptManager control still doesn't have a property that let's you easily know the control that started an async postback. Fortunately, we do have Fiddler. with it, it's easy to see that there's a "secret" field that is being sent back to the server on the body of the request. This field is identified by the Id of the ScriptManager and you use it to get the ID of the control responsible for the postback:
manager=manager|bt&__EVENTTARGET=&__EVENTARGUMENT=&__VIEWSTATE=%2FwEPDwUJNzUzMjU3MTgzZGQ6YfDv2DwDFkdpUAnK5uj8PifsMg%3D%3D&ctl03$txt=&__EVENTVALIDATION=%2FwEWBAL7vsHoDgLMhYXhDQL46LbdBgK%2B79rvDBiOVPIc%2BzmCL7ifp3dWkDx8CElm&bt=Change%20control
And now everything looks easy, right? :)
I was wrong about this one. ScriptManager does expose a property (AsyncPostbackElementSourceID) which lets you get access to the control that started the partial postback. One thing you must keep in mind: if you handle the Init event (instead of the load event) you'll have to resort to the previous code which is striked out.