Speed Up Your Site with the Improved View State in ASP.NET 2.0
This article discusses:
|
This article uses the following technologies: ASP.NET, C# |
Contents
If you're a seasoned ASP.NET developer, the mere mention of view state probably sends shivers down your spine, as you envision kilobytes of base64-encoded data being sucked through a cocktail straw. Unless you take steps to prevent it, most ASP.NET pages will have a significant amount of supplemental data stored in a hidden field named __VIEWSTATE that in many cases is not even necessary. For fun, surf to your favorite sites built with ASP.NET, view the sources of the pages, and count the number of characters in the hidden __VIEWSTATE fields. I tried it and the sizes ranged from 800 to 7,800 characters.
View state, of course, has an important role in ASP.NET. Used wisely, it can simplify page development and improve user interaction with your site. If left unchecked, it can increase the size of your site's responses dramatically and bring your response times to a crawl over slow connections. The release of ASP.NET 2.0 brings several improvements to the view state mechanism that will make it easier to use without hindering your site's performance. These improvements include a reduction in encoding size, the introduction of control state to separate behavioral state from content, and intelligent integration with data-bound controls.
View State Fundamentals
Before I jump into the improvements in view state in ASP.NET 2.0, a short summary of the purpose and implementation of view state in the current release is in order. View state solves a very specific problem for ASP.NET developers—retaining state for server-side controls that are not form elements. This is important because much of the server-side control model in ASP.NET is built around the assumption that all controls retain their state if the user posts back to the same page. That is, if you modify the contents of any control during the processing of a request, you can count on those modifications still being there on any subsequent POST request back to the same page. As an example of view state in action, try running the page shown in Figure 1.
Figure 1 View State in Action
<%@ Page Language="C#" %> <script runat="server"> protected override void OnLoad(EventArgs e) { int val = int.Parse(_sum.InnerText); _sum.InnerText = (val+1).ToString(); base.OnLoad(e); } </script> <html> <body> <form runat="server"> <h2>ViewState test page</h2> <span id="_sum" runat="server">0</span> <br /> <input type="submit" /> </form> </body> </html>
Each time you press the Submit button, the value in the _sum span is incremented. Because view state retains the previous value across the request, it will start with the last value displayed and show 1, 2, 3, 4, and so on. To see what happens when view state is not enabled, try adding enableviewstate='false' as an attribute on the span element. It will display a value of 1 no matter how many times the page is posted since the previous value of the span is not propagated across requests.
View state completes the control-based programming model in ASP.NET. Without view state, some controls (like textboxes and dropdown lists) would keep some state across POST requests, while others would not, and working with these disparate controls would be a frustrating exercise in remembering special cases. With view state, developers can focus on the programming model and user interaction without having to worry about retaining state. View state can also be hashed or encrypted to prevent a user from tampering with or decoding it. For more information on this topic, see chapter 19 of "Securing Your ASP.NET Application and Web Services," available online.
There is one other important place where view state is used, and that is in controls that issue server-side change events. If the user changes the value in a textbox or switches the selected element in a dropdown list, you can register an event handler and have code execute when the event is raised. These controls compare the current value of the control with its previous value, which is stored implicitly in view state if any process has subscribed to the change event. If you disable view state on a control whose change notification event you are handling, it won't fire correctly since it must always assume that the previous value was the same as the default value in the form.
View State Problems
There are many issues with view state in ASP.NET 1.x, as I indicated earlier. It is on by default, and unless you know to look for it and disable it when it is not needed, it can significantly increase the amount of data rendered by your page. This becomes particularly painful when you use data-bound controls, all of which use view state to save their state across postbacks. As a simple and dramatic example, consider the ASP.NET page shown in Figure 2.
Figure 2 Problems with View State in ASP.NET 1.x
<%@ Page Language="C#" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Configuration" %> <%@ Import Namespace="System.Data.SqlClient" %> <script language="C#" runat="server"> protected override void OnLoad(EventArgs e) { string dsn = ConfigurationSettings.AppSettings["dsnPubs"]; string sql = "SELECT * FROM Authors"; using (SqlConnection conn = new SqlConnection(dsn)) using (SqlCommand cmd = new SqlCommand(sql, conn)) { conn.Open(); _dg1.DataSource = cmd.ExecuteReader(); _dg1.DataBind(); } base.OnLoad(e); } </script> <html> <body> <form id="Form1" runat="server"> <asp:DataGrid ID="_dg1" Runat="server" /> </form> </body> </html>
This page has a single DataGrid control that binds to the results of a simple query against the authors table of the pubs database. If you run this page (making any necessary corrections to the connection string) and look at the view state field, you will probably be shocked to find that it has more than 12,000 characters in it. It's even more shocking when you look at the contents of the page and realize that the actual number of characters needed to display the entire contents of the table to the browser is about 1,600. One reason for this explosion is that view state must encode not only the data but also the type of data (metadata); also, base64 encoding generally adds about 33 percent space overhead. Still, this much overhead to simply retain the state of the control across POST requests may seem disproportionate at times and definitely should be avoided if at all possible.
Deciding when to disable view state in your controls is obviously important if you are concerned about the size of your responses. The example just shown is a classic case where view state is being populated but never used, which suggests the primary rule to follow when optimizing view state usage on your site: if you populate the contents of a control every time a page is requested, it is generally safe (and wise) to disable view state for that control.
On the other hand, you may decide to take advantage of the fact that view state is going to retain the state of your controls and only populate control contents on the initial GET requests to a page (just falling through on subsequent POST requests). This saves a round-trip to whatever back-end data source you are using. By modifying my page to leverage this fact, I could change the OnLoad method to check the IsPostBack flag before populating the DataGrid, as shown in Figure 3.
Figure 3 Checking IsPostBack
protected override void OnLoad(EventArgs e) { if (!IsPostBack) { string dsn = ConfigurationSettings.AppSettings["dsnPubs"]; string sql = "SELECT * FROM Authors"; using (SqlConnection conn = new SqlConnection(dsn)) using (SqlCommand cmd = new SqlCommand(sql, conn)) { conn.Open(); _dg1.DataSource = cmd.ExecuteReader(); _dg1.DataBind(); } } base.OnLoad(e); }
There are, of course, other options that may be even better here, such as caching the results of the query on the server and rebinding the control with each request. It is up to you to decide where the state should be stored and what the trade-offs are in your particular architecture and applications.
You may have noticed that I was careful to say that it is "generally" safe to disable view state if you populate the contents of a control each time a page is requested. The exception to this is that some controls also use view state for behaviors as well as plain-state retention. As described earlier, the dropdown list and the textbox controls use view state to store the previous value to properly issue a change notification event on the server. Similarly, the DataGrid class uses view state to issue pagination, edit, and sorting events. Unfortunately, if you want to use features like sorting, paging, or editing in the DataGrid, you cannot disable its view state. This all-or-nothing mentality of server-side controls with respect to view state is one of the most frustrating aspects of the server-side control model in ASP.NET 1.x for developers who are trying to build fast, efficient sites.
View State Improvements in ASP.NET 2.0
Now that I have outlined the problems, it is time to discuss all the improvements in ASP.NET 2.0. The first one is the overall size of view state when it is serialized. In ASP.NET 1.x the serialization of two strings into the view state buffer looks like this:
<p<l<string1;>;l<string2;>>;>;
The serialization format used in ASP.NET 1.x for view state is a tuple format consisting of a hierarchical collection of triplets and pairs serialized using less-than and greater-than characters. The letter preceding the greater-than symbol indicates the type of the object stored (t=triplet, p=pair, i=integer, l=ArrayList, and so on). Each subelement within the less-than and greater-than characters is separated with a semicolon. It's an interesting serialization format, sort of like a compressed XML. If you're concerned with space, however, it is not the most efficient serialization format (only marginally better than XML).
ASP.NET 2.0 changes this serialization format. The serialization of the same two strings into the view state buffer in ASP.NET 2.0 is shown in the following line of code:
At least that's what it looks like when rendered to a browser. The square boxes are actually nonprintable characters which, if we rewrite using Unicode character references, turn into the code shown here:
[][]string1[]string2
string1string2
Instead of using a collection of printable characters to delineate objects in the view state stream ('<', '>', ';', 'l', 'i', 'p' and so on), ASP.NET 2.0 uses a number of nonprintable characters to mark the beginning of an object and to describe what type of object it is.
Using nonprintable characters to delineate the objects stored in view state serves two purposes. First, it improves the efficiency of the lexical analysis during the parsing of the view state string since there is no longer any need to match characters or parse tokens. Second, and more importantly, it reduces the number of characters used to encode the objects in view state. In my simple example of encoding two strings, the first encoding used 16 delineation characters versus 3 in the 2.0 format. This effect quickly compounds to make a significant impact.
For example, if we place a DataGrid on a form and bind it to the authors table in the pubs database, as shown in Figure 3, the size of the view state string in 1.x is 12,648 characters. If we do the same in 2.0, the size of view state is reduced to 6,728 characters, a reduction of nearly half. Figure 4 shows the contrast in space required by view state in ASP.NET 1.x, ASP.NET 2.0, and the number of characters actually rendered to the page for a variety of input sizes. In this case the authors table was augmented with additional rows at 500 row increments up to 2,000.
Figure 4 View State Size Comparison
Without doing anything special, you can look forward to an immediate reduction in the size of your view state with the release of ASP.NET 2.0. This does not mean that you should stop thinking about whether your controls need view state to function properly or not, since it can still contribute significantly to the size of the response, as evidenced in Figure 4. The next improvement in view state, however, can potentially save you much more.
Control State
I mentioned earlier that one of the most frustrating aspects of working with server-side controls in ASP.NET 1.x is the all-or-nothing mentality with respect to view state. Behavioral aspects of controls like pagination in the DataGrid or selection change notifications in textboxes require view state to be enabled to function properly. I'm sure all of you are sufficiently daunted by the prospect of leaving view state enabled in any of your DataGrids at this point. In ASP.NET 2.0, Microsoft addresses this particular problem by partitioning the view state into two separate and distinct categories: view state and control state.
Control state is another type of hidden state reserved exclusively for controls to maintain their core behavioral functionality, whereas view state only contains state to maintain the control's contents (UI). Technically, control state is stored in the same hidden field as view state (being just another leaf node at the end of the view state hierarchy), but if you disable view state on a particular control, or on an entire page, the control state is still propagated. The nice aspect of this implementation is that we can now amend our original principle on optimizing view state for ASP.NET 2.0 to be much stronger: if you populate the contents of a control every time a page is requested, you should disable view state for that control.
As long as control builders properly partition their state into behavioral state (to maintain core functionality) and UI state (to retain contents), this statement should be followed religiously. I would like to tell you to start following this recommendation now with all work done in ASP.NET 2.0, but the DataGrid has not been refactored to keep its behavioral state in control state. Fortunately, the new GridView is the logical successor to the DataGrid and it uses control state properly, as do the new versions of the TextBox and DropDownList controls. Figure 5 shows a list of the controls that currently make use of control state and what properties they store there for your reference. This list will likely change with the final release of ASP.NET 2.0.
Figure 5 Controls That Use Control State
If you would like to explore further how the new controls in ASP.NET 2.0 use control state and view state, I have written a utility called the View State Decoder which shows all of the view and control state on a given page. Figure 6 shows a screenshot of this utility in action. You can download the program from the link at the top of this article. Note that this application relies on the character encodings currently used for view state which may change before the final release.
Figure 6 ViewState Decoder
For those of you building controls, the usage model for control state is not quite as convenient as view state. Instead of providing a state-bag with an indexer to insert and remove items, you must override the virtual LoadControlState and SaveControlState methods and manually manage your portion of the object collection that is mapped into control state. The one other thing you must do is call the Page.RegisterRequiresControlState method during initialization so that the page knows to ask for control state at the right time. It is probably a good thing that storing items in control state is more difficult than view state since there is no way for users to disable it (there are also performance advantages to having an opt-in model for control state). Developers should think carefully before storing any state there as it will always be added to the rendering of a page. Figure 7 shows a custom control that stores a string in control state (in this case, its color).
Figure 7 Storing a String in Control State
When you are deciding what should go into control state and what should go into view state, remember that it should be a separation of behavioral state from content or UI state. Things like properties and collections of data should generally stay in view state and not migrate to control state. State that triggers a server-side event is the most typical type to store in control state.
Declarative Data Sources and View State
There is a small problem with the application of our view state optimization principle—you will very often not know whether you are populating the contents of a control on each request or not. The introduction of declarative data sources in ASP.NET 2.0 means that binding data to a control no longer involves explicitly wiring up its Data source property to a DataReader or DataSet and calling DataBind. Instead, you can declaratively place a data source on your page and point your control to that data source, for example using the new GridView class and an associated SqlDataSource bound to the authors table in the pubs database (see Figure 8). If you run this page, you will find that it just works. The GridView and its corresponding data source are smart enough to know when to interact. By the time the page is ready to render, the GridView is full of data from the data source and will properly render the entire authors table to the client.
Figure 8 Pointing to Pubs with a GridView
In this simple case, we have not disabled view state on the GridView, so you may think that we are again just wasting view state on storing data that is never used. Fortunately, the ASP.NET 2.0 engine makes the extra effort to do the right thing here, and it will not bother going back to the database when view state is enabled on a control.
Similarly, if you disable view state on the GridView, the data binding to the data source will occur on each request including POST back requests. This functionality is built into the DataBoundControl base class from which the AdRotator, BulletedList, CheckBoxList, DropDownList, ListBox, RadioButtonList, GridView, DetailsView, and FormView controls inherit. All of these controls exhibit this intelligent use of view state when bound to declarative data sources.
Conclusion
The next release of ASP.NET promises many improvements for Web developers, not the least of which is a better chance of sleeping at night without nightmares of base64-encoded data flowing across their Web farms. With a more compact serialization format, a partitioning between behavioral and UI state, and smart interaction with data-bound controls and declarative data sources, those view-state nightmares just might become sweet dreams.
Fritz Onion is a co-founder of Pluralsight, an education and content-creation company, where he focuses on Web development with ASP.NET. Fritz is the author of Essential ASP.NET (Addison Wesley, 2003). Read his blog at http://www.pluralsight.com/fritz.