Like many features available in Microsoft's products, page ViewState in
ASP.NET works behind the scenes by default. However, unlike most other
features, ViewState can impact the pages we develop dramatically. The impact
may not only be in page size but also in server-side performance. Pages with
large ViewState can throw unexpected errors. Developers must understand what
factors affect page ViewState and the strategies to follow to gain maximum
benefit from this feature. Since there is no one strategy for all scenarios,
we will review two different scenarios.
First, you must understand the benefits of ViewState.The Web is a
stateless scenario. In the ASP world, developers often use hidden form
fields to store values to persist information across postbacks. Automatic
state management is a feature that enables server controls to repopulate
their values on a round-trip without requiring you to write any code.
However, this feature is not without cost, because the state of a control is
passed to and from the server in a hidden form field. Each control defines
what it needs to store in its ViewState. They are stored as key-value pairs,
using the System.Web.UI.StateBag object. On page postback this data is sent
back to the server and ASP.NET uses it to construct the state of the
controls on the page during page initialization. To better appreciate the
amount of data stored in the ViewState we'll look at two examples.
Examples of Pages with ViewState
First we take an empty page with the following source:
<html>
<head>
<title>Viewstatetest - Empty
page</title>
</head>
<body>
<form id="viewstatetest"
runat=server></form>
</body>
</html>
If you save it as an .aspx file, open it in a browser, and look at the
page source you will see the code shown in Listing 1. (All of the code for
this article can be downloaded from below.) There is already some ViewState data because some of the ViewState data is actually ViewState metadata that always exists. This metadata tells ASP.NET how the ViewState data will be applied to create the state of the different page controls.
Now, let's consider a page that has a control. The simple page shown in
Listing 2 uses a DataGrid with all default settings to display just five
rows from a table. On saving this .aspx page and opening it up in a browser
we can take a look at the page source, which is shown in Listing 3.
That is about 4K of ViewState for just five rows. In a typical scenario
in which there will be more controls on a page (and probably a higher number
of result rows), the ViewState can be really large. It is thus imperative
that we gain some understanding of how to control the size of the ViewState.
Controlling ViewState
ViewState can be controlled at four levels the machine level, the
application level, the page level, and the control level. Each level
inherits the setting of the level above by default. For ViewState to be
enabled, all four levels must enable ViewState. By default it is enabled at
each level. If ViewState is disabled at the application level (this is done
in the web.config file) it overrides any page-level setting. Similarly, if
ViewState is disabled at the page level, then it overrides the setting of
any control within the page. A child cannot override the ViewState setting
of the parent. For example, if a page-level ViewState setting is set to
false, controls within the page cannot store ViewState information.
Only controls contained within a <form runat=server> tag in the .aspx
page can store ViewState. A form field is required so that the hidden field
that contains the ViewState information can post back to the server. It must
be a server-side form so the ASP.NET page framework can add the hidden field
when the page is executed on the server.
The page saves about 20 bytes of information into ViewState. This is
used to distribute postback data and ViewState values to the correct
controls on postback. So even if ViewState is disabled for the page or
application, you may see a few remaining bytes in ViewState. In cases where
the page does not need to post back at all, ViewState can be eliminated from
the page by omitting the server-side <form> tag.
Remember that ViewState for each control on each page is enabled by
default. Since many server controls defined on a page contribute to
ViewState size, ViewState, if unchecked, will grow really large and impact
performance.
We need to understand how the various server controls affect ViewState.
While it's not possible to delve into the details of each of the server
controls, for our purposes they can be classified into two groups.
The first group, the light control group (see Figure 1), consists of the
controls that have little or no effect on the ViewState of a page. So for
all practical purposes the enableviewstate setting of these controls can be
ignored. The validation controls also fall in this group. Most of these
(notably TextBox and CheckBox) retain their states regardless of their
ViewState settings. Thus, their ViewState can be turned off globally. One
easy way of doing this is to create a custom control that inherits from
TextBox, for example, and sets the ViewState property to false in its
constructor, as shown below. This control can be used in place of the
TextBox control throughout. It is necessary to disable the ViewState in the
constructor (and nowhere else), since that way it can always be overridden
for the page by using the enableviewstate attribute. This is a good strategy
to follow in nearly every scenario.
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
namespace ViewState.Controls
{
public class MyTextBox : System.Web.UI.WebControls.TextBox
{
public MyTextBox()
{
base.EnableViewState = false;
}
}
}
The second group, the heavy control group (see Figure 2), consists of
all the list and grid controls. Typically, these store their entire contents
in the ViewState. Thus, all our efforts henceforth should concentrate on how
best to manage the ViewState of these controls.
Scenario 1: Network Bandwidth Is the Constraint
This is the typical scenario of a Web site on the Internet, e.g., an
e-commerce site. While it is easy to add more hardware to alleviate
performance issues, little can be done about network bandwidth on the client
side. Generally, pages should be made as light as possible. A highly
interactive page with server-side controls belonging to the heavy control
group will add a lot of ViewState data, increasing the size of the page.
This ViewState not only must be downloaded by the client but also uploaded
during postback. Thus, a 1KB increase in ViewState increases the traffic by
2KB. Therefore, our strategy's main aim should be to minimize ViewState
data.
The DataGrid control is a particularly heavy ViewState user. By default,
all of the data displayed in the grid is also stored in ViewState, which is
particularly useful when an expensive operation (like a complex SQL query)
is required to fetch the data. However, this behavior also makes DataGrid
the prime suspect for unnecessary ViewState. If sorting and paging is being
used, then a rebind of the DataGrid to its dataview class is necessary on
every postback, and the entire ViewState of the DataGrid is unnecessary.
Even when sorting and paging is not being used, the grid should be rebound
on every postback. If this rebinding requires a complex query, then it is
worthwhile to investigate whether the query can be broken into expensive and
less expensive operations. If so, only the results of the expensive
operations need be stored in the ViewState and reused on every postback to
rebind the grid.
Sometimes data can be added to the ViewState judiciously for overall
benefit. The most striking example is provided by Web farms. Typically in
these scenarios the session state is persisted in an external database,
since a session may jump between servers during its lifetime. In this case,
the session data is fetched from the database, deserialized, and made
available to the page. Any changes to the session data that follow are
serialized and persisted to the database. Although ASP.NET's session
persistence makes it easy to implement this, it is an expensive process. If
this data is stored in ViewState instead, then the round-trip access to the
database can be completely eliminated. While adding data to the ViewState,
keep in mind that ViewState is optimized to work with the following:
Int, boolean, string, and other primitive types both boxed and unboxed
versions
Arrays of primitive types
ArrayList and Hashtable
Any type that is marked with the serializable attribute, or that
supports the serializable interface
These should allow the ability to store most data structures into
ViewState. Again, there is a critical point at which the amount of data
involved makes it more efficient to store it in the database. This depends
on the individual characteristics of the application setup and can only be
found empirically.
Two caveats should be kept in mind when persisting data in ViewState.
First, ViewState data is easy to decode (since it is basically base64
coding). Thus, any sensitive information should not be stored in ViewState
and sent over an insecure link. Sensitive information can be stored in the
ViewState if it is encrypted and hashed, as described later. Second,
sessions, unlike ViewState, time out after a predetermined time. If timeout
functionality is required, then sessions must be used instead of ViewState
to store data.
Scenario 2: Processing Power Is the Constraint
This is the usual scenario of a local intranet site running over a
corporate LAN. The available bandwidth is usually many times greater than
that available over the Internet, so the constraint usually lies in
server-side processing power. Typically there is less hardware available for
running an intranet, as compared to an external-facing Internet site.
Intranet applications also tend to be more interactive, having quick,
frequent postbacks between pages, owing to the speed of the network the
nature of the application.
In such a scenario, application design should favor putting as much
reusable data into the ViewState as possible, minimizing database access.
This is done by checking the Page.IsPostback property in the Page_Load event
of the page. Data is loaded from the database only for the initial load. All
subsequent requests are served using the stored ViewState.
This strategy results in making the application servers, rather than the
database server, the critical resource. This is favorable since we can get
an almost linear increase in performance by adding more application servers
until the database becomes the critical resource. Thus, ViewStates of all
the controls in the heavy control group which require database access to
build themselves should be enabled. In this way, they need to be built
only once for every page and built from the ViewState on postbacks. An
exception, as always, is DataGrid, with sorting and paging allowed on it. It
needs to be rebound on every postback and enableviewstate should be set to
false.
There is a directive, EnableViewStateMac, that can be set to either true or false at the page level or in web.config. When true, a hash code is appended to the ViewState during page rendering. On postback the hash code is calculated again and checked
against the stored value. The page is rejected if they are not the same.
This ensures that the ViewState has not been tampered with during the
round-trip from the client to the server. While this feature has its uses
over an insecure Internet connection, it requires extra CPU resources on the
server. On an intranet, where there is less chance of the ViewState being
tampered with, EnableViewStateMac can be set to false in web.config to increase performance.
Checking ViewState Size
After we implement our strategy we need to check the results. Each .aspx
page developed should be tested for ViewState size before being released to
production. Server-side tracing of ASP.NET pages does not allow us to see
the size of the total ViewState of each page. It displays only individual
ViewState sizes of the controls. Does this mean we have to laboriously open
every page in the browser and see the source of the ViewState to compare
sizes? Fortunately, there is a better way of doing things.
We can create a base page class that every other .aspx page in our
application inherits (see Listing 4). This class can override the
OnPreRender event of the page class to trace the ViewState. This trace can
then be used to see the size of the ViewState. To test this class we can
modify the DataGrid page we used earlier. The only changes are in the page
attribute at the top and adding a button to do the postback (see Listing 5).
We have to set the web.config to enable trace and to show the trace in
the page output. After this is done, on postback we see a lot of trace
output. Just after the "request details" section we see the ViewState size
information. The relevant portion of the trace is shown in Table 1.
Conclusion
The ViewState of a page is affected most by the controls belonging to
the heavy control group. The amounts of ViewState data being stored by these
controls should be carefully monitored to see whether they are within
acceptable limits for the particular application. Special care should be
given to the DataGrid control. In some situations, data can be added to the
ViewState instead of the session to reduce database access and increase
performance.
About The Author
Utpal Chakraborty, manager of software engineering at Organic (www.organic.com), is a Microsoft Certified Professional. He has extensive experience in design and implementation of n-tier applications and is currently leading a number of .NET implementations. Prior to working with .NET Utpal spent many years developing in Java and C++.
uchakraborty@organic.com
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail:
info@sys-con.com