Problem You want to reduce the size of your application pages to improve performance. Solution Review each page of your application and each of its controls to determine if the ViewState is required. Disable the ViewState where it is not explicitly needed. In the code for the page, use the .NET language of your choice to do either of the following: Disable the ViewState for the page by setting the EnableViewState attribute in the @ Page directive to False. (Alternatively, set Page.EnableViewState to False in the code-behind.) Disable the ViewState for individual controls on the page by setting the control's EnableViewState attribute to False. (Alternatively, set the control's EnableViewState property to False in the code-behind.) To illustrate these performance improvements, we took two examples from Chapter 2 and optimized them by disabling the ViewState. In the first example, we took the ASP. NET page created for Recipe 2.22, which displays a grid containing books and price data, and disabled the ViewState at the page level. Table 19-1 shows the page and ViewState size before and after the optimization. Table 19-1. ViewState performance improvement for Recipe 2.22 example | Before optimization | After optimization |
---|
Page size | 9,947 bytes | 6,766 bytes | ViewState size | 3,362 bytes | 162 bytes |
In the second example, we have used the ASP.NET page created in Recipe 2.10 and disabled the ViewState for the row controls within the DataGrid that appears within the page body. Example 19-1 shows the .aspx file for this application. The code-behind class for the application is shown in Example 19-2 (VB) and Example 19-3 (C#). Table 19-2 shows the page and ViewState sizes before and after optimization. Table 19-2. ViewState performance improvement for Recipe 2.10 example | Before optimization | After optimization |
---|
Page size | 9,448 bytes | 6,378 bytes | ViewState size | 3,734 bytes | 670 bytes |
Discussion The ViewState is used to keep track of the state of each control on a page and to rehydrate the control upon postback to the server. Because of its ability to maintain state when a page is posted back to the server, the use of the ViewState reduces the amount of code you would have to write. Thanks to the ViewState, you no longer need to extract values from the posted form for processing or reset the control values when you display the page again, as is the case with classic ASP. The controls are accessed as they were when the page was initially generated. Though use of the ViewState significantly reduces your coding and maintenance efforts, it comes at a cost. All of the data required to keep track of the control's state is stored in a hidden input control in the HTML page as shown next. Depending on the number and types of controls you use on your pages, the ViewState can get large, resulting in a decrease in performance. Because the ViewState data is sent to the browser when the page is rendered and returned to the server as part of the postback, a performance hit occurs when the page is first displayed and when the page is posted back to the server. Performance is degraded not so much by the generation of the ViewState data when the page is first rendered, but rather by the transfer of the extra ViewState data to and from the browser on postbacks, as well as by the processing of the data by the browser. Here is a typical ViewState input control: <input type="hidden" name="_ _VIEWSTATE" value="dDwtOTQzNjg3NDE1O3Q8O2w8aTwxPjs"/> While "byte counters" will be quick to disable the ViewState completely because of its inevitable negative impact on performance, a compromise is available that provides the best of both worlds: selectively disable ViewState because it is not needed for all pages or controls. By reviewing each of the pages in your application, you can improve the application's performance without losing the benefits of the ViewState. The first step when reviewing a page is to determine if the page does a postback to itself. If not, then the ViewState can be disabled for the entire page. This is done by setting the EnableViewState attribute in the @ Page directive to false: <%@ Page Language="VB" MasterPageFile="~/ASPNetCookbookVB.master" AutoEventWireup="false" CodeFile="CH19ViewStatePerformanceVB1.aspx.vb" Inherits="ASPNetCookbook.VBExamples.CH19ViewStatePerformanceVB1" Title="ViewState Performance" EnableViewState="false" %> Alternately, you can place this line of code in the Page_Load method to disable the ViewState for the entire page: Page.EnableViewState = False Page.EnableViewState = False; Even with the ViewState disabled for a page, a few bytes will remain in the value setting of the hidden input control. If you are determined to remove all traces of the ViewState, you must remove the form element or remove the runat="server" attribute from the form element. Either action can cause maintenance issues later, and the resulting savings of fewer than 50 bytes in a 20KB page has no measurable performance impact, so we do not recommend this remedy. If the page does a postback to itself, you will need to review each of the controls on the page. For each control, you need to determine if any state information is required by the control upon postback. If no state information is required, the ViewState for the control can be disabled. The example page created in Recipe 2.22 displays a grid containing books and price data. The page has no "action" controls; therefore, this page has no mechanism to postback to itself and is a good candidate for disabling the ViewState at the page level, a conclusion presented in the results shown in Table 19-1. After this optimization, the page size is 68% of the original size and the ViewState represents less than 2.4% of the optimized page. The example page created in Recipe 2.10 is a good candidate for performance improvement. This page is similar to the page created in Recipe 2.22 but has three "action" controls used to sort the data in the grid. Clicking on the column headers in the grid causes the page to be posted back to itself with the data sorted by the column clicked; therefore, this page cannot have the ViewState disabled at the page level. Because the ViewState cannot be disabled at the page level, we need to review each control to determine if the ViewState is needed. The page contains two controls, a content control and a DataGrid control. The content control contains no "action" controls and no programmatically set content; therefore, the ViewState for the content control can be disabled using the code shown here: <asp:Content Runat="server" ContentPlaceHolder enableviewstate="false"> While you might be tempted to disable the ViewState for the DataGrid, because all of the data is regenerated on each postback, you cannot. ASP.NET needs the ViewState information for the controls within the header of the DataGrid to process its click events and to execute the dgBooks_SortCommand method. If you disable the ViewState for the DataGrid, the postback will occur but none of the event handlers will be called. A DataGrid is a container of controls. At its highest level, a DataGrid consists of a header control and one or more row controls. In this example, only the header contains "action" controls and because the data in each row are regenerated with each postback, the ViewState for the row controls can be disabled using the code shown here: For Each item In dgBooks.Items item.EnableViewState = False Next foreach (DataGridItem item in dgBooks.Items) { item.EnableViewState = false; } | Code that programmatically disables the ViewState of individual controls, must be executed every time the page is rendered. In addition, the disabling of controls within a DataGrid must be performed after data binding. |
|
The results in Table 19-2 confirm the advantage of this optimization. By disabling the ViewState for the content control and for each row in the DataGrid, we have reduced the size of the ViewState and the overall page size as well. After optimization, the page size is 68% of the original size and the ViewState represents less than 11% of the optimized page. | ASP.NET 2.0 has removed the information needed for control postback from the ViewState; the postback information is now contained within the ControlState. This separation of control functionality from data provides more flexibility in disabling the ViewState to reduce the size of pages. The ControlState is implemented in most ASP.NET server controls. The DataGrid is one of the server controls that does not use the ControlState. If a GridView had been used in this example instead, such as the one shown in Recipe 2.14, the ViewState for the entire GridView could have been disabled without affecting the functionality of the page. |
|
See Also Recipes 2.10, 2.14, and 2.22 Example 19-1. Modified .aspx file from Recipe 2.10 <%@ Page Language="VB" MasterPageFile="~/ASPNetCookbookVB.master" AutoEventWireup="false" CodeFile="CH19ViewStatePerformanceVB2.aspx.vb" Inherits="ASPNetCookbook.VBExamples.CH19ViewStatePerformanceVB2" Title="View State Performance" %> <asp:Content Runat="server" ContentPlaceHolder enableviewstate="false"> <div align="center" > Improving ViewState Performance of Recipe 2-10 (VB) </div> <asp:DataGrid runat="server" BorderColor="#000080" BorderWidth="2px" HorizontalAlign="Center" AutoGenerateColumns="False" Width="90%" AllowSorting="True" OnSortCommand="dgBooks_SortCommand" > <HeaderStyle HorizontalAlign="Center" Css /> <ItemStyle css /> <AlternatingItemStyle css /> <Columns> <asp:BoundColumn DataField="Title" SortExpression="Title" /> <asp:BoundColumn DataField="ISBN" ItemStyle-HorizontalAlign="Center" SortExpression="ISBN" /> <asp:BoundColumn DataField="Publisher" ItemStyle-HorizontalAlign="Center" SortExpression="Publisher" /> </Columns> </asp:DataGrid> </asp:Content> | Example 19-2. Optimized code-behind for Recipe 2.10 (.vb) Option Explicit On Option Strict On Imports System.Configuration Imports System.Data Imports System.Data.OleDb Namespace ASPNetCookbook.VBExamples ''' <summary> ''' This class provides the code-behind for ''' CH19ViewStatePerformanceVB2.aspx ''' </summary> Partial Class CH19ViewStatePerformanceVB2 Inherits System.Web.UI.Page 'the following enumeration is used to define the sort orders Private Enum enuSortOrder soAscending = 0 soDescending = 1 End Enum 'strings to use for the sort expressions and column title 'separate arrays are used to support the sort expression and titles 'being different Private ReadOnly sortExpression() As String = {"Title", "ISBN", "Publisher"} Private ReadOnly columnTitle() As String = {"Title", "ISBN", "Publisher"} 'the names of the variables placed in the viewstate Private Const VS_CURRENT_SORT_EXPRESSION As String = "currentSortExpression" Private Const VS_CURRENT_SORT_ORDER As String = "currentSortOrder" '''*********************************************************************** ''' <summary> ''' This routine provides the event handler for the page load event. It ''' is responsible for initializing the controls on the page. ''' </summary> ''' ''' <param name="sender">Set to the sender of the event</param> ''' <param name="e">Set to the event arguments</param> Private Sub Page_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load Dim defaultSortExpression As String Dim defaultSortOrder As enuSortOrder If (Not Page.IsPostBack) Then 'sort by title, ascending as the default defaultSortExpression = sortExpression(0) defaultSortOrder = enuSortOrder.soAscending 'store current sort expression and order in the viewstate then 'bind data to the DataGrid ViewState(VS_CURRENT_SORT_EXPRESSION) = defaultSortExpression ViewState(VS_CURRENT_SORT_ORDER) = defaultSortOrder bindData(defaultSortExpression, _ defaultSortOrder) End If 'disable the ViewState for controls that do not need it disableViewState() End Sub 'Page_Load '''*********************************************************************** ''' <summary> ''' This routine provides the event handler for the datagrid sort event. ''' It is responsible rebinding the data to the datagrid by the selected ''' column. ''' </summary> ''' ''' <param name="source">Set to the source of the event</param> ''' <param name="e">Set to the event arguments</param> Protected Sub dgBooks_SortCommand(ByVal source As Object, _ ByVal e As DataGridSortCommandEventArgs) Dim newSortExpression As String Dim currentSortExpression As String Dim currentSortOrder As enuSortOrder 'get the current sort expression and order from the viewstate currentSortExpression = CStr(ViewState(VS_CURRENT_SORT_EXPRESSION)) currentSortOrder = CType(ViewState(VS_CURRENT_SORT_ORDER), enuSortOrder) 'check to see if this is a new column or the sort order 'of the current column needs to be changed. newSortExpression = e.SortExpression If (newSortExpression = currentSortExpression) Then 'sort column is the same so change the sort order If (currentSortOrder = enuSortOrder.soAscending) Then currentSortOrder = enuSortOrder.soDescending Else currentSortOrder = enuSortOrder.soAscending End If Else 'sort column is different so set the new column with ascending 'sort order currentSortExpression = newSortExpression currentSortOrder = enuSortOrder.soAscending End If 'update the view state with the new sort information ViewState(VS_CURRENT_SORT_EXPRESSION) = currentSortExpression ViewState(VS_CURRENT_SORT_ORDER) = currentSortOrder 'rebind the data in the datagrid bindData(currentSortExpression, _ currentSortOrder) End Sub 'dgBooks_SortCommand '''*********************************************************************** ''' <summary> ''' This routine queries the database for the data to displayed and binds ''' it to the datagrid ''' </summary> ''' ''' <param name="sortExpression">Set to the sort expression to use for ''' sorting the data</param> ''' <param name="sortOrder">Set to the requried sort order</param> Private Sub bindData(ByVal sortExpression As String, _ ByVal sortOrder As enuSortOrder) Dim dbConn As OleDbConnection = Nothing Dim da As OleDbDataAdapter = Nothing Dim dTable As DataTable = Nothing Dim strConnection As String Dim strSQL As String Dim index As Integer Dim col As DataGridColumn = Nothing Dim colImage As String Dim strSortOrder As String Try 'get the connection string from web.config and open a connection 'to the database strConnection = ConfigurationManager. _ ConnectionStrings("dbConnectionString").ConnectionString dbConn = New OleDbConnection(strConnection) dbConn.Open( ) 'build the query string and get the data from the database If (sortOrder = enuSortOrder.soAscending) Then strSortOrder = " ASC" Else strSortOrder = " DESC" End If strSQL = "SELECT Title, ISBN, Publisher " & _ "FROM Book " & _ "ORDER BY " & sortExpression & _ strSortOrder da = New OleDbDataAdapter(strSQL, dbConn) dTable = New DataTable da.Fill(dTable) 'loop through the columns in the datagrid updating the heading to 'mark which column is the sort column and the sort order For index = 0 To dgBooks.Columns.Count - 1 col = dgBooks.Columns(index) 'check to see if this is the sort column If (col.SortExpression = sortExpression) Then 'this is the sort column so determine whether the ascending or 'descending image needs to be included If (sortOrder = enuSortOrder.soAscending) Then colImage = " <img src='/books/1/505/1/html/2/images/sort_ascending.gif' border='0'>" Else colImage = " <img src='/books/1/505/1/html/2/images/sort_descending.gif' border='0'>" End If Else 'This is not the sort column so include no image html colImage = "" End If 'If (col.SortExpression = sortExpression) 'set the title for the column col.HeaderText = columnTitle(index) & colImage Next index 'set the source of the data for the datagrid control and bind it dgBooks.DataSource = dTable dgBooks.DataBind() Finally 'cleanup If (Not IsNothing(dbConn)) Then dbConn.Close() End If End Try End Sub 'bindData '''*********************************************************************** ''' <summary> ''' This routine disables the ViewState for all controls on the page ''' that do not need to use it. ''' </summary> Private Sub disableViewState() Dim item As DataGridItem 'disable the ViewState for each row in the DataGrid For Each item In dgBooks.Items item.EnableViewState = False Next item End Sub 'disableViewState End Class 'CH19ViewStatePerformanceVB2 End Namespace | Example 19-3. Optimized code-behind for Recipe 2.10 (.cs) using System; using System.Configuration; using System.Data; using System.Data.Common; using System.Data.OleDb; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; namespace ASPNetCookbook.CSExamples { /// <summary> /// This class provides the code-behind for /// CH19ViewStatePerformanceCS2.aspx /// </summary> public partial class CH19ViewStatePerformanceCS2 : System.Web.UI.Page { // the following enumeration is used to define the sort orders private enum enuSortOrder { soAscending = 0, soDescending = 1 } // strings to use for the sort expressions and column title // separate arrays are used to support the sort expression and titles // being different static readonly String[] sortExpression = new String[] { "Title", "ISBN", "Publisher" }; static readonly String[] columnTitle = new String[] { "Title", "ISBN", "Publisher" }; // the names of the variables placed in the viewstate static readonly String VS_CURRENT_SORT_EXPRESSION = "currentSortExpression";; static readonly String VS_CURRENT_SORT_ORDER = "currentSortOrder"; ///*********************************************************************** /// <summary> /// This routine provides the event handler for the page load event. It /// is responsible for initializing the controls on the page. /// </summary> /// /// <param name="sender">Set to the sender of the event</param> /// <param name="e">Set to the event arguments</param> private void Page_Load(object sender, System.EventArgs e) { String defaultSortExpression; enuSortOrder defaultSortOrder; if (!Page.IsPostBack) { // sort by title, ascending as the default defaultSortExpression = sortExpression[0]; defaultSortOrder = enuSortOrder.soAscending; // bind data to the DataGrid this.ViewState.Add(VS_CURRENT_SORT_EXPRESSION, defaultSortExpression); this.ViewState.Add(VS_CURRENT_SORT_ORDER, defaultSortOrder); bindData(defaultSortExpression, defaultSortOrder); } // disable the ViewState for controls that do not need it disableViewState(); } // Page_Load ///*********************************************************************** /// <summary> /// This routine provides the event handler for the datagrid sort event. /// It is responsible rebinding the data to the datagrid by the selected /// column. /// </summary> /// /// <param name="source">Set to the source of the event</param> /// <param name="e">Set to the event arguments</param> protected void dgBooks_SortCommand(Object source, System.Web.UI.WebControls.DataGridSortCommandEventArgs e) { String newSortExpression = null; String currentSortExpression = null; enuSortOrder currentSortOrder; // get the current sort expression and order from the viewstate currentSortExpression = (String)(this.ViewState[VS_CURRENT_SORT_EXPRESSION]); currentSortOrder = (enuSortOrder)(this.ViewState[VS_CURRENT_SORT_ORDER]); // check to see if this is a new column or the sort order // of the current column needs to be changed. newSortExpression = e.SortExpression; if (newSortExpression == currentSortExpression) { // sort column is the same so change the sort order if (currentSortOrder == enuSortOrder.soAscending) { currentSortOrder = enuSortOrder.soDescending; } else { currentSortOrder = enuSortOrder.soAscending; } } else { // sort column is different so set the new column with ascending // sort order currentSortExpression = newSortExpression; currentSortOrder = enuSortOrder.soAscending; } // update the view state with the new sort information this.ViewState.Add(VS_CURRENT_SORT_EXPRESSION, currentSortExpression); this.ViewState.Add(VS_CURRENT_SORT_ORDER, currentSortOrder); // rebind the data in the datagrid bindData(currentSortExpression, currentSortOrder); } // dgBooks_SortCommand ///*********************************************************************** /// <summary> /// This routine provides the event handler for the page index changed /// event of the datagrid. It is responsible for setting the page index /// from the passed arguments and rebinding the data. /// </summary> /// /// <param name="source">Set to the sender of the event</param> /// <param name="e">Set to the event arguments</param> protected void dgBooks_PageIndexChanged(Object source, System.Web.UI.WebControls.DataGridPageChangedEventArgs e) { String currentSortExpression; enuSortOrder currentSortOrder; // set new page index and rebind the data dgBooks.CurrentPageIndex = e.NewPageIndex; // get the current sort expression and order from the viewstate currentSortExpression = (String)(this.ViewState[VS_CURRENT_SORT_EXPRESSION]); currentSortOrder = (enuSortOrder)(this.ViewState[VS_CURRENT_SORT_ORDER]); // rebind the data in the datagrid bindData(currentSortExpression, currentSortOrder); } // dgCustomers_PageIndexChanged ///*********************************************************************** /// <summary> /// This routine queries the database for the data to displayed and binds /// it to the datagrid /// </summary> /// /// <param name="sortExpression">Set to the sort expression to use for /// sorting the data</param> /// <param name="sortOrder">Set to the requried sort order</param> private void bindData(String sortExpression, enuSortOrder sortOrder) { OleDbConnection dbConn = null; OleDbDataAdapter da = null; DataTable dTable = null; String strConnection = null; String strSQL = null; int index = 0; DataGridColumn col = null; String colImage = null; String strSortOrder = null; try { // get the connection string from web.config and open a connection // to the database strConnection = ConfigurationManager. ConnectionStrings["dbConnectionString"].ConnectionString; dbConn = new OleDbConnection(strConnection); dbConn.Open(); // build the query string and get the data from the database if (sortOrder == enuSortOrder.soAscending) { strSortOrder = " ASC"; } else { strSortOrder = " DESC"; } strSQL = "SELECT Title, ISBN, Publisher " + "FROM Book " + "ORDER BY " + sortExpression + strSortOrder; da = new OleDbDataAdapter(strSQL, dbConn); dTable = new DataTable(); da.Fill(dTable); // loop through the columns in the datagrid updating the heading to // mark which column is the sort column and the sort order for (index = 0; index < dgBooks.Columns.Count; index++) { col = dgBooks.Columns[index]; // check to see if this is the sort column if (col.SortExpression == sortExpression) { // this is the sort column so determine whether the ascending or // descending image needs to be included if (sortOrder == enuSortOrder.soAscending) { colImage = " <img src='/books/1/505/1/html/2/images/sort_ascending.gif' border='0'>"; } else { colImage = " <img src='/books/1/505/1/html/2/images/sort_descending.gif' border='0'>"; } } else { // This is not the sort column so include no image html colImage = ""; } // if (col.SortExpression == sortExpression) // set the title for the column col.HeaderText = columnTitle[index] + colImage; } // for index // set the source of the data for the datagrid control and bind it dgBooks.DataSource = dTable; dgBooks.DataBind(); } // try finally { //clean up if (dbConn != null) { dbConn.Close(); } } // finally } // bindData ///*********************************************************************** /// <summary> /// This routine disables the ViewState for all controls on the page /// that do not need to use it. /// </summary> private void disableViewState() { // disable the ViewState for each row in the DataGrid foreach (DataGridItem item in dgBooks.Items) { item.EnableViewState = false; } } // disableViewState } // CH19ViewStatePerformanceCS2 } | |