1

I have a VB ASP.NET web application with two User Controls each containing one text input. There are two submit buttons each corresponding to one of the User Controls.

Clicking a button adds an instance of its corresponding User Control. For the most part this works except that in a specific scenario the IDs of the textboxes get mixed up thereby mixing up previously entered values.

The problem scenario is as follows:

1) Click the second button (the Add Approver button) twice and enter some values in the two resulting textboxes (for ease of analysis make the values different).

2) Click the first button (the Add Document button) once. (There is no need to add any value in the resulting textbox here.)

At this point everything appears correct. Viewing the page source, I see that the two "Approver" textboxes have IDs of ctl02_txtApprover and ctl03_txtApprover and the one "Document" textbox has an ID of ctl04_txtDocument.

  1. Click the first button (the Add Document button) again.

At this point the value in the first "Approver" textbox disappears. The value in the second "Approver" textbox migrates to the first "Approver" textbox. Viewing the page source, the IDs for the two "Approver" textboxes have changed to ctl03_txtApprover and ctl04_txtApprover. The migrated values make sense considering that the textbox IDs have changed. In other words, the ViewState appears correct but the control IDs are incorrect.

I have made the code as simple as I can and have posted it here.

Default.aspx

<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="Default.aspx.vb" Inherits="WebApplicationUserControlTest._Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:PlaceHolder ID="phDocument" runat="server" />
        <asp:Button ID="btnAddDocument" runat="server" Text="Add Document" />
        <br /><br />
        <asp:PlaceHolder ID="phApprover" runat="server" />
        <asp:Button ID="btnAddApprover" runat="server" Text="Add Approver" />
    </form>
</body>
</html>

Default.aspx.vb

Public Class _Default
Inherits System.Web.UI.Page
Private Const VIEWSTATE_DOCUMENT_COUNT As String = "DocumentCount"
Private Const VIEWSTATE_APPROVER_COUNT As String = "ApproverCount"

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    If Not IsPostBack Then
        ViewState(VIEWSTATE_DOCUMENT_COUNT) = 0
        ViewState(VIEWSTATE_APPROVER_COUNT) = 0
    Else
        're-display any preexisting dynamic sections on postback
        AddAllDocumentInfoSections()
        AddAllApproverSections()
    End If
End Sub

Protected Sub btnAddDocument_Click(sender As Object, e As EventArgs) Handles btnAddDocument.Click
    ViewState(VIEWSTATE_DOCUMENT_COUNT) += 1
    AddDocumentSection()
End Sub

Protected Sub btnAddApprover_Click(sender As Object, e As EventArgs) Handles btnAddApprover.Click
    ViewState(VIEWSTATE_APPROVER_COUNT) += 1
    AddApproverSection()
End Sub

Private Sub AddAllDocumentInfoSections()
    For i As Integer = 0 To ViewState(VIEWSTATE_DOCUMENT_COUNT) - 1
        AddDocumentSection()
    Next
End Sub

Private Sub AddAllApproverSections()
    For i As Integer = 0 To ViewState(VIEWSTATE_APPROVER_COUNT) - 1
        AddApproverSection()
    Next
End Sub

Private Sub AddDocumentSection()
    Dim c As UserControl = LoadControl("~/Document.ascx")
    phDocument.Controls.Add(c)
End Sub

Private Sub AddApproverSection()
    Dim c As UserControl = LoadControl("~/Approver.ascx")
    phApprover.Controls.Add(c)
End Sub
End Class

Document.ascx

<%@ Control Language="vb" AutoEventWireup="false" CodeBehind="Document.ascx.vb" Inherits="WebApplicationUserControlTest.Document" %><asp:TextBox ID="txtDocument" runat="server" /><br /><br />

Approver.ascx

<%@ Control Language="vb" AutoEventWireup="false" CodeBehind="Approver.ascx.vb" Inherits="WebApplicationUserControlTest.Approver" %><asp:TextBox ID="txtApprover" runat="server" /><br /><br />

I am using Visual Studio 2010. The Target Framework is 4.0. I have tried changing the clientIDMode but this does not seem to make a difference. Have I run into a bug with .NET or is there something wrong with my code?

2 Answers 2

1

There is something wrong with your code.

If you dynamically add controls to the same naming container in a control tree, then you need to add them in the same order after each postback.

In your case, you're not doing this.

At your step 2, you have added three controls in this order:

  • Approver 1 (AddAllApproverSections)
  • Approver 2 (AddAllApproverSections)
  • DocumentInfo 1 (btnAddDocument_Click)

But then after the postback, you regenerate them in the following order:

  • DocumentInfo 1 (AddAllDocumentInfoSections)
  • Approver 1 (AddAllApproverSections)
  • Approver 2 (AddAllApproverSections)

Hence the control ids aren't the same, and the problems you're seeing.

One solution might be to store additional information in ViewState that represents the order the controls were added, so that you can recreate them in the same order.

But I'd probably be inclined to go for a different approach, for example put the DocumentInfo sections into the template of a Repeater, and the Approver sections into a second Repeater. Each Repeater would be data bound to a suitable collection, and adding an item (Approver or DocumentInfo) would be achieved by adding an element to the relevant collection and calling DataBind.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you Joe. The use of two Repeaters, one for each User Control seems to work. And looking at the page source it makes sense with the name of each textbox prefixed with the name of its Repeater. I had previously tried using two Panels for this same idea to no avail. I guess I need a better understanding of naming containers.
@Cienfuegos, a Panel isn't a naming container (doesn't implement the INamingContainer marker interface), so that's why your attempt didn't work. In this scenario, I think a Repeater is a better approach.
1

The problem here is that you are modifying the Controls collection and ViewState after they have been initialized. You should never dynamically add controls in the Page Load event.

You need to add your controls in the Page_Init stage of the Page life cycle, and remove the code from the else statement in your Page_Load event. Your new Page_Init event would look like this:

Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
    AddAllDocumentInfoSections()
    AddAllApproverSections()
End Sub

I believe you may have to change the way you're storing the "count" for these controls, as the View State information is not yet available at this stage. I would just store it as a Session variable, in that case. You'd just need to change your reference to "ViewState" throughout that code sample with "Session", like this:

Private Sub AddAllDocumentInfoSections()
    For i As Integer = 0 To Session(VIEWSTATE_DOCUMENT_COUNT) - 1
        AddDocumentSection()
    Next
End Sub

4 Comments

Thank you. I have previously tried using the page init. Also, while the code you provided does not adversely affect anything the resulting functionality is exactly as before.
"You should never dynamically add controls in the Page Load event." - I would qualify this: you can dynamically add controls in Page Load and later as long as you know what you're doing.
@Joe That is a valid point =) I've upvoted your answer, as you caught what I missed about this whole problem. Good answer.
Fyi, as long as I use two different naming containers I am able to continue to use the Page_Load event along with ViewState. Perhaps something has changed with recent versions of .Net that makes the previous restriction to Page_Init no longer relevant? Thanks again.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.