Coding Horrors: ASP.net Viewstate

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);
}
 

Post/View comments
 

Passing parameters by reference using func_get_arg(s)

I wanted to write a blog about func_get_arg(s) for quite a while now. Since its a very handy function in PHP, but never got around doing it.

Until I ran into an interesting issue last week, while working on a new article for my website.

Lets have a look at the function before continuing, so that everyone knows what I am talking about. What we've got here is functionality which allows developers to pass parameters to a function, without needing to literally define them.

 
function demonstrate()
{
    $num_args = func_num_args();
    for ($i = 0; $i < $num_args; $i++)
    {
        echo func_get_arg($i);
    }
}
 
demonstrate("a","b","c"); // outputs abc
 

The issue comes in when we want to pass parameters by reference, observe the following code.

 
function increment($a)
{
    $a++;
}
$value = 10;
increment(&$value);
 
echo $value."<br/>"; // Value correctly echoed as 11
 
function increment2()
{
    $a = func_get_arg(0);		
    $a++;
}
 
increment2(&$value);
 
echo $value."<br/>"; // Value still 11
 

At first glance one would think that this is a bug, and as a matter of fact has been reported to be one:

http://bugs.php.net/bug.php?id=6427
http://bugs.php.net/bug.php?id=14705
http://bugs.php.net/bug.php?id=15098
http://bugs.php.net/bug.php?id=16464

In the PHP documentation, it does however state that it only returns a copy of the parameters - which isnt entirely true in concept anymore. Mainly because PHP 5 introduced a new object model, objects are passed by reference (or by handle), non-objects are copied.

So even if we are to "copy" an object it will simply be a copy of its handle, meaning it will still point to the orignal object. One would need to clone it if you need a copy.

In context of the func_get_arg(s) function, objects will automatically be by reference. As for non-objects we will need to "box" them if we want to pass it by reference using this function.

 
$value = 11;
 
function increment3()
{
    $a = func_get_arg(0);		
    $a->scalar++;
}
 
$value = (object)$value; // Similar to Boxing
increment3($value);
$value = $value->scalar; // Essentially our unboxing
echo $value."<br/>"; // Value correctly echoed as 12
 

Which is a bit of a tedious solution as pointed out by one of my vistors (Chris) from www.pulseforce.com

The following solution he suggested makes a LOT more sense (you can read his comments further down):
 
<?php 
 
$a = 11;
 
function increment4()
{
	$a = func_get_arg(0);
	$a++;
	return array($a);
}
 
list($a) = increment4($a);
 
echo $a; // Value correctly echoed as 12
 
?>
 

Post/View comments
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27