This has been documented in numerous places including here, here, here (with an incorrect fix) and here.
The code in the Textbox that actually signals to the page that textboxes using the ReadOnly property should not be saved in ViewState is in a few places:
1: Protected Overrides Function SaveViewState() As Object
2: If Not Me.SaveTextViewState Then
3: Me.ViewState.SetItemDirty("Text", False)
4: End If
5: Return MyBase.SaveViewState
6: End Function
8: Private Shadows ReadOnly Property SaveTextViewState() _
9: As Boolean
12: If (Me.TextMode = TextBoxMode.Password) Then
13: Return False
14: End If
15: If (((MyBase.Events.Item(EventTextChanged) Is Nothing) _
16: AndAlso MyBase.IsEnabled) _
17: AndAlso ((Me.Visible AndAlso Not Me.ReadOnly) _
18: AndAlso (MyBase.GetType Is GetType(TextBox)))) Then
19: Return False
20: End If
22: Return True
23: End Get
24: End Property
In the SaveTextViewState() method, part of the conditional statement specifies that if the ReadOnly property is set to true, then do not save the ViewState.
Also, in the LoadPostData() method, there is another check to make sure that the control's ReadOnly property is not set to true before it loads the data from the form post:
1: Protected Overrides Function LoadPostData( _
2: ByVal postDataKey As String, _
3: ByVal postCollection As _
4: NameValueCollection) As Boolean
6: Dim text As String = Me.Text
7: Dim text2 As String = _
9: If (Not Me.ReadOnly _
10: AndAlso Not [text].Equals(text2, _
11: StringComparison.Ordinal)) Then
12: Me.Text = text2
13: Return True
14: End If
16: Return False
17: End Function
If the ReadOnly property is set to true, then it will not set the Text property of the TextBox to be the value from the Form post.
The fix that people are touting is relatively simple. In the code behind of the page, you can simply add the attribute manually to the TextBox like this:
1: Protected Sub Page_Load(ByVal sender As Object, _
2: ByVal e As System.EventArgs) Handles Me.Load
3: With Me.txtTest
4: .Attributes.Add("readonly", "readonly")
5: End With
6: End Sub
And while this would work - this just feels like a bandage. Plus, if you have a big web project you are converting from .net 1.1 to .net 2.0 that contains TextBoxes all over the place that have the ReadOnly=True set, then this would be a lot of code to include on every single page. Even if you have a shared function somewhere that you can call - that means you still have to call the function for each Textbox that you wish to set as ReadOnly.
An alternative is to create your own TextBox class that inherits from the existing TextBox and handles the ReadOnly property in the fashion mentioned above. Any example of the code for the new TextBox is as follows:
1: Namespace WebControls
2: Public Class TextBox
3: Inherits System.Web.UI.WebControls.TextBox
5: Public Overrides Property [ReadOnly]() As Boolean
7: Return False
8: End Get
9: Set(ByVal value As Boolean)
10: If value Then
11: Me.Attributes("readonly") = "readonly"
14: End If
15: End Set
16: End Property
18: End Class
19: End Namespace
That's fine for new TextBoxes, but what about my hundreds and hundreds of TextBoxes that are already in my application? Well, .Net 2.0 allows you to use something called tagMappings in your web.config. Essentially, they are a way to redirect the compiler to use a different class for an existing mapping when it compiles your pages. From MSDN:
"Defines a collection of tag types that are remapped to other tag types at compile time."
So, how do we use this neat feature to solve this problem? Well, in the web.config, we will tell the .Net compiler to use our new TextBox control instead of the existing System.Web.UI.WebControls.TextBox control. In order to do this, you simply add the following to your web.config:
3: <add tagType="System.Web.UI.WebControls.TextBox"
The tagType attribute is the existing tag you would like to change (System.Web.UI.WebControls.TextBox) and the mappedTagType is the new TextBox control from above.
With this tag added in the web.config - whenever you have the following in a page:
1: <asp:TextBox runat="server"
3: ReadOnly="true" />
Your application will use the TextBox defined directly above.