Wednesday, March 28, 2012

Dynamic Controls in ASP.NET AJAX context - Controls seem to be disappearing.

I have a Web Application that I am developing and I have run across aserious problem. Due to the fact that the Application entails a lot ofdetails and code I am not able to give an actual sample of the problemI am running into. However, I have created the same problem on asmaller scale in a temporary Application.

Theissue revolves around ASP.NET AJAX and its ability to track Dynamicallycreated Web Controls. On the sample Application below I have a Pagethat contains 8 Buttons. Whenever one of those Buttons is pressed thePage performs an Async PostBack which causes the Page Load Event toexecute followed by the Event Handler for the Button pressed. In theEvent Handler the Button gets removed from the Page. The desiredeffect is that when someone pressed a Button it will in turn remove theappropriate Web Control (Button) from the Page. Everything works finewith one very big exception: ASP.NET AJAX seems to have a problemkeeping track of the Controls on the page.

For example, if youcopy and paste the code at the bottom of this post into Visual Studioand press the Buttons in the following order you will notice someButtons simply disappearing:
2 - 7 - PostBack - 4 - 1 - PostBack

Onevery important part of this demonstration is that the Button is notonly removed but its also prevented from Rendering. If you take outthis line of code everything works fine but in my actual Applicationthe Control is not Rendered and the results match that of what happensin the temp Application.

So my question is, whats happening inthe background? It seems that removing the Control does not prevent itfrom being Rendered and if you manually prevent it from Rendering bysetting its Visible Property to False then ASP.NET gets completelythrown off as to whats going on.

I know its rather difficult toanswer a question like this because you can't simply provide analternative to the problem so any help is greatly appreciated. I amsimply trying to figure out whats going on in the background so I canfind a solution to my real Application.

Code Behind:

Partial Class _Default
Inherits System.Web.UI.Page

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

' Clearing any previous text.
label1.text = ""

' If its the first time loading the Page
If Not Page.IsPostBack Then
Page.Session.Add("AddButton1", "True")
Page.Session.Add("AddButton2", "True")
Page.Session.Add("AddButton3", "True")
Page.Session.Add("AddButton4", "True")
Page.Session.Add("AddButton5", "True")
Page.Session.Add("AddButton6", "True")
Page.Session.Add("AddButton7", "True")
Page.Session.Add("AddButton8", "True")

Page.Session.Add("RemovalTracker", "")

' Else the page is performing a PostBack
Else
label1.text = CStr(Page.Session.Item("RemovalTracker"))
End If

'Dynamically creating the Buttons and then adding them to the Page / Form.

If CBool(Page.Session.Item("AddButton1")) = True Then
Dim button1 As New Button
button1.Text = "Button1"
button1.ID = "Button1"
AddHandler button1.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button1)
End If

If CBool(Page.Session.Item("AddButton2")) = True Then
Dim button2 As New Button
button2.Text = "Button2"
button2.ID = "Button2"
AddHandler button2.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button2)
End If

If CBool(Page.Session.Item("AddButton3")) = True Then
Dim button3 As New Button
button3.Text = "Button3"
button3.ID = "Button3"
AddHandler button3.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button3)
End If

If CBool(Page.Session.Item("AddButton4")) = True Then
Dim button4 As New Button
button4.Text = "Button4"
button4.ID = "Button4"
AddHandler button4.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button4)
End If

If CBool(Page.Session.Item("AddButton5")) = True Then
Dim button5 As New Button
button5.Text = "Button5"
button5.ID = "Button5"
AddHandler button5.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button5)
End If

If CBool(Page.Session.Item("AddButton6")) = True Then
Dim button6 As New Button
button6.Text = "Button6"
button6.ID = "Button6"
AddHandler button6.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button6)
End If

If CBool(Page.Session.Item("AddButton7")) = True Then
Dim button7 As New Button
button7.Text = "Button7"
button7.ID = "Button7"
AddHandler button7.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button7)
End If

If CBool(Page.Session.Item("AddButton8")) = True Then
Dim button8 As New Button
button8.Text = "Button8"
button8.ID = "Button8"
AddHandler button8.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button8)
End If

End Sub

Protected Sub testSub(ByVal sender As Object, ByVal e As System.EventArgs)

' Appending the Button Text to SessionState.
Page.Session.Item("RemovalTracker") = Page.Session.Item("RemovalTracker") & " - " & CType(sender, Button).Text

' Building the ID for the Session Item that needs to be modified.
Dim buttonToRemove As String
buttonToRemove = "Add" & CType(sender, Button).ID
Session.Item(buttonToRemove) = "False"

' Preventing the Buttom from Rendering - this is CRUCIAL to my demonstration.
CType(sender, Button).Visible = False

Page.Form.Controls.Remove(CType(sender, Button))

End Sub

End Class

Default.aspx Page:
<%@dotnet.itags.org. Page Language="VB" AutoEventWireup="true" CodeFile="Default.aspx.vb" Inherits="_Default" %
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body
<form id="form1" runat="server">

<asp:ScriptManager ID="ScriptManager1" runat="server" />

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<br />
<asp:Label ID="Label1" runat="server" Text=""></asp:Label>
<br />
<br />
<br />
<asp:Button ID="ButtonPostBack" runat="server" Text="PostBack" />
<br />
<br />
</ContentTemplate>
</asp:UpdatePanel
</form>

</body>
</html>

After 3+ days of brainstorming, testing, debugging and everything I could throw at ASP.NET I have found a solution.

Theproblem lies not within ViewState, not with ASP.NET's Control Tree (nottechnically), not within the Code itself but in how ASP.NET handlesdynamic Controls in regards to their ID's.

If I modify my codeabove as demonstrated below everything works just fine. You wouldthink that incrementing the ID's instead of assinging the same ID's tothe same Controls would do nothing but for some bizarre reason thatshow it works.

My problem in my real Application is very similiarto this problem and I have in fact solved my problem. Its funny how ifI had been able to pin-point this problem several days ago I would beway beyond this. Oh well, thats ASP.NET.

ASPX Page:
<%@. Page Language="VB" AutoEventWireup="true" CodeFile="Default.aspx.vb" Inherits="_Default" %
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
</head>
<body
<form id="form1" runat="server">

<asp:ScriptManager ID="ScriptManager1" runat="server" />

<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<br />
<asp:Label ID="Label1" runat="server" Text=""></asp:Label>
<br />
<br />
<br />
<asp:Button ID="ButtonPostBack" runat="server" Text="PostBack" />
<br />
<br />
</ContentTemplate>
</asp:UpdatePanel
</form>

</body>
</html
Code Behind:
Partial Class _Default
Inherits System.Web.UI.Page

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

' Clearing any previous text.
label1.text = ""

Dim counter As Integer = 0

' If its the first time loading the Page
If Not Page.IsPostBack Then
Page.Session.Add("AddButton1", "True")
Page.Session.Add("AddButton2", "True")
Page.Session.Add("AddButton3", "True")
Page.Session.Add("AddButton4", "True")
Page.Session.Add("AddButton5", "True")
Page.Session.Add("AddButton6", "True")
Page.Session.Add("AddButton7", "True")
Page.Session.Add("AddButton8", "True")

Page.Session.Add("RemovalTracker", "")

' Else the page is performing a PostBack
Else
label1.text = CStr(Page.Session.Item("RemovalTracker")

)
End If

'Dynamically creating the Buttons and then adding them to the Page / Form.

If CBool(Page.Session.Item("AddButton1")) = True Then
Dim button1 As New Button
button1.Text = "Button1"
button1.ID = counter.ToString
counter += 1
AddHandler button1.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button1)
End If

If CBool(Page.Session.Item("AddButton2")) = True Then
Dim button2 As New Button
button2.Text = "Button2"
button2.ID = counter.ToString
counter += 1
AddHandler button2.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button2)
End If

If CBool(Page.Session.Item("AddButton3")) = True Then
Dim button3 As New Button
button3.Text = "Button3"
button3.ID = counter.ToString
counter += 1
AddHandler button3.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button3)
End If

If CBool(Page.Session.Item("AddButton4")) = True Then
Dim button4 As New Button
button4.Text = "Button4"
button4.ID = counter.ToString
counter += 1
AddHandler button4.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button4)
End If

If CBool(Page.Session.Item("AddButton5")) = True Then
Dim button5 As New Button
button5.Text = "Button5"
button5.ID = counter.ToString
counter += 1
AddHandler button5.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button5)
End If

If CBool(Page.Session.Item("AddButton6")) = True Then
Dim button6 As New Button
button6.Text = "Button6"
button6.ID = counter.ToString
counter += 1
AddHandler button6.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button6)
End If

If CBool(Page.Session.Item("AddButton7")) = True Then
Dim button7 As New Button
button7.Text = "Button7"
button7.ID = counter.ToString
counter += 1
AddHandler button7.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button7)
End If

If CBool(Page.Session.Item("AddButton8")) = True Then
Dim button8 As New Button
button8.Text = "Button8"
button8.ID = counter.ToString
counter += 1
AddHandler button8.Click, AddressOf testSub
UpdatePanel1.ContentTemplateContainer.Controls.Add(button8)
End If

End Sub

Protected Sub testSub(ByVal sender As Object, ByVal e As System.EventArgs)

' Appending the Button Text to SessionState.
Page.Session.Item("RemovalTracker") = Page.Session.Item("RemovalTracker") & " - " & CType(sender, Button).Text

' Building the ID for the Session Item that needs to be modified.
Dim buttonToRemove As String
buttonToRemove = "Add" & CType(sender, Button).ID
Session.Item(buttonToRemove) = "False"

' Preventing the Buttom from Rendering - this is CRUCIAL to my demonstration.
CType(sender, Button).Visible = False

Page.Form.Controls.Remove(CType(sender, Button))

End Sub

No comments:

Post a Comment