ASP.NET 2.0: Textbox ReadOnly Property

This has been documented in numerous places including here, here, here (with an incorrect fix) and here.

Essentially, if you set the Readonly Property of a textbox using either the XHTML or in the code-behind, any changes you make to the value of that textbox client-side (ie:  javascript) will not be persisted across postbacks.  I had found this method extremely effective for Calendar controls and what-not.  You just provide a link to a popup and then when the user selects an item in the popup, you can populate the underlying Textbox with the value from the popup.  Unfortunately, out of the box, that functionality has been taken away.

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:

        Protected Overrides Function SaveViewState() As Object
            If Not Me.SaveTextViewState Then
                Me.ViewState.SetItemDirty("Text", False)
            End If
            Return MyBase.SaveViewState
        End Function

        Private Shadows ReadOnly Property SaveTextViewState() _
                                                    As Boolean
            Get

                If (Me.TextMode = TextBoxMode.Password) Then
                    Return False
                End If
                If (((MyBase.Events.Item(EventTextChanged) Is Nothing) _
                        AndAlso MyBase.IsEnabled) _
                        AndAlso ((Me.Visible AndAlso Not Me.ReadOnly) _
                        AndAlso (MyBase.GetType Is GetType(TextBox)))) Then
                    Return False
                End If

                Return True
            End Get
        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:

        Protected Overrides Function LoadPostData( _
                        ByVal postDataKey As String, _
                        ByVal postCollection As _
                            NameValueCollection) As Boolean
            MyBase.ValidateEvent(postDataKey)
            Dim text As String = Me.Text
            Dim text2 As String = _
                        postCollection.Item(postDataKey)
            If (Not Me.ReadOnly _
                AndAlso Not [text].Equals(text2, _
                    StringComparison.Ordinal)) Then
                Me.Text = text2
                Return True
            End If

            Return False
        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:

    Protected Sub Page_Load(ByVal sender As Object, _
            ByVal e As System.EventArgs) Handles Me.Load
        With Me.txtTest
            .Attributes.Add("readonly", "readonly")
        End With
    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:

Namespace WebControls
    Public Class TextBox
        Inherits System.Web.UI.WebControls.TextBox

        Public Overrides Property [ReadOnly]() As Boolean
            Get
                Return False
            End Get
            Set(ByVal value As Boolean)
                If value Then
                    Me.Attributes("readonly") = "readonly"
                Else
                    Me.Attributes.Remove("readonly")
                End If
            End Set
        End Property

    End Class
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:

<pages>
      <tagMapping>
        <add tagType="System.Web.UI.WebControls.TextBox" 
             mappedTagType="WebControls.TextBox"/>
      </tagMapping>
</pages>

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:

<asp:TextBox runat="server" 
             ID="txtTest" 
             ReadOnly="true" />

Your application will use the TextBox defined directly above.

Enjoy!

 

Published 10 July 07 02:19 by Greg

Comments

# Jake said on August 1, 2007 1:18 PM:

This actually works really well. Excellent post!!!!!!

One comment, depending on the make up of your project, you may need to use a different name space other than WebControls to pull this off. Especially if you have a mixture of other controls in the same name space.

I used MyWebControls as my name space and then in the Web.config changed the mappedTagType value to ProjectNameSpace.MyWebControls.TextBox to work around some namespace related issues.

This was more specific which will be need in larger projects.

Jake

Anonymous comments are disabled