Dynamic Controls and Managing ViewState

06.24.2007

Suggested Reading:
Understanding ASP.NET View State

Recently I’ve started learning ASP.NET (VB) in order to develop an application assigned to me at ExxonMobil. I have previously worked with ASP Classic, but the .NET framework is fairly new to me, so I’m just picking up on the architecture and how Microsoft is trying to take over my code and make things easier for me. Don’t get me wrong, I think .NET is a step in the right direction. It absolutely does a better job of following the model-view-controller design pattern by almost forcing you to keep your design code split from your logic. With that in mind, there are certainly accompanying downfalls, which I’ll talk about in this post.

For my project, I’m developing an application to track exception in the corporate Internet filter. That means when someone finds out that their vendor’s website has been blocked by WebSense (the corporate Internet filter) and they feel they are justified in having it unblocked, they must go through a process. This process will be managed by the new system I’m developing. Now, what if the vendor’s website is made up of three different, but related, domain names (e.g., http://imyourvendor.com, http://imyourvendorslegaldepartment.com). Instead of having the fill out a big form for each related websites, on the Website Address field I just wanted to provide a link that says “add another,” which would then create an additional textbox for the user to enter another related website address. Here’s a snapshot of the concept:

Address Form Fields

Now, if you’re acquainted with JavaScript you know it can’t be too hard to just plug another textbox in there without refreshing the page. On the other hand, customized JavaScript that adds DOM elements and ASP.NET are practically enemies, the reasons of which could fill an entirely separate article(s). After a few hours of battling with that idea, I decided for the sake of programmers that came later and for my own sanity, I would stick to postbacks.

One more thing to understand before we delve into code: ViewState manages changes in control values, not the controls themselves. So, if we dynamically create a new textbox control on every click of the Add Another link, we must recreate all the dynamic textboxes that we had created up until that time. On every postback, you must recreate all the dynamic controls you had created previously. That’s one thing Microsoft won’t do for you.

So, in order to track how many dynamic textboxes I had created previously, I’m going to create a property for that purpose:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
' This keeps track of how many URL text fields we have on the form. It is used
' to recreate our dynamic text fields on each post-back
Private Property URLControlsCount() As Integer
    Get
        If (ViewState("_urlControlsCount") IsNot Nothing) Then
            Return CType(ViewState("_urlControlsCount"), Integer)
        Else
            Return 1 ' We always want at least one URL text field on the form
        End If
    End Get
    Set(ByVal value As Integer)
        ViewState("_urlControlsCount") = value
    End Set
End Property

Notice that the property value is stored within the ViewState and, as such, will be tracked across future postbacks. Also notice that if the value hasn’t been set, it will return 1. That comes into play later because I always want at least one textbox displaying on the page.

Now, let’s create the Click event for the “Add Another” button:

1
2
3
4
5
6
7
8
Protected Sub AddURLLink_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles AddURLLink.Click
    Dim newTextBox As TextBox = New TextBox()
 
    URLControlsCount += 1
    newTextBox.ID = "url" + URLControlsCount.ToString()
    newTextBox.CssClass = "GridFormTextBox"
    URLFieldsPlaceHolder.Controls.Add(newTextBox)
End Sub

This increments the property we created in the last step so that on a future postback we will know how many controls to recreate. It then uses that control value to make an ID (convenient since URLControlsCount is already incrementing) and then adds the control to a placeholder.

Now we need to make a function to recreate the controls that we had created on previous postbacks. Here’s mine:

1
2
3
4
5
6
7
8
9
' We must re-create our dynamic URL controls on each postback
Private Sub CreateURLControls()
    For i As Integer = 1 To URLControlsCount
        Dim newTextBox As TextBox = New TextBox()
        newTextBox.ID = "url" + i.ToString()
        newTextBox.CssClass = "GridFormTextBox"
        URLFieldsPlaceHolder.Controls.Add(newTextBox)
    Next
End Sub

Now here comes the tricky part. Where are we going to call that function? If we call it from Page_Load, we won’t be able to get the values from the controls when we need to (when the user is done filling out the form and we’re ready for processing). The reason is that the values are applied to the controls at the beginning of (before?) Page_Load. If we don’t have our controls in the control tree until after the beginning of Page_Load, their values won’t be applied, so when we call…

1
CType(URLFieldsPlaceHolder.FindControl("url" & i), TextBox).Text

…we won’t get an error (the controls do indeed exist), but we’ll get an empty string rather than the true value.

So how about we call the function from Page_Init? While this would essentially allow us to access the values of the controls from within Page_Load (because they would have been added to the control tree before the beginning of Page_Load), we’ve run into a different problem: the ViewState for our URLControlsCount property has not been loaded yet. In other words, when our CreateURLControls() function tries to get the value of URLControlsCount, it’s not going to find the right value. In the end, we end up with the wrong number of controls (but hey, at least we would be able to access them now, right?).

So what’s the answer? 42. No, not really. We need to call the CreateURLControls() function after the ViewState has been loaded, but before Page_Load. In order to do this, let’s override LoadViewState() like so:

1
2
3
4
Protected Overrides Sub LoadViewState(ByVal savedState As Object)
    MyBase.LoadViewState(DirectCast(savedState, Pair).First)
    CreateURLControls()
End Sub

This allows us to load the ViewState before creating our controls, but before Page_Load. We must also override SaveViewState():

1
2
3
Protected Overrides Function SaveViewState() As Object
    Return New Pair(MyBase.SaveViewState(), Nothing)
End Function

LoadViewState is invoked only if ViewState was saved for the given control. I don’t really understand this part as much as I should, so I won’t pretend like i do…I’ll leave it at that.

One last thing. LoadViewState() is only called on postbacks. This means that CreateURLControls() won’t get called the first time we load the page, which means that we won’t have any textbox on our page. I want to start out with one textbox on the page, so i will call CreateURLControls() from Page_Init only if it’s not a postback. Remember how in the first step I set up the property to return 1 if the ViewState value didn’t exist? That’s what we need on this occasion, and this occasion only.

1
2
3
4
5
Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
    If Not IsPostBack Then
        CreateURLControls()
    End If
End Sub

Wow….how about that. Kind of a whirlwind and if you weren’t already struggling to find the answer to this problem, very little of this might make sense to you. On the other hand, if you were trying to figure this out and pouring over websites and books trying to find the answer, hopefully this revealed the piece of the puzzle you were searching for. Here’s my final code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
Imports ePACS.ISUBData.BO
Imports ePACS.ISUBData.BLL
 
Partial Class TestWithMaster
    Inherits System.Web.UI.Page
 
    ' This keeps track of how many URL text fields we have on the form. It is used
    ' to recreate our dynamic text fields on each post-back
    Private Property URLControlsCount() As Integer
        Get
            If (ViewState("_urlControlsCount") IsNot Nothing) Then
                Return CType(ViewState("_urlControlsCount"), Integer)
            Else
                Return 1 ' We always want at least one URL text field on the form
            End If
        End Get
        Set(ByVal value As Integer)
            ViewState("_urlControlsCount") = value
        End Set
    End Property
 
    Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init
        If Not IsPostBack Then
            CreateURLControls()
        End If
    End Sub
 
    Protected Overrides Sub LoadViewState(ByVal savedState As Object)
        MyBase.LoadViewState(DirectCast(savedState, Pair).First)
        CreateURLControls()
    End Sub
 
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Dim masterPage As MasterPage = Me.Master
        masterPage.Title = "Submit a Request"
        masterPage.Subtitle = "Submit a request to block or unblock a website from the corporate filtering system"
        masterPage.RightPanelVisibility = False
    End Sub
 
    Protected Overrides Function SaveViewState() As Object
        Return New Pair(MyBase.SaveViewState(), Nothing)
    End Function
 
    Protected Sub AddURLLink_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles AddURLLink.Click
        Dim newTextBox As TextBox = New TextBox()
 
        URLControlsCount += 1
        newTextBox.ID = "url" + URLControlsCount.ToString()
        newTextBox.CssClass = "GridFormTextBox"
        URLFieldsPlaceHolder.Controls.Add(newTextBox)
    End Sub
 
    ' We must re-create our dynamic URL controls on each postback
    Private Sub CreateURLControls()
        For i As Integer = 1 To URLControlsCount
            Dim newTextBox As TextBox = New TextBox()
            newTextBox.ID = "url" + i.ToString()
            newTextBox.CssClass = "GridFormTextBox"
            URLFieldsPlaceHolder.Controls.Add(newTextBox)
        Next
    End Sub
 
End Class

If I made a mistake in my explanation or you just want to have a nerd party, please…join the intimate conversation.

Tags: ,


Comment

06.24.2007 / Richard Hardy said:

That’s exactly what I was going to say.


Leave a Comment

Your email address is required but will not be published.




Comment