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-behind class for the page, use the .NET language of your choice to do either of the following:
Disable the ViewState
for the page by setting
Page.EnableViewState
to False
.
Disable the ViewState
for individual controls by
setting the control’s
EnableViewState
property to
False
.
To illustrate these performance improvements, we took two examples
from Chapter 1 and optimized them by disabling
the ViewState
. In the first example, we took the
ASP.NET page created for Recipe 1.19, which
displays a grid containing books and price data, and disabled the
ViewState
at the page level. Table 16-1 shows the page and
ViewState
size before and after the optimization.
Table 16-1. ViewState performance improvement for Recipe 1.19 example
Before optimization |
After optimization | |
---|---|---|
Page size |
18,175 bytes |
11,271 bytes |
|
6,953 bytes |
49 bytes |
In the second example, we have used the ASP.NET page created in
Recipe 1.12, replaced the table used
for the page header with the header user control created in
Recipe 4.1, and then disabled the
ViewState
for the header control as well as the
row controls within the DataGrid
that appears
within the page body. Example 16-1 shows the
.aspx
file for this application. The code-behind
class for the application is shown in Example 16-2
(VB) and Example 16-3 (C#). Table 16-2 shows the page and
ViewState
sizes before and after optimization.
Table 16-2. ViewState performance improvement for Recipe 1.12 example
Before optimization |
After optimization | |
---|---|---|
Page size |
14,643 bytes |
9,251 bytes |
|
6,665 bytes |
1,273 bytes |
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
significantly reduces the amount of code
you would otherwise 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 was the case with classic ASP. The
controls are simply accessed as they were when the page was initially
generated.
While 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 very large, resulting in a
significant 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 post-back,
a performance hit occurs when the page is first displayed as well as
when the page is posted back to the server. Performance is degraded
not so much by the generation of the ViewState
data itself when the page is first rendered, but rather by the
transfer of the extra ViewState
data to and from
the browser on post-backs, 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 completely disable the ViewState
because of its
inevitable negative impact on performance, there is a compromise
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 significantly improve the performance of the application 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 placing this line of code in the
Page_Load
method:
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 absolutely determined to remove all
traces of the ViewState
, you must either 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 less than 50 bytes in a 20K
page has no measurable performance impact, so we do not recommend
such an extreme 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 whether 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 1.19
displays a grid containing books and price data. The page contains
two “action” controls that are
anchors used to access other pages; 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
borne out by the results shown in Table 16-1. After
this optimization, the page size is 62% of the original size and the
ViewState
represents less than 0.5% of the
optimized page.
The example page created in Recipe 1.12
is also a good candidate for performance improvement. As mentioned,
we replaced the table used for the page header at the top of the page
with the header user control created in Recipe 4.1. This change better illustrates our
point and is shown in the .aspx
file in Example 16-1. This page is similar to the page created in
Recipe 1.19 but has three additional
“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 instead review each control to determine if
the ViewState
is needed. The page contains two
controls, a header control and a DataGrid
control.
The header control contains no
“action” controls and no
programmatically set content; therefore, the
ViewState
for the header control can be disabled
using the code shown here:
pageHeader.EnableViewState = False pageHeader.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 itself 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 is 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; }
When programmatically disabling the ViewState
of
individual controls, the code that performs the disabling must be
executed anytime the page is rendered. In addition, the disabling of
controls within a DataGrid
must be performed after
data binding.
The results in Table 16-2 confirm the advantage of
this optimization. By disabling the ViewState
for
the page header and for each row in the DataGrid
,
we have significantly reduced the size of the
ViewState
and the overall page size as well. After
optimization, the page size is 63% of the original size and the
ViewState
represents less than 14% of the
optimized page.
Recipe 1.12; Recipe 1.19; Recipe 4.1
Example 16-1. Modified .aspx file from Recipe 1.12
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="CH16ViewStatePerformanceVB2.aspx.vb" Inherits="ASPNetCookbook.VBExamples.CH16ViewStatePerformanceVB2"%><%@ Register TagPrefix="ASPCookbook" TagName="PageHeader"
Src="CH04UserControlHeaderVB.ascx" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>View State Performance</title> <link rel="stylesheet" href="css/ASPNetCookbook.css"> </head> <body leftmargin="0" marginheight="0" marginwidth="0" topmargin="0"> <form id="frmDatagrid" method="post" runat="server"><ASPCookbook:PageHeader id="Pageheader" runat="server" />
<table width="90%" align="center" border="0"> <tr> <td><img src="images/spacer.gif" height="10" border="0"></td> </tr> <tr> <td align="center" class="PageHeading"> Improving ViewState Performance of Recipe 1-12 (VB) </td> </tr> <tr> <td><img src="images/spacer.gif" height="10" border="0"></td> </tr> <tr> <td align="center"> <asp:datagrid id="dgBooks" runat="server" bordercolor="000080" borderwidth="2px" autogeneratecolumns="False" width="100%" allowsorting="True"> <headerstyle horizontalalign="Center" forecolor="#FFFFFF" backcolor="#000080" font-bold=true cssclass="TableHeader" /> <itemstyle backcolor="#FFFFE0" cssclass="TableCellNormal" /> <alternatingitemstyle backcolor="#FFFFFF" cssclass="TableCellAlternating" /> <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> </td> </tr> </table> </form> </body> </html>
Example 16-2. Optimized code-behind forRecipe 1.12 (.vb)
Option Explicit On Option Strict On '----------------------------------------------------------------------------- ' ' Module Name: CH16ViewStatePerformanceVB2.aspx.vb ' ' Description: This module provides the code behind for the ' CH16ViewStatePerformanceVB2.aspx page ' '***************************************************************************** Imports Microsoft.VisualBasic Imports System.Configuration Imports System.Data Imports System.Data.OleDb Imports System.Web.UI.WebControls Namespace ASPNetCookbook.VBExamples Public Class CH16ViewStatePerformanceVB2 Inherits System.Web.UI.Page 'controls on the formProtected WithEvents dgBooks As System.Web.UI.WebControls.DataGrid
Protected pageHeader As ASPNetCookbook.VBExamples.CH04UserControlHeaderVB
'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" '************************************************************************* ' ' ROUTINE: Page_Load ' ' DESCRIPTION: This routine provides the event handler for the page load ' event. It is responsible for initializing the controls ' on the page. '------------------------------------------------------------------------- Private Sub Page_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles MyBase.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)'disable the ViewState for controls that do not need it
disableViewState( )
End If End Sub 'Page_Load '************************************************************************* ' ' ROUTINE: dgBooks_SortCommand ' ' DESCRIPTION: This routine provides the event handler for the datagrid ' sort event. It is responsible re-binding the data to the ' datagrid by the selected column. '------------------------------------------------------------------------- Private Sub dgBooks_SortCommand(ByVal source As Object, _ ByVal e As DataGridSortCommandEventArgs) _ Handles dgBooks.SortCommand 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)'disable the ViewState for controls that do not need it
disableViewState( )
End Sub 'dgBooks_SortCommand '************************************************************************* ' ' ROUTINE: bindData ' ' DESCRIPTION: This routine queries the database for the data to ' displayed and binds it to the datagrid '------------------------------------------------------------------------- Private Sub bindData(ByVal sortExpression As String, _ ByVal sortOrder As enuSortOrder) Dim dbConn As OleDbConnection Dim da As OleDbDataAdapter Dim ds As DataSet Dim strConnection As String Dim strSQL As String Dim index As Integer Dim col As DataGridColumn Dim colImage As String Dim strSortOrder As String Try 'get the connection string from web.config and open a connection 'to the database strConnection = _ ConfigurationSettings.AppSettings("dbConnectionString") 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) ds = New DataSet da.Fill(ds) '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='images/sort_ascending.gif' border='0'>" Else colImage = " <img src='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 = ds dgBooks.DataBind( ) Finally 'cleanup If (Not IsNothing(dbConn)) Then dbConn.Close( ) End If End Try End Sub 'bindData '************************************************************************* ' ' ROUTINE: disableViewState ' ' DESCRIPTION: This routine disables the ViewState for all controls ' on the page that do not need to use it. '-------------------------------------------------------------------------Private Sub disableViewState( )
Dim item As DataGridItem
'disable the ViewState for the page header
pageHeader.EnableViewState = False
'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 'CH16ViewStatePerformanceVB2 End Namespace
Example 16-3. Optimized code-behind for Recipe 1.12 (.cs)
//---------------------------------------------------------------------------- // // Module Name: CH16ViewStatePerformanceCS2.aspx.cs // // Description: This class provides the code behind for // CH16ViewStatePerformanceCS2.aspx // //**************************************************************************** using System; using System.Configuration; using System.Data; using System.Data.OleDb; using System.Web.UI.WebControls; namespace ASPNetCookbook.CSExamples { public class CH16ViewStatePerformanceCS2 : System.Web.UI.Page { // controls on the formprotected System.Web.UI.WebControls.DataGrid dgBooks;
protected ASPNetCookbook.CSExamples.CH04UserControlHeaderCS pageHeader;
// the following enumeration is used to define the sort orders private enum enuSortOrder : int { 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"; //************************************************************************ // // ROUTINE: Page_Load // // DESCRIPTION: This routine provides the event handler for the page // load event. It is responsible for initializing the // controls on the page. //------------------------------------------------------------------------ private void Page_Load(object sender, System.EventArgs e) { String defaultSortExpression; enuSortOrder defaultSortOrder; // wire the event handler for the sort command this.dgBooks.SortCommand += new DataGridSortCommandEventHandler(this.dgBooks_SortCommand); 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 //************************************************************************ // // ROUTINE: dgBooks_SortCommand // // DESCRIPTION: This routine provides the event handler for the // datagrid sort event. It is responsible re-binding // the data to the datagrid by the selected column. //------------------------------------------------------------------------ private 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);// disable the ViewState for controls that do not need it
disableViewState( );
} // dgBooks_SortCommand //************************************************************************ // // ROUTINE: bindData // // DESCRIPTION: This routine queries the database for the data to // displayed and binds it to the repeater //------------------------------------------------------------------------ private void bindData(String sortExpression, enuSortOrder sortOrder) { OleDbConnection dbConn = null; OleDbDataAdapter da = null; DataSet ds = 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 = ConfigurationSettings.AppSettings["dbConnectionString"]; 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); ds = new DataSet( ); da.Fill(ds, "Table"); // 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='images/sort_ascending.gif' border='0'>"; } else { colImage = " <img src='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 = ds; dgBooks.DataBind( ); } // try finally { //clean up if (dbConn != null) { dbConn.Close( ); } } // finally } // bindData //************************************************************************ // // ROUTINE: disableViewState // // DESCRIPTION: This routine disables the ViewState for all controls // on the page that do not need to use it. //------------------------------------------------------------------------private void disableViewState( )
{
// disable the ViewState for the header
pageHeader.EnableViewState = false;
// disable the ViewState for each row in the DataGrid
foreach (DataGridItem item in dgBooks.Items)
{
item.EnableViewState = false;
}
} // disableViewState
} // CH16ViewStatePerformanceCS2 }