Coding Horrors: ASP.net Viewstate
August 11, 2008 by
Christoff Truter
C#
ASP.NET
Coding Horrors
ASP.net at the bottom of it all mainly builds on sessionless
technologies. The object instances on a web form essentially dies as soon as our
rendered output reaches the browser.
Since we can't work with dead objects, Microsoft came up with a mechanism that "hibernates"
objects and "re-awakens" them as soon as we need them.
This mechanism is called viewstate, which primary function is to persist the state
of web forms, across postsbacks.
The web form contains a hidden field, which houses a serialized copy of the last
state of the page.
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTkwNjc4NTIwMWRkemG0USJ6hDi/MgWFObHK2J15A3w=" />
During postback, this field gets passed along with the page request. The field then
gets deserialized and re-instantiated into it's various objects.
Typically changes we made programmatically to objects, gets stored inside the viewstate
(among other things).
This is were the catch comes in, we might end up with a behemoth of a viewstate
if we're not careful - sacrificing a great deal of bandwidth.
To give you an idea, I once saw viewstate
big enough to fill my 10mb harddrive back in the day.
This week alone I saw a 1.7mb page on one of our systems, due to its viewstate.
(that page alone consumed 4.7GB of bandwidth that day)
How does one go about solving this?
Looking to the web, I've seen a few interesting solutions around this issue. The
most popular solution involves overriding the
LoadPageStateFromPersistenceMedium
and
SavePageStateToPersistenceMedium methods
on the page class.
Instead of sending the viewstate along with the page, they store it on the server's
harddrive or even more
evil in
the session.
protected string ViewStateFileName
{
get
{
string vKey = (String.IsNullOrEmpty(Request.Form["viewstatekey"]))
? Guid.NewGuid().ToString() : Request.Form["viewstatekey"];
ClientScript.RegisterHiddenField("viewstatekey", vKey);
return ResolveUrl("~/viewstate/" + vKey);
}
}
protected override object LoadPageStateFromPersistenceMedium()
{
if (File.Exists(ViewStateFileName))
{
using (StreamReader stream = new StreamReader(ViewStateFileName))
{
LosFormatter formatter = new LosFormatter();
return formatter.Deserialize(stream);
}
}
else
{
return null;
}
}
protected override void SavePageStateToPersistenceMedium(object state)
{
using (StreamWriter stream = new StreamWriter(ViewStateFileName))
{
LosFormatter formatter = new LosFormatter();
formatter.Serialize(stream, state);
}
}
If you feel the urge to store viewstate on disc (like my example at the top), you
will need to consider a few things.
You will need to periodically clear the folder containing viewstate, which might
prove to be quite an ugly process if you're sitting with hundreds of thousands files.
Forcing us to clear this folder according to some kind of time interval (unless
you're looking at using this on the odd page?).
Your time interval also needs to be quite selective when it comes to which files
needs to be deleted, since you might end up deleting an active viewstate.
Looking at the approach of storing viewstate in the session, is simply a horrible
idea from a memory consumption point of view.
One might still get away with it in a small web application, but on much larger
busier applications, you might end up running out of memory.
I am not a huge fan of any of these approaches, mainly because I believe that one
should be a minimalist when it comes to viewstate - only use it when really needed
(look into technologies like Ajax for example). Viewstate isnt supposed to be gigantic
to start out with.
In fact, with a few small suggestions without sacrificing any functionality, we
managed to bring that 1.7mb page down to a mere 35kb, without overriding ASP.net's
default viewstate behaviour.
Ideally if I am going to override viewstate functionality, it will solely be to
provide me with a warning if viewstate gets too big.
In the following code we see just that, if the viewstate is larger than 32k, an
exception gets thrown.
protected override void SavePageStateToPersistenceMedium(object state)
{
if (Debugger.IsAttached)
{
using (MemoryStream stream = new MemoryStream())
{
LosFormatter formatter = new LosFormatter();
formatter.Serialize(stream, state);
if ((stream.Capacity / 1024) > 32)
{
throw new Exception("Please optimize viewstate");
}
}
}
base.SavePageStateToPersistenceMedium(state);
}
Re:Deprecated October 1, 2009 by Anonymous
Thanks a lot..