Abstract
In the following lines, I�ll show how to create a very simple viewer that displays the content of the controls store in view state. It�s not just a tree holding all the ViewState data as stored in __VIEWSTATE
field, but a list of all the controls storing data in ViewState and the data saved in view state by each control. This will therefore work to reveal how ASP.NET arranges data regarding the controls saved in ViewState, and the data each one of those controls preserve.
The Problem
As a part of the PostBack mechanism introduced in ASP.NET, Microsoft created the ViewState method to reduce page creation time at PostBack. The idea behind ViweState is simple: while page is created for the first time, some of the controls data are fetched from the data sources (such as the Database). When the user starts a server event from the client, all the page controls are recreated to process incoming server side events. To prevent access of data sources controls data, which wasn�t sent using HTML Form, it can be serialized into a hidden text field (__VIEWSTATE
) and sent to the client as a part of the HTML form. When a server side event is required, the hidden __VIEWSTATE
field that holds the control serialized data is sent over to the server. ASP.NET can use __VIEWSTATE
field preserved data to recreate controls instead of accessing data storage to get controls data. In addition to control usage of ViewState, ASP.NET enables programmers to add their own data into ViewState, so they can preserve the data over page calls.
You may ask then which controls use ViewState and what exactly they preserve in ViewState? I decided to take the challenge and to create a viewer displaying all the controls that preserve data in view state and the data that those controls preserve.
There are four main methods involved in handling ViewState in the Page
level. SavePageStateToPersistenceMedium
and LoadPageStateFromPersistenceMedium
methods are responsible for saving/loading ViewState to/from persistence medium (default persistence medium is a hidden field). LoadViewState
and SaveViewState
methods, actually handle the data which eventually loads/saves onto the persistence media. As part of ViewState handling LoadViewStateRecursive
and SaveViewStateRecursive
methods of the Control
class are called from LoadViewState
and SaveViewState
methods to handle page controls ViewState recursively.
If you will override page SavePageStateToPersistenceMedium
method and inspect the only parameter that passes to SavePageStateToPersistenceMedium
, you will see that this parameter holds a tree of objects. Going down the tree you will see Triplet
s, Pair
s and Array
s. All of those container classes are used altogether to store Page
and controls ViewState. Triplets are classes consisting of three Object
s and Pairs, as the name would suggest, two. Every object of those classes can be another Triplet
, Pair
, ArrayList
, Array
and every one of them may contain other objects and so on. Triplet
, Pair
, ArrayList
, Array
may also contain strings and primitive types. ASP.NET uses LosFormatter
class to serialize ViewState tree into a text sent to the client in a hidden input (__VIEWSTATE
).
A quick search in Google ends up with viewers that show only tree view of all Triplet
, Pair
, ArrayList
, Array
and primitive types. That's nice, but how can I identify the controls that stored the data and what exactly did they store in those endless Triplet
, Pair
, ArrayList
tree. Specific information about which controls hold data inside ViewState and controls data must be somewhere inside ViewState. That is to say, since ASP.NET knows what ViewState data it needs to send for every control, I should start looking for that data in the ViewState tree.
How ASP.NET saves ViewState data
Apparently there is some logic behind that mass and it goes like this:
0 .Triplet
1 .First(string
) - Page hash value
2 .Second(Triplet
)
3 .First(Pair
)
4 .First(ArrayList
) - keys
5 .Second(ArrayList
)- values
6 .Third(ArrayList
)
7 [X](Triplet
)
8 .Second(ArrayList
) � array that holds the control Position in Form controls collection.
9 .Third(ArrayList
) - array that holds the ViewState for every control from the upper array.
Line 1: it all starts with a triplet (line 0). The first element of the first Triplet
holds a string that is a hash key of the control hierarchy of the page which stores ViewState data. The hash key value is passed through view state even if all Page
controls' EnableViewstae
properties are set to false
.
Line 3: if that entry holds a Pair
, it means that the user asked to save data in the view state. If the user didn�t save any values to ViewState, this object sets to null
.
Line 4: if Line 3 holds Pair
, this line is the first Pair
object which is ArrayList
of all the user Viewstate keys.
Line 5: if Line 3 holds Pair
, this line is the second Pair
object which is ArrayList
of all the user Viewstate values.
Line 6-7: The third object of the second Triplet
holds data about which control preserves data in ViewState and the data it preserves. The third object is actually an ArrayList
of Triplet
objects that holds controls and data. Every entry in the third triplet object (ArrayList
) is another Triplet
object. That inner triplet holds the interesting data in the second and third objects.
Line 8: The second object in the above inner Triplet
is an Arraylist
. Every entry in that array holds a value that is the position of control in the controls collection of the HtmlForm
object (the Form
tag).
Line 9: Points to the third element of the inner Triplet
, that is an Arraylist
as well. Every entry in that array corresponds to the controls position array and actually holds data that is preserved by the control. That entry in array can be a single value or another complicated sub tree of Triplet
, Pair
s and ArrayList
s.
That�s for the logic of representing a control that preserves data and its data in the ViewState tree. Before we take a look at how to use this logic to display a page ViewState by controls, there are also other conclusions that I gathered regarding controls data structure. Those rules aren�t as firm as the controls logic. What I mean is that controls data rules are usually created but you can�t anticipate a scenario that control vendors might take to preserve data in ViewState.
- If
Pair
s� two objects hold ArrayList
they usually represent key / value relationship between the ArrayList
s, where usually the first array holds the keys and the second array holds the values.
- If
Triplet
s� first object is primitive value while the second and third array holds ArrayList
then the Triplet
also holds ArrayList
s with Key / Value relationship. Usually the first object (primitive value) holds counter of the elements in the ListArray
s. As describe above, second and third ArrayList
s hold Key / Value data.
- There are scenarios where a
Triplet
holds ArrayList
in the second and third objects and the first object is set to null
. Those arrays hold Key / Value Data.
- If
Triplet
's first object is primitive type it usually holds data about the number of elements in the second and third objects.
Creating ViewState Viewer
I have implemented my simple viewer inside a separate class (ViewStateViewer
) so you could activate it from every page. All you need is simply to call the ViewStateViewer
's single public method (GetPageControlsViewState
). Using HtmlGenericControl
, GetPageControlsViewState
will render tree with page, user defined and controls viewstate. Eventually it will look like the image at the top of the article.
The viewer class contains several private methods that are responsible for parsing the tree object recursively and displaying the data as tree; indeed they are pretty easy to follow. The interesting part goes into GetPageControlsViewState
method, which holds the logic of getting out from the ViewState data preserved by page, user and controls.
public void GetPageControlsViewState()
{
Create a new HtmlGenericControl
for holding the tree data that will be rendering on the page.
System.Web.UI.HtmlControls.HtmlGenericControl VSHtml = new
System.Web.UI.HtmlControls.HtmlGenericControl();
Check if current request holds PostBack data by checking the hidden field __VIEWSTATE
in request Form
collection. I use HttpContext.Current.Handler
for getting Page
object to make this code unbound to any Page
.
if(((Page)HttpContext.Current.Handler).Request.Form["__VIEWSTATE"] != null)
{
Get the string that holds the serialized data of ViewState and de-serialize it into an object.
string vsInput = ((Page)HttpContext.Current.Handler).Request.Form
["__VIEWSTATE"].Replace("\n", "").Replace("\r", "");
object vsObject = new LosFormatter().Deserialize(vsInput);
Getting the page ViewState is pretty easy. It�s always in the first element of the first Triplet
.
VSHtml.InnerHtml = "<b>Page Data : </b><br>" +
ParseViewState(((Triplet)vsObject).First,1);
Check if the user added custom ViewState values by testing if the second object is a Pair
object type. If so, then get the keys and values arrays from the Pair
object and loop throughout those arrays to get the key / value pairs and display them.
Triplet Second = (Triplet)((Triplet)vsObject).Second;
if (Second.First is System.Web.UI.Pair)
{
System.Web.UI.Pair oPair = (System.Web.UI.Pair)Second.First;
System.Collections.ArrayList oKeys =
(System.Collections.ArrayList)oPair.First;
System.Collections.ArrayList oVals =
(System.Collections.ArrayList)oPair.Second;
VSHtml.InnerHtml += "<br><b> User ViewState : </b><BR>";
for(int i = 0; i<oKeys.Count; i++)
{
VSHtml.InnerHtml += "\t Key=" + oKeys[i].ToString () +
" Value=" + oVals[i].ToString () + "<br>";
}
}
Get the third object and loop through the ArrayList
items.
ArrayList oArrObjectsAndData = (ArrayList)Second.Third;
for (int iObjAndData=0;iObjAndData<oArrObjectsAndData.Count;iObjAndData++)
{
Get Triplet
object from ArrayList
and then cast the second (holding objects representation) and the third (ViewState data) objects to ArrayList
.
Triplet oTripControls = (Triplet)oArrObjectsAndData[iObjAndData];
ArrayList oArrObjects = (ArrayList)oTripControls.Second;
ArrayList oArrData = (ArrayList)oTripControls.Third;
Loop through the objects ArrayList
. For each object:
- Get its ID from
HtmlForm
controls by using control position in the Controls
collection;
- Get ViewState data of the control from the
oArrData
ArrayList
and parse it.
for(int iCont =0; iCont < oArrObjects.Count; iCont++)
{
string ContID = GetForm().Controls[(int)oArrObjects[iCont]].ID;
Triplet oTrip = (Triplet)oArrData[iCont];
VSHtml.InnerHtml += "<br><b>" + ContID + " : </b><BR>" +
ParseViewState(oTrip ,5);
}
}
Set HtmlGenericControl
visibility and add it to the parse form collection.
VSHtml.Visible = true;
GetForm().Controls.Add (VSHtml);
}
}
If you look in the results, you can see that every control has its logic for storing ViewState. ListBox
holds Triplet
with two arrays. One holds the display values and the other holds their corresponding values. Grid saves several arrays that hold general information about itself (number of rows, page etc.) and values and value types for each cell. With a small effort, you can create custom viewer for each server control, check for the type of the control that preserves data and use its custom viewer.
Using this code, you can easily see which objects preserve data in the ViewState and what data the objects are preserving. If you take it one step ahead, you can develop custom viewers to display server control view state in a more readable manner.