Server-side Viewstate

Zero-off the Client-side Viewstate Burden by Leaving the Page’s Call Context on the Server

CoreCoder

LANGUAGES: C# | VB.NET

ASP.NET VERSIONS: 1.x | 2.0

 

Server-side Viewstate

Zero-off the Client-side Viewstate Burden by Leaving the Page s Call Context on the Server

 

By Dino Esposito

 

One of the most-often asked questions I get at conferences and classes invariably regards the theme of ASP.NET viewstate and security. It s amazing to see that many people are not afraid of using clear and unprotected hidden fields, but are skeptical about viewstate because they feel it is insecure. This is nonsense. The viewstate may not offer data confidentiality (unless you configure it for encryption), but it typically offers strong anti-tampering measures and, at the very minimum, encodes transmitted data.

 

The real issue with viewstate is not security let alone data integrity and confidentiality. The real viewstate drawback is size. Representing the call context for the current page, the viewstate is a dictionary that contains small binary or ternary subtrees of data for each control in the page s tree. Most controls save a lot of data to the viewstate, which may result in a significant increase of the overall page size. The viewstate, in fact, is implemented through a hidden field and affects the page s end-to-end execution twice. It will take longer to download and render the page on the client and it will take longer to upload the next postback request to the Web server.

 

In this article, I ll first decline some numbers to help you determine if your viewstate is optimal, satisfactory, or excessively large. The second aspect that I ll point out here is that the client-side hidden field is simply the default storage medium where the viewstate information can be stored. Nothing, in fact, prevents you from keeping the viewstate on the server. What really matters is that the viewstate is persisted across two successive requests of the same page; where you persist it is simply a matter of detail.

 

Viewstate Reference Numbers

The size of the viewstate strictly depends on how many controls you have in the page and how they use the viewstate. Consider that most controls tend to persist to the viewstate almost all their public properties at least those that are implemented as simple types. What size should be considered the critical mass for the viewstate of a page? You should strive to stay below 10KB, with an ideal threshold set around 3KB or 4KB. The size of the viewstate is also influenced by the markup that a particular control generates. In other words, you can have a page with just one control and still go far beyond 10KB of viewstate. This may happen if the control is a composite, templated, and data-bound control. Complex controls may appear as individual controls in the source, but expand up to a subtree of controls at render time. As an example, consider the page at http://www.asp.net/ControlGallery/default.aspx?Category=7&tabindex=2. It lists the custom controls available in the gallery. Looking at the generated HTML, I presume that the page in origin is based on a Repeater control bound to the results of a query. No forms of paging are implemented, so the longer the resultset, the longer the page gets, and the larger the viewstate grows. The last time I checked that page, its viewstate was as large as 103KB.

 

A large viewstate may be an issue for the user, but it is also an issue for the application in the long run. What else can you do to limit the impact of this key and critical architectural feature? As mentioned, the viewstate can be placed to any storage medium that you can access during the page processing phase, such as session state, ASP.NET cache, databases, and server files.

 

Managing the Viewstate

To modify the way your ASP.NET pages manage the viewstate, you need to derive all of them from a page that overrides a couple of virtual methods. These methods are named as follows:

 

protected virtual void

 SavePageStateToPersistenceMedium(object viewState);

protected virtual object

 LoadPageStateFromPersistenceMedium();

 

To start, create a new class that inherits from Page and override the two methods. Next, add the obtained class or, better yet, the assembly that contains it, to any project where you want to employ server-side viewstate. Each page class in the project that needs to use server-side viewstate will inherit from the custom page class.

 

A class that overrides both methods can load and save viewstate information from and to any storage medium different from a hidden field. By the way, note that you cannot override only one method. Both of them must be overridden because their behavior is tightly coupled and each wouldn t work without the other. What kind of code should these methods contain? Let s start with SavePageStateToPersistenceMedium.

 

The tasks accomplished by SavePageStateToPersistenceMedium are very easy to explain and understand. The method takes one argument: the contents to serialize. It simply opens the destination stream and calls the LosFormatter class to serialize the data as usual:

 

(C#)

void SavePageStateToPersistenceMedium(object viewStateBag)

{

 string file = GetFileName();

 StreamWriter sw = new StreamWriter(file);

 LosFormatter m_formatter = new LosFormatter();

 m_formatter.Serialize(sw, viewStateBag);     

 sw.Close();

 return;

}

 

(VB.NET)

Sub SavePageStateToPersistenceMedium(ByVal viewStateBag

                                    As Object)

 Dim file As String = GetFileName()

 Dim sw As StreamWriter = New StreamWriter(file)

 Dim m_formatter As LosFormatter = New LosFormatter()

 m_formatter.Serialize(sw, viewStateBag) 

 sw.Close()

 Return

End Sub

 

The previous code snippet creates a new disk file with the contents of the viewstate. If you take a look at the contents of the file, you ll see exactly the same data you would see in a __VIEWSTATE hidden field. Put succinctly, nothing changes in the way the viewstate is generated and persisted (except the physical storage medium).

 

The LoadPageStateFromPersistenceMedium method simply reverts the previous algorithm. It determines the medium to read from (a file in the example), extracts the contents (typically a Base64 encoded string), and calls LosFormatter to deserialize. Figure 1 illustrates this code in detail.

 

protected override object

 LoadPageStateFromPersistenceMedium()

{

 object viewStateBag;

 string file = GetFileName();

 StreamReader sr = new StreamReader(file);

 string m_viewState = sr.ReadToEnd();

 sr.Close();

 LosFormatter m_formatter = new LosFormatter();

 try {

    viewStateBag = m_formatter.Deserialize(m_viewState);

 }

 catch {

    throw new HttpException("The View State is invalid.");

 }

 return viewStateBag;

}

Figure 1A: Loading viewstate information from a server-side storage medium (C#).

 

Protected Overrides Function

 LoadPageStateFromPersistenceMedium() As Object

 Dim viewStateBag As Object

 Dim file As String = GetFileName()

 Dim sr As StreamReader = New StreamReader(file)

 Dim m_viewState As String = sr.ReadToEnd()

 sr.Close()

 Dim m_formatter As LosFormatter = New LosFormatter()

 Try

    viewStateBag = m_formatter.Deserialize(m_viewState)

 Catch

    Throw New HttpException("The View State is invalid.")

 End Try

 Return viewStateBag

End Function

Figure 1B: Loading viewstate information from a server-side storage medium (VB.NET).

 

The LosFormatter class is specifically designed to serialize and deserialize the viewstate, taking care of Base64 encoding and decoding and hashing. If required, the class also triggers the encryption process for the viewstate contents. The class has quite a simple programming interface made of only two publicly callable methods: Serialize and Deserialize. The Serialize method writes the final Base64 representation of the viewstate to a Stream or a TextWriter object:

 

(C#)

public void Serialize(Stream stream, object viewState);

public void Serialize(TextWriter output, object viewState);

 

(VB.NET)

Public Sub Serialize(ByVal stm As Stream,

                    ByVal viewState As Object);

Public Sub Serialize(ByVal ouptut As TextWriter,

                    ByVal viewState As Object);

 

The Deserialize method builds a StateBag object from a stream, a TextReader object, or a plain Base64 string:

 

(C#)

public object Deserialize(Stream stream);

public object Deserialize(TextReader input);

public object Deserialize(string input);

 

(VB.NET)

Public Function Deserialize(ByVal stm As Stream)

 As Object

Public Function Deserialize(ByVal input As TextReader)

 As Object

Public Function Deserialize(ByVal input As String)

 As Object

 

The LosFormatter class represents most of the underpinnings of the viewstate internal mechanism; by using it, you can be sure that everything will happen exactly as in the default case no matter what the storage medium.

 

Choosing the Storage Medium

The code snippets briefly considered so far save and retrieve the viewstate from a disk file. If you opt for this solution, you ought to come up with a consistent naming convention that generates unique names for the files based on the page URL and the session ID. But there s more to consider. Imagine you use names according to the following schema:

 

sessionID.pageURL

 

Apparently, you are taking into account both the session ID and the page URL. Is this sufficient? No, it isn t. How would you handle more requests for the same page in the same session? What if the page posts back? When a postback occurs, a new request for the same page is generated within the same session. So how would you deal with that? The simplest approach is to overwrite the old viewstate file. In this case, though, what happens if the user moves back to the previous page? Simple; that viewstate is irreversibly lost.

 

To be able to track previous pages, you can only store multiple copies of the viewstate per each page that holds a server-side viewstate. In this case, the problem evolves to the following: Which of the copies is the one that the current page is referencing? You also need a way to uniquely associate a server-side viewstate resource to a page. Put another way, you need to establish a link between each page and the correct copy of the server-side viewstate object to which the page is bound. How can you do that? The simplest thing to do is to give a name or an index to each viewstate object and store that name to the page using a hidden field.

 

The idea is to store into the page a sort of virtual pointer to the viewstate, instead of the viewstate itself. For example, an extra hidden field could contain the name of the file where the real viewstate is stored on the server. The code in the page class will use the contents of the hidden field to locate the file on the server and read from, and write to, it.

 

To let users freely navigate back and forth between the pages you should maintain a stack of viewstates for each page. The height of the stack determines the maximum number of consecutive clicks on the Back or Forward buttons. A reasonable number for this could be 8.

 

Conclusion

Is keeping the viewstate on the server really worth the effort? It depends. Be aware that the amount of space required to store data is far superior to the default case; that is, when the viewstate is stored in a hidden field. Because the viewstate is not cached with the rest of the page on the client, back and forward movements can be effective only if you can retrieve from the server the related viewstate. This means that each page should maintain a stack; this multiplies by 8 (or whatever number you choose) the size of the server-side viewstate. In addition, each page should include a hidden field to contain a name that links that page to a particular copy of the viewstate on the server.

 

Finally, I haven t spent much time discussing the medium where the viewstate should go. In my examples, I used disk files, but other options should be considered as well, such as the memory of the Web server or perhaps a database. If you opt for memory typically the Session object you get the fastest approach at the price of a memory occupation, which can become important and significant enough to prejudice an application's stability with restarts driven by memory shortage. If you opt for a database, the Web server memory is not affected and you don t have sparse files scattered throughout the Web space. The time-to-data ratio, though, would be quite higher.

 

Overall, keeping the viewstate on the server is definitely possible and relatively easy to code. A few design choices, though, should be made carefully to make it worthwhile. Don t be too surprised, however, if at the end of the day it turns out that the classic hidden field is still the best way to go.

 

Dino Esposito is a trainer and consultant who specializes in ASP.NET and ADO.NET. Author of Programming Microsoft ASP.NET and Introducing ASP.NET 2.0, both from Microsoft Press, Dino also helped several companies architect and build effective products for ASP.NET developers. Dino is the cofounder of http://www.DotNet2TheMax.com, a popular portal for .NET programmers. Write to him at mailto:[email protected] or join the blog at http://weblogs.asp.net/despos.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish