CoreCoder
LANGUAGES: C#
ASP.NET VERSIONS: 1.0 | 1.1 | 2.0
Maintain
State Control
Diversify
the Role of the Viewstate with a Control-specific Cross-page State
By Dino
Esposito
Like it
or not, page size affects performance. The larger the response, the more time
the page takes to download and display. To feel the difference, create any
ASP.NET Web form page with a 10-row DataGrid and not many frills, such as
tables, images, and stylesheets. Then, run the page and count the size of only
one of the page elements the viewstate. Well, only the viewstate of such a
page is likely be more than 2KB!
The
theme of this article is not pure page performance, but rather the role of
viewstate in the context of performance and reliability.
Often
wrongly blamed for deficient page performance, the viewstate plays a key role
in the ASP.NET programming model: It is a secure and compact vehicle for
storing data that must survive across postbacks. The actual weight of
viewstate, however, must be evaluated with respect to other server-side state
management tools such as Session and Cache. Data stored in viewstate is
stateful information that would otherwise stay on the server, thus taxing the
Web server s memory. The key point about viewstate is this: Take advantage of
it, but don t misuse it and never overindulge in its storage capabilities.
Who Uses
Viewstate?
Viewstate
can take at least 10 percent of the entire page size and for nothing, from
the browser s perspective. Although most controls can work with or without viewstate,
for some of them (the most complex) a disabled viewstate can be a serious issue
up to the point of jeopardizing the overall behavior. The majority of
properties that a server control provides are implemented through the ViewState
hashtable:
public string
Text
{
get {return
Convert.ToString(ViewState["Text"]);
set {ViewState["Text"] = value;}
}
If the
viewstate for a given control is disabled, the property is assigned a default
value on postback. The assigned value can be the value assigned through the
ASPX markup, if any. If not, it will be the value assigned in the control s
constructor or in the get accessor of the property. No value programmatically
assigned to the property is retained across postbacks. Let s consider a TextBox
control.
Most of
the time you simply use the Text property of the TextBox control. Interestingly
enough, this property seems to work regardless of the viewstate settings. When
disabled, the viewstate for the TextBox is neither restored nor saved, as
expected. However, the methods of the IPostBackDataHandler interface regularly
process the TextBox-posted data the HTML value attribute of the <input>
tag. In doing so, the LoadPostData method copies the value to the Text property
of the server-side TextBox control.
It is
important to note that this happens only for the Text property. All the other
properties are lost, including ReadOnly, MaxLength, colors, and borders.
Drilling down a bit, though, you easily see that a disabled viewstate alters
the behavior of the control, even for what relates to the Text property. The
TextChanged event will repeatedly fire even when no real change occurred over
the postback (see the code in Figure 1). What s up?
bool
LoadPostData(string postDataKey,
NameValueCollection
postCollection)
{
string restoredText;
string postedText;
// Always the empty string (or what in the
ASPX) with
// the viewstate disabled
restoredText = this.Text;
postedText =
postCollection.get_Item(postDataKey);
// Whenever the input differs from the empty
string
// (or what in the ASPX), the event fires.
Repeatedly.
if (restoredText != postedText) {
this.Text = postedText;
return true; // Fires TextChanged
}
return false;
}
Figure
1: Disable the
viewstate and the TextChanged event will fire too often.
When the
TextBox is instantiated to serve a new request for the page (i.e., a postback),
properties get default values set in the constructor or values assigned through
the ASPX markup. With a disabled viewstate, this is what happens to the
restoredText variable shown in Figure 1. The first time a different value is
posted, the TextChanged event fires as expected. From then on, though, the
TextChanged event fires at each postback because the strings compared in Figure
1 are always different. A restored viewstate would guarantee a constant and
consistent update of the restoredText variable on each postback.
All
standard 1.x ASP.NET controls are designed to minimize the impact of a disabled
viewstate. In general, though, disabling the viewstate to get pages to download
faster may introduce harder to solve problems than just slow rendering.
Disabling the viewstate can break the functionality of certain components,
especially custom and third-party controls that make undocumented use of the
viewstate. More often than not, controls make a private use of the viewstate,
meaning that they store in the viewstate private or protected properties
completely invisible and not programmable from within the page level. If this
is the case, there s no way out other than re-enabling the viewstate. Be aware
of this problem you leave to your customers when you design and code custom
controls for ASP.NET 1.x.
In
ASP.NET 2.0, this problem finds a definitive solution with the introduction of the
control state. In ASP.NET 1.x you can simulate the version 2.0 control state
through a custom hidden field.
Introducing
Control State
How does
control state differ from viewstate? In ASP.NET 2.0, the control state is a
subset of the viewstate. You should use control state only for small amounts of
critical data that is essential for the functioning of a control across
postbacks. In no way should the control state be used as an alternative to
viewstate. Control state and viewstate are stored in the same hidden field
(__VIEWSTATE) and are managed through pairs of overridable functions. However,
unlike the viewstate, the control state can t be disabled. This guarantees that
no pages risk being broken by a disabled viewstate. The control state is
completely handled by the proprietary control and, in a certain way, the control state is the control s private viewstate.
If your
custom control has private or protected properties stored in the viewstate, you
should move all of them to the control state in ASP.NET 2.0. Anything you store
in the control state remains there until it is explicitly removed. The more
data you pack into it, the more data is moved back and forth between the
browser and the Web server. But in doing so, you gain total control over the working
of the component in any host page and you can deliver to your customers a solid
component that is not a time bomb or a Trojan horse.
Control State in ASP.NET 2.0
In
ASP.NET 1.x, the viewstate comes with a minimal, but effective, programming
interface. All controls come with a ViewState property, a customized hashtable
(StateBag class) where you explicitly store information. The content of the
collection is serialized and secured when the page gets to generate the
browser s response. The viewstate originates a Base64-encoded hidden field.
Upon page loading, the hidden field is decoded, deserialized, and remapped to
control properties. You can control the serialization and deserialization
process through a pair of overridable methods: LoadViewState and SaveViewState.
The
implementation of control state develops along the same lines, with one notable
difference: There s no global container for control state data like the
StateBag class. In other words, you can t rely on, say, a ControlState
collection object; you can use any variable type and name you like to store
data during the page request. Two overridable methods offer a chance to
deserialize and serialize data from and to an output stream. The methods are
LoadControlState and SaveControlState (see Figure 2).
public class
MyButton : Button
{
private int m_counter;
public int Counter
{
get {return m_counter;}
set {m_counter = value;}
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Page.RegisterRequiresControlState(this);
}
protected override void
LoadControlState(object state)
{
if (state != null)
{
Pair p = state as Pair;
if (p != null)
{
base.LoadControlState(p.First);
_m_counter = (int) p.Second;
}
else
{
if (state is int)
_ m_counter = (int) state;
else
base.LoadControlState(state);
}
}
}
protected override object SaveControlState()
{
object obj = base.SaveControlState();
if (m_counter != 0)
{
if (obj != null)
return new Pair(obj, _index);
else
return (m_counter);
}
else
return obj;
}
}
Figure
2: A custom
control that implements control state.
The
MyButton control illustrated in Figure 2 has a public property named Counter
designed to bufferize the number of times the control is clicked. If you store
this value in the viewstate, you re lost as soon as the page developer turns
viewstate support off. Before you implement the control state, you must first
require ad hoc support from the page infrastructure:
protected
override void OnInit(EventArgs e)
{
base.OnInit(e);
Page.RegisterRequiresControlState(this);
}
The
RegisterRequiresControlState method adds the control to the list of controls in
the page that implements control state. The list is scanned when the page is
going to load and save state from the body of the HTTP request. If the control
is in the list, the LoadControlState method is invoked. As demonstrated in
Figure 2, the method receives a Pair object where the first element is the rest
of the state, and the second element is what the control itself stored in
SaveControlState. If your control needs to persist more than one piece of
information you can group them in an array and store the array. The control
state is serialized as a special section within the __VIEWSTATE hidden field.
To fully
understand control state, and the risk run by 1.x pages, consider that in
ASP.NET 2.0 the DataList control stores SelectedIndex and EditItemIndex in the
control state.
Control State in ASP.NET 1.x
With
ASP.NET 2.0 Beta 1 available, most developers have already begun to seriously
approach the new platform, which is now only a few months away from release.
With 2.0 providing an off-the-shelf solution, I m not sure if I would spend any
time fixing the issue for 1.x controls. However, the work is not hard and can
be outlined in two steps.
First,
take your few critical properties out of the viewstate and implement them
through protected or private properties. Next, write a couple of methods that
serialize and deserialize those properties into a string. You can use any
string format you like. These two methods, say LoadMyControlState and
SaveMyControlState, will plug into the control infrastructure and work by
streamlining the content to and from a custom hidden field. You can call
LoadMyControlState from within the OnLoad event and SaveMyControlState from
within OnPreRender. Figure 3 shows the source code of a TextBox that retains
ReadOnly and BackColor, even with the viewstate disabled. By using the
LosFormatter ASP.NET class you can encode your data like the viewstate.
public class
TextBox : System.Web.UI.WebControls.TextBox
{
const string CONTROLSTATE =
"__CONTROLSTATE";
protected virtual void LoadMyControlState()
{
string ctlState;
ctlState = Page.Request.Form[CONTROLSTATE];
if (controlState == null)
return;
LosFormatter f = new LosFormatter();
object state = f.Deserialize(controlState);
object[] arrValues = (object[]) state;
ReadOnly = (bool) arrValues[0];
BackColor = (Color) arrValues[1];
}
protected virtual void SaveMyControlState()
{
object[] state = new object[2];
state[0] = this.ReadOnly;
state[1] = this.BackColor;
LosFormatter f = new LosFormatter();
StringWriter writer = new StringWriter();
f.Serialize(writer, state);
Page.RegisterHiddenField(CONTROLSTATE,
writer.ToString());
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad (e);
LoadMyControlState();
}
protected override void OnPreRender(EventArgs
e)
{
base.OnPreRender (e);
SaveMyControlState();
}
}
Figure
3: A TextBox that
implements control state.
Conclusion
Don t
blindly kill the viewstate; and especially don t do it if you re using
third-party controls including your own team s controls. Aim at controlling
the overall page size and define a target size that is both reasonable and in
line with your application s expectations. A page size ranging from 20K to 30K is
more than acceptable; however, you can even go a little over if the application
is particularly complex.
Be aware
that viewstate is a key part of the ASP.NET infrastructure; you can limit its
size and even kill it, but never blindly. Introduced in ASP.NET 2.0, the
control state gives you more flexibility you can serialize it at will and
keeps your controls out of the page developer s decisions about the overall
page viewstate.
Dino
Esposito is a
Wintellect 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 has also helped several companies architect and build
effective products for ASP.NET developers. Dino is the cofounder of http://www.VB2TheMax.com, a popular portal
for VB and .NET programmers. Write to him at mailto:dinoes@wintellect.com or join
the blog at http://weblogs.asp.net/despos.