Quick and Dirty View of ViewState
At the place of my employment, the pace of development is very fast. Fortunately ASP.NET is terrific at doing rapid development. Unfortunately, it’s also very good at helping you make bad mistakes if you are not careful. ViewState is one of those great technologies that makes whipping up a website quick, but will bite you bad if you forget to keep track of it. That happened to me on a recent page where the number of controls is quite staggering. The problem is not the number of controls, but the amount of ViewState that gets created to support them. I needed to reduce the amount of content in ViewState fast!
At the end of the post I’ve provided a link to the best information about ViewState that I’ve ever come across. It’s worth reading several times. Once I knew what I needed to do to reduce the amount of data in ViewState I needed a way to know exactly what was in ViewState. As I’m sure you’re aware, most of the ViewState process happens behind the scenes and the end result is a large encoded block in your page source. I came across a couple of applications that would actually decode this block to show its contents, but I found the results hard to use and in several cases it failed to decode the ViewState at all. What i needed was a quick and simple way to see what’s going into that big block of data and this is what I came up with:
private IEnumerable<Control> GetRecursiveControlsEnumerator() { // Recursively enumerate through the control tree Stack<Control> stack = new Stack<Control>(); stack.Push(this); while (stack.Count > 0) { Control control = stack.Pop(); if (control.HasControls()) { for (int i = control.Controls.Count - 1; i >= 0; i--) stack.Push(control.Controls[i]); } yield return control; } } protected override void OnSaveStateComplete(EventArgs e) { base.OnSaveStateComplete(e); // Iterate over each control and inspect the viewstate PropertyInfo pi = typeof(Control).GetProperty("ViewState", BindingFlags.Instance | BindingFlags.NonPublic); Debug.WriteLine("***Begin ViewState Dump***"); Debug.WriteLine(String.Format("Path: \"{0}\"", Request.Url)); foreach (Control control in GetRecursiveControlsEnumerator()) { StateBag sb = pi.GetValue(control, null) as StateBag; if (sb == null || sb.Count == 0) continue; // Enumerate all the view state items foreach (string key in sb.Keys) { if (sb.IsItemDirty(key)) { Debug.WriteLine( String.Format( "ClientID: \"{0}\", Key: \"{1}\", Value: \"{2}\"", control.ClientID, key, (sb[key] == null ? "<null>" : sb[key]))); } } } Debug.WriteLine("***End ViewState Dump***"); }
I should first point out that I always write my websites with a PageBase class. That is, all pages inherit from the custom base page which itself inherits from System.Web.UI.Page. This can make site-wide changes much easier. The code above went in my base page so I could use it from any page in the site, but it would work just the same in a single page.
The basic premise is to iterate over all the controls, get the contents of ViewState via reflection, and then dump the contents along with the control ID to the debugger window.
First we hook the OnSaveStateComplete event because (if memory serves me correctly) it is the last event to fire before the page is finished processing and, more importantly, it occurs after ViewState is done collecting data. I used reflection to then get at each control’s individual ViewState property. For those frightened by reflection… get over it. The use of reflection is a topic for a whole other time, but suffice it to say, I have no reservations about using it in a simple debugging tool. I have my project set to conditionally compile this code and so it doesn’t even go out in the final release. If there is a simpler way to get each controls ViewState data (before it is encoded) then someone is welcome to enlighten me. The GetRecursiveControlsEnumerator method is just a fancy way of iterating all the controls using a generic Stack instead of a recursive function call. Finally the contents of ViewState is written to the debugger window along with the control ID.
Writing the control ID along with the actual data is key to diagnosing problems. As I alluded to earlier, I’ve seen other tools and methods of viewing ViewState, but without knowing which control is putting the data in ViewState it becomes very frustrating trying to track it down.
Let me just say that the results are spectacular. Using any tool that can monitor the debugging stream or simply attaching Visual Studio to the process will allow you to monitor all the ViewState that is getting sent out in the final response and the control that contributed it to ViewState. Within a matter of minutes I was able to see where some item were getting placed into ViewState unnecessarily and I was able to cut the ViewState quantity in half.
Resources: