ViewState Unleashed
View State:In a broad sense, ViewState is used to "maintain the state of the view." For example, take a page that includes a single text box and a button. The TextBox is initially rendered with the value “text” preloaded in the text box. If the end user typed in bar and submitted the form, an ASP.NET developer would like to see a TextChanged event fired informing them of the change. How could ASP.NET know that the text in the TextBox changed had it not maintained some view state?
Text boxes and most other Web controls implement an interface called IPostBackDataHandler, indicating that they are interested in inspecting the data that's sent back via an HTTP POST during a form submit. In its implementation of IPostBackDataHandler.LoadPostData, the TextBox compares the data just posted with the data stored in ViewState, updates its Text property appropriately, and returns true. The page keeps track of all controls that returned true during this phase of page initialization and spins through this list of consumers of posted data and raises their PostDataChangedEvent, which in turn shows up as a TextChanged event to the developer.
The result is that you get an event when a TextBox's text changes between posts, as the TextBox's previous state was saved for you in a "state bag" and carried along for the ride in a hidden form field called _VIEW- STATE. Had the ASP.NET framework not included this feature, you would have had to store the previous value elsewhere. ASP.NET uses an optimized serializer called the LosFormatter to serialize the ViewState state bag into a tight binary format that is then Base64 encoded and rendered in the hidden form field.
You need to be aware of where ViewState fits into the page lifecycle. Note that numerous less significant events are available—even more were added in ASP.NET 2.0—but these are the core events and best represent the major events in a page's life. Below diagram shows the lifecycle during the initial load of a page, and how ViewState is involved during a PostBack.
Initial Load: InitàLoadàPreRenderàSaveViewStateàRenderàDispose
In Init, controls are created and added to the control tree, in PreRender “CreateChildControls” is executed and controls are prepared for render, In SaveViewState controls save current states, in Render controls render themselves and finally in Dispose controls and Page are disposed.
Controls on a page take the opportunity to save their data to the ViewState state bag just before they render. Another diagram shown below shows how pages and controls use the ViewState data carried along and returned during a PostBack to synthesize events for the page developer's use.
PostBack: InitàLoadViewStateàLoadàPostBack dataàPostBack eventsàPreRenderàSaveViewStateàRenderàDispose
Serializing:
Now comes an important question of transfer of this viewstate data. This is done by serializing it into an HTML friendly format because data has to be put inside hidden field.
But how are objects serialized into an HTML-friendly format that can be output with a hidden form field? You might have used the XmlSerializer or BinaryFormatter to serialize objects within your code, but neither of those really fits. Fortunately there's another lesser known serializer called the LosFormatter that does the work of serializing objects into ViewState. MSDN Online Help says this about the LosFormatter:
Important : "The limited object serialization (LOS) formatter is designed for highly compact ASCII format serialization. This class supports serializing any object graph, but is optimized for those containing strings, arrays, and hashtables. It offers second order optimization for many of the .NET primitive types.
"This is a private format, and needs to remain consistent only for the lifetime of a Web request. You are not allowed to persist objects serialized with this formatter for any significant length of time."
The LosFormatter fills in a gap between the verbose XmlSerializer and the terse BinaryFormatter. You can think of the LosFormatter as a BinaryFormatter "light" that is optimized for objects containing very simple types. It's also a nice convenience that LosFormatter creates an ASCII string representation of your object graph.
Many ASP.NET 1.x developers became frustrated with ViewState and started disabling it without considering the consequences. Many controls that required ViewState of some kind stopped working correctly when ViewState was disabled, which frustrated developers even more. ASP.NET 2.0 introduced a new kind of ViewState called ControlState. ControlState can't be turned off and provides a place for controls to put very small bits of state that are absolutely crucial to the operation of their control. However, for ASP.NET 1.x developers who don't have ControlState available, you may want to use the LosFormatter directly and store a bit of state here and there within your own private hidden form field.
For example, following is a small helper function that will take an object and serialize it as a string.
string LosSerializeObject(object obj)
{
System.Web.UI.LosFormatter los = new System.Web.UI.LosFormatter();
StringWriter writer = new StringWriter();
los.Serialize(writer, obj);
return writer.ToString();
}
object RetrieveObjectFromViewState( string serializedObject)
{
System.Web.UI.LosFormatter los = new System.Web.UI.LosFormatter();
return los.Deserialize(serializedObject);
}
The LosFormatter creates an interestingly formatted string just before Base64 encodes it. For example, the code
aTw1Pg==
looks like this when decoded: i<5>
This indicates an integer with the value 5. It's not XML even though it uses angle brackets—it's just an encoding schema. Usually you won't need to use the LosFormatter, but it's good to know that it's available. Manipulating:
Although ViewState is stored out in the open in a form field, its manipulation is done entirely by ASP.NET's infrastructure and you don't have to worry about it. However, some inspecting firewalls and proxies are very picky about what they allow through. If your firewall peers within the body of a request or response and enforces strict policies about the maximum size of an HTTP POST or single POST name/value pair, you could see problems. This used to be a problem with AOL but is seen less and less on the open Internet and more often within tight corporate intranets. In fact, while working with Microsoft’s advisory services I received a call from one of the premiere partner of MS who had an issue in which his web project worked fine in development environment while it fails to show up in production environment. I traced to find out the root cause of the problem and could see that the client (browser) received only view state and that too truncated. When I peeked into the firewall policies of his network infrastructure I found that tweaking some settings allowed application to work fine. Strange issue! It took me around 5 days to get on to solution. Often when these systems see what they consider a large value sent via an HTTP POST, they deny it. This, of course, doesn't bode well for your application if it doesn't receive what it's expecting.
The ASP.NET Page class includes two methods you can override, LoadPageStateFromPersistence Medium and SavePageStateToPersistenceMedium. This is your chance to jump in and decide what to do with the page's ViewState.
The SavePageStateToPersistenceMedium method receives the ViewState object as input and should store it however you like. This example serializes the ViewState object using the LosFormatter, and then chops the now serialized ASCII string into chunks of no more than 1,000 bytes. The LoadPageStateFromPersistenceMedium method must load the persisted ViewState from wherever it is stored—in this case, it is stored in the HTTP Request as a series of form fields—and return the resultant object. Code below shows how you can split the ViewState into as many fields as you need given an arbitrarily small chunk size. I have included two method overrides: one for SavePageStateToPersistence Medium that creates as many hidden form fields as are necessary and one for LoadPageStateFrom PersistenceMedium that reassembles the saved ViewState from however many hidden form fields are returned to the server side after a PostBack.
The save method calls LosFormatter.Serialize and stores the results in a StringBuilder. A while loop spins through the resulting string, chopping it up and storing the segments using the page's own RegisterHiddenField method. Notice that the counting variable cnt is incremented each time the loop executes and is used to create the name of the hidden field, such as _VIEWSTATE1, and so on.
protected override void SavePageStateToPersistenceMedium(object viewState)
{
LosFormatter format = new LosFormatter();
StringWriter writer = new StringWriter();
format.Serialize(writer, viewState);
StringBuilder s = new StringBuilder(writer.ToString());
int cnt = 1;
int left = s.Length;
while( left > 0 )
{
//Change to any value other than 1000 as necessary
int cut = (left > 1000) ? 1000 : left;
RegisterHiddenField("__VIEWSTATE" + cnt.ToString(),
s.ToString().Substring(0,cut));
s = s.Remove(0,cut);
left -= cut;
cnt++;
}
cnt--;
RegisterHiddenField("__VIEWSTATE0", cnt.ToString());
RegisterHiddenField("__VIEWSTATE", "");
}
protected override object LoadPageStateFromPersistenceMedium()
{
LosFormatter format = new LosFormatter();
int cnt = Convert.ToInt32(Request["__VIEWSTATE0"].ToString());
StringBuilder s = new StringBuilder();
for (int i = 1; i <= cnt; i++)
{
s.Append(Request["__VIEWSTATE" + i.ToString()].ToString());
}
return format.Deserialize(s.ToString());
}
Alternative Storage for ViewState
Storing ViewState within the returned HTML page is convenient, but when ViewState gets to be more than 20K or 30K, it may be time to consider alternative locations for storage. You might want to store ViewState in the ASP.NET Session object, on the file system, or in the database to minimize the amount of data shipped back and forth to the user's browser.When moving ViewState to the Session object, you have to take into consideration how ViewState is supposed to work. Remember that when ViewState is stored in a page's hidden form field, the ViewState for a page is literally stored along with the page itself. This is an important point, so read it again. When you choose to store ViewState elsewhere—separated from the page—you need a way to correlate the two. Your first reaction might be to think that each user needs a copy of the ViewState for each page they visit. Each page instance needs its own copy of ViewState.
There are many ways to do so. Some think that creating unique files on the file system and then collecting them later is a good technique. Personally, I would always rather add more memory than be bound to the always slower disk. No matter where you store ViewState, if you store it outside the page, then you'll need some process to later delete the older bits of state. This could take the form of a scheduled task that deletes files or a SQL Server job that removes rows from a database.
People (like me ;)) who really want to get their ViewState out of the page have tried many ways to solve this problem. Code below shows a hack that stores the ViewState value within the user's ASP.NET Session using a unique key per request. The ViewState string value that is usually sent out in a hidden form field is stored in the Session using the unique key, and then that considerably smaller key is put into a hidden form field instead. Therefore, each page gets its own Guid, as each HTTP request is unique. This Guid is declared as the ViewState "key" and is stored as its own hidden form field. This key is then used to store the ViewState in the Session. I have taken this code from a geek I met in an ASP.NET forum
private string _pageGuid = null;
public string PageGuid
{
get
{
//Do we have it already? Check the Form, this could be a post back
if (_pageGuid == null)
_pageGuid = this.Request.Form["__VIEWSTATE_KEY"];
//No? We'll need one soon.
if (_pageGuid == null)
_pageGuid = Guid.NewGuid().ToString();
return _pageGuid;
}
set
{
_pageGuid = value;
}
}
protected override object LoadPageStateFromPersistenceMedium()
{
return Session[this.PageGuid];
}
protected override void SavePageStateToPersistenceMedium(object viewState)
{
RegisterHiddenField("__VIEWSTATE_KEY", this.PageGuid);
Session[this.PageGuid] = viewState;
}
Moving ViewState to the Bottom of the Page
If your sites stay in the first few pages of Google's search results, it is said that you have "Google juice." Many developers worry that because ViewState's hidden form field appears very early in the page and almost always before any meaningful content, web bots and spiders such as Google won't bother looking past a giant glob of ViewState. To get around this problem you may want to move ViewState to the bottom of a rendered page. You likely wouldn't want to take the performance hit on every page for this hack, but certainly it's reasonable on occasion. It also gives you a great opportunity to override Render and seriously mess with the resulting HTML to include any other hacks or HTML modifications that you were previously unable to do using standard techniques.
Let's try this technique first. Override your page's Render method and call up to the base class's Render and insist that the page render in its entirety. The downside here, of course, is that by hacking into the render you're bypassing the buffered writing to the response output and dealing with strings. It's almost as if you're saying to the page, "Render yourself … stop! Wait. OK, continue, I'm done messing around."
Code below uses the standard System.String.IndexOf method to find the first chunk of the ViewState's hidden form field. A little string calculation finds the end of the ViewState value, which you store for use later. After removing the ViewState just found, you look for the end of the form tag and insert the saved ViewState just before .
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
System.IO.StringWriter stringWriter = new System.IO.StringWriter();
HtmlTextWriter htmlWriter = new HtmlTextWriter(stringWriter);
base.Render(htmlWriter);
string html = stringWriter.ToString();
int StartPoint = html.IndexOf("
if (StartPoint >= 0)
{
int EndPoint = html.IndexOf("/>", StartPoint) + 2;
string viewstateInput = html.Substring(StartPoint, EndPoint - StartPoint);
html = html.Remove(StartPoint, EndPoint - StartPoint);
int FormEndStart = html.IndexOf("") - 1;
if (FormEndStart >= 0)
{
html = html.Insert(FormEndStart, viewstateInput);
}
}
writer.Write(html);
}
ViewState unquestionably serves a useful purpose by layering an eventing subsystem on top of HTML and HTTP, but we can continue to look for ways to minimize its page footprint while still taking advan- tage of the value it provides. You can inspect it and look for fat to trim, you can move it around, you can chop it up into smaller bits hoping no one will notice you can compress it in place and pay a CPU cost, or you can hold it on the server side in memory or on the file system.
0 comments:
Post a Comment