Database Design

Manage Client State

Working with and understanding how client state is used in your app is critical to putting a good design into practice. Examine three techniques for managing client state: ses-sion state, view state, and cookies.

Technology Toolbox: VB.NET, C#, SQL Server 2000, ASP.NET

Editor's Note: This article is excerpted from Chapter 5 of Matthew Gibbs' and Rob Howard's book, Microsoft ASP.NET Coding Strategies with the Microsoft ASP.NET Team [Microsoft Press, ISBN: 073561900X].

Working with and understanding how client state is used in your application is critical to putting a good design into practice. In this article, we'll examine three techniques for managing client state: session state, view state and cookies. The most common type of client state is session state.

ASP.NET session is free-threaded, but in some cases you can access it serially. Session state in ASP.NET still utilizes an HTTP cookie for managing the SessionID, but ASP.NET also supports storing the SessionID in the URL if using cookies is not desirable. ASP.NET session state also supports two out-of-process modes to simplify deployment in Web server farms: out-of-process state server (StateServer) and out-of-process SQL Server (SQLServer).

ASP.NET defaults to in-process (InProc) session state. When in this mode, values stored within session state don't require serialization support and are stored within the memory space of the ASP.NET worker process. This behavior is identical to the way ASP stores its session data. However, instead of the data being stored in the IIS process, the data is stored in managed memory within the ASP.NET worker process.

When stored in-process, session state data is lost whenever the process is recycled. In Windows Server 2003 running IIS 6, the worker process recycles automatically every 29 hours, which is the default setting and is configurable. However, this means that the session data will be lost every 29 hours, whether it's 2 a.m. or 3 p.m.

InProc is by far the fastest way to use session state. It doesn't support Web farm scenarios (unless you enforce client affinity), but it also doesn't have the serialization and deserialization overhead associated with out-of-process modes. It's safe to assume that out-of-process session state is 15 to 30 percent slower (depending upon variables such as network speed and the size of the object or objects being serialized).

Use in-process session state if you have only a single server. In IIS 6, either use out-of-process or disable process recycling behavior to avoid data loss. This code shows the configuration settings from machine.config that specify the default settings for session state:

<configuration>
   <system.web>
      <sessionState mode="InProc"
         stateConnection
         String="tcpip=127.0.0.1:42424"
         stateNetworkTimeout="10"
         sqlConnectionString="..."
         cookieless="false"
         timeout="20" />
   </system.web>
</configuration>

The timeout value specifies the time, in minutes, after which a session is considered timed out and its values can be removed. Session state uses a sliding expiration: The timeout is reset each time the item is requested. A session could theoretically be kept alive indefinitely if a request was made just once before the value in the timeout is reached.

InProc session state allows any data type to be stored, and it participates in the global session events Session_OnStart, which is raised when a new session is created; and Session_OnEnd, which is raised when a session is abandoned. You can program these events in either global.asax or within an HTTP module. Don't use the Session_End event; it can be called only for sessions created in the InProc mode. The event is not raised for sessions created in one of the out-of-process modes when sessions are abandoned.

ASP.NET session state supports two out-of-process options: state server (StateServer) and SQL Server (SQLServer). Each has its own configuration settings and idiosyncrasies to contend with, such as managing stored types. The ASP.NET state server is recommended for medium-sized Web applications, and SQL Server is recommended for enterprise-size or highly transactional Web applications. It's important that the programming model is transparent. For example, you don't have to change how you access or use session state when you change the storage mode.

We recommend SQLServer for out-of-process session state because it's as fast as StateServer, and SQL Server is excellent at managing data. Furthermore, ASP.NET can communicate with SQL Server natively (meaning internally, using the System.Data.SqlClient libraries), and you can configure SQL Server to support data failover scenarios. StateServer works well in cases where SQLServer isn't available, but unfortunately it doesn't support data replication or failover scenarios.

Manage Types for Out-of-Process Modes
One of your major costs when using an out-of-process mode is the serialization and deserialization of items stored. Using an optimized internal method, ASP.NET performs the serialization and deserialization of certain "basic" types, including numeric types of all sizes, such as Int, Byte, and Decimal, as well as several non-numeric types, such as String, DateTime, TimeSpan, Guid, IntPtr, and UintPtr.

If you have a session variable that isn't one of the basic types, ASP.NET will serialize and deserialize it using the BinaryFormatter, which is relatively slower than the internal method. If you've created a custom class and want to store it in session state, you must mark it with the [Serializable] metadata attribute or implement the ISerializable interface. ([Serializable] is the C# metadata attribute. <Serializable()> is the VB.NET metadata attribute.) The SerializableAttribute class is defined in the mscorlib.dll assembly within the System namespace. The ISerializable interface is defined in the assembly mscorlib.dll and within the System.Runtime.Serialization namespace. When a class is marked with the SerializableAttribute, all public members will attempt to be serialized. If the class contains references to other objects, you must mark those objects with the SerializableAttribute or implement ISerializable. Implementing ISerializable gives you more control over how the serialization and deserialization of your class take place.

For the sake of performance, you're better off storing all session state data using only one of the basic data types. For example, you can store a name and address in session state using two String session variables, which is the most efficient method; or you can create a class with two String members and store that class object in a session variable, which is more costly.

Store only basic data types in session state; avoid storing complex types or custom classes. Storing basic data types decreases the serialization and deserialization costs associated with out-of-process session, as well as reduces the complexity of the system.

The StateServer out-of-process mode relies on a running Windows NT Service as well as changes to the default configuration settings. This code shows machine.config with the necessary configuration settings for StateServer. Note that the mode attribute is set to StateServer. The stateConnectionString and stateNetworkTimeout settings are required values for StateServer mode:

<configuration>
   <system.web>
      <sessionState mode="StateServer"
         stateConnection
         String="tcpip=127.0.0.1:42424"
         stateNetworkTimeout="10"
         cookieless="false"
         timeout="20"/>
   </system.web>
</configuration

When ASP.NET is configured to use state server for out-of-process session, it uses a TCP/IP address and port number to send HTTP requests to the state server (which is in fact a lightweight Web server running as a Microsoft Windows Service).

You must change the IP address (in stateConnectionString) to the IP address of the machine running the ASP.NET state service. You should also change the port (the default is 42424) unless the state service is running behind a firewall (which it should be). You can configure the port number on the machine running the service by editing the registry and changing the value of the port setting found here:

HKLM\SYSTEM\CurrentControlSet\Services   aspnet_state\Parameters\

The default setting for the port is 0x0000A5B8 in hexadecimal, or 42424 in base 10 (see Figure 1).

If the server running the state service is accessible outside the firewall, you should change the port address of the state service to a value other than the default. In ASP.NET 1.1, only local machines can connect to the state server for security reasons. Allow only non-local host requests in ASP.NET 1.1 by opening the same registry entry listed earlier for the port setting: HKLM\SYSTEM\CurrentControlSet\Services \aspnet_state\Parameters\. Change AllowRemoteConnection to 1.

The value of stateNetworkTimeout represents the number of seconds that may elapse between the time ASP.NET tries to connect to the state service and the time the request times out. You don't need to change the default value for stateNetworkTimeout, but you have the option to make the value higher or lower depending upon your requirements.

Once you've properly configured the server designated to run the state server, it's simply a matter of starting the Windows service. You can start the service from either the command line or the Microsoft Management Console (MMC) for Services. Starting the state service from the command line is simple. Navigate to the .NET Framework installation directory. For version 1, this is [system drive]\WINDOWS\Microsoft.NET\Framework\ v1.0.3705\. Start the server by executing a net start command:

net start aspnet_state

After starting the service, you should see this text: "The ASP.NET State Service service is starting. The ASP.NET State Service service was started successfully."

The second option for starting the state service is through the Services MMC snap-in, which you open by navigating to Start | Administrative Tools | Services. Right-click on the ASP.NET State Service option in the list and select Start to start the service (see Figure 2).

SQL Server is an enterprise-class database solution optimized for managing, storing, and retrieving data quickly and efficiently. It is also capable of replication and clustering. You can configure SQL Server to failover in a clustered environment. For example, a backup can take over when the clustered production SQL Server fails.

Note that clustered SQL Server scenarios aren't supported out of the box for ASP.NET session state. Session data must be stored in a non-tempDB table to enable the clustering or replication features of SQL Server.

You don't need to make any special changes to the code to use SQL Server as the session state store. This code shows the necessary configuration file machine.config for SQL Server:

<configuration>
   <system.web>
      <sessionState mode="SQLServer"
         sqlConnection
         String="database=[ServerName];
         Trusted_Connection=true"
         cookieless="false"
         timeout="20"/>
   </system.web>
</configuration>

The mode attribute needs to be SQLServer, and the sqlConnectionString attribute must point to a server running SQL Server that has been configured already for ASP.NET SQL session state.

For ASP.NET 1, configure SQL Server for mixed-mode authentication by adding the ASPNET account enabled for the necessary SQL Server permissions (EXECUTE) for ASP.NET session state. (The ASPNET account is the user that the ASP.NET worker process runs as.) For ASP.NET 1.1 running on IIS 6, configure SQL Server for mixed-mode authentication by adding the NT AUTHORITY\NETWORK SERVICE account.

You should use integrated authentication if the account has the necessary permissions. This prevents the need to store a username and password in clear text within the configuration. When you use integrated authentication, ASP.NET accesses SQL Server using the credentials of the Windows user that the worker process runs as. By default, these credentials are ASPNET and NT AUTHORITY\NETWORK SERVICE on Windows Server 2003 running IIS 6.

Use integrated authentication rather than store SQL Server credentials within your configuration file. If you decide to use SQL Server usernames and passwords, don't use the system administrator (sa) account. Instead, use an account that has only the necessary access to the database object required for the operations (for session state, this account is EXECUTE only). If you must use SQL Server credentials, ASP.NET 1.1 supports storing credentials securely.

Configure SQL Server to support ASP.NET session state either by opening the InstallSqlState.sql file in isqlw.exe (Microsoft SQL Server Query Analyzer) or by using the command-line tool osql.exe. To use SQL Server Query Analyzer, go to the Start menu and navigate to All Programs | Microsoft SQL Server | Query Analyzer (see Figure 3). Ensure SQL Server Agent is running before running the SQL Scripts. The agent runs a periodic job to purge expired sessions from the database.

If you're running ASP.NET 1, go to File | Open [system drive] | WINDOWS | Microsoft.NET | Framework | v1.0.3705 | InstallSqlState.sql. If you're running ASP.NET 1.1, navigate to [system drive]\WINDOWS\Microsoft.NET\Framework\v1.0.4322\ directory and open the same file. Execute the script either by clicking on the Play button or by pressing F5. Using the command-line tool (osql.exe), open a command window and then navigate to \[system drive]\WINDOWS\Microsoft.NET\Framework\[ASP.NET version]\.

If integrated authentication is enabled for SQL Server and the current Windows logged-on user has permissions to SQL Server, type osql -E < InstallSqlState.sql. If SQL Server mixed-mode authentication is enabled and the current logged-on user doesn't have permissions within SQL Server, specify a username and password using osql - U [sql user] - P [password] < InstallSqlState.sql. After running the SQL Script, SQL Server is configured to support ASP.NET session state. Two tables are created within the tempdb database: ASPStateTempApplications and ASPStateTempSessions.

Configure Session Data in a Web Farm
The major benefit of out-of-process session is that it no longer requires client/server affinity. You can configure servers in an ASP.NET Web server farm to share session data. (However, individual applications cannot share session data because a session is tied to a single application.)

To configure session support in a Web farm, you must take one additional step regardless of whether a session is even used: You must configure the machineKey settings. You can configure machine-wide settings in the machine.config file ([Windows Directory] | Microsoft.NET | Framework | [Versions] | Config\). The machineKey settings store the validationKey and the decryptionKey attribute values, which are used in many scenarios—for example, ViewState, Forms authentication, and session—to provide encryption and validation of values sent back and forth from client to server.

The values for the validationKey and decryptionKey attributes are set to AutoGenerate by default, which enables the server to create the values randomly. Randomly selected values work well in a single server environment; however, in a Web server farm in which there is no guarantee which server will satisfy the client's request, the values for validationKey and decryptionKey must be precise and predictable.

When using out-of-process session, ASP.NET makes two requests to the out-of-process session store for every one page requested (if the request comes in with a session ID). When the request first starts, the session state module connects to the state store, reads the session state data, and marks the session as locked. The page is executed at this point, and the Session object is accessible. When the page completes execution, the session state module connects to the state store, writes the session state data if it changed, and unlocks the session data.

The locking mechanism implemented is a reader-writer lock using the ReaderWriterLock class. Multiple requests can read data simultaneously when the session isn't locked. If the session is locked, read requests are blocked until the write lock is released. This locking strategy guarantees that the Session object is always an accurate reflection of the data. If you attempt to build a site that uses frames, and each page within a frame requires session state, the pages will execute serially. You can configure session state to be read-only on a page-by-page or even application basis. Configuring a page to use session data in a read-only manner allows the page to be requested without locking the session data and prevents serialized access.

If session is configured as read-only in out-of-process mode, the session module doesn't need to go back to the session store to release the lock. Multiple requests can read session data simultaneously without serialized access, which yields better throughput. Configure the read-only option on a page-by-page basis by setting the page-level directive EnableSessionState:

<%@ Page EnableSessionState="ReadOnly" %>

Another option is to configure session to enableSessionState="false" as the default setting (you can change this in web.config or machine.config) and use EnableSessionState="ReadOnly" or EnableSessionState="true" at the page level. Here's the code for disabling session state:

<configuration>
   <system.web>
      <pages enableSessionState="false" />
   </system.web>
</configuration>

For out-of-process session, set session state to enableSessionState="false" within the configuration file and set the EnableSessionState page directives to either true or ReadOnly based on what behavior is needed. Note that the length of the session will still be reset, even when it's set to false. When you apply this strategy for optimizing out-of-process session, you get fewer requests to the out-of-process session store, which increases the scalability and throughput of the site.

Session state, to HTTP purists, is a frowned-upon but necessary feature for building real-world Web applications. Session state was designed to work around the limitations of the stateless nature of HTTP. The browser and the server must share a common piece of data: the SessionID. This shared value must be stored somewhere. Solve this problem by taking advantage of another HTTP feature known as a cookie. A cookie is a much debated feature supported by all browsers that allows the server to store a small amount of data, private to the server, on the client. Upon each client request to the server, the browser sends along any cookie data belonging to that server.

For both ASP and ASP.NET, the SessionID is stored within a cookie. When the client makes requests to the server, the client presents the cookie, giving ASP.NET the opportunity to fetch any associated session data belonging to the presented SessionID. Using the SessionID as a key for user data is not recommended. The SessionID is generated randomly, and session data—as well as session IDs—do expire. Additionally, although a SessionID might be generated on each request, a SessionID is set only when a Session value is set server side. This means that if no session values are set server side, new SessionIDs are issued on each request.

Storing the SessionID in a cookie works well except when the client chooses not to accept cookies. When cookies aren't supported, ASP.NET provides a cookieless option in which the SessionID is stored within the URL instead of an HTTP cookie.

You cannot configure an ASP.NET Web application to support both cookie and cookieless SessionID storage; that is, the application cannot dynamically choose whether to use cookies. You must carefully design navigation in the user interface when building applications to take advantage of a cookieless session. Any links within the site that are not relative (those starting with http://) will cause the user to lose his or her session when clicked. For relative URLs (for example, /MyStore/default.aspx), ASP.NET adds the embedded SessionID automatically when generating the page output.

Use ViewState to Store State
Web application communication takes place over HTTP, a stateless protocol. The ASP.NET session state feature circumvents the stateless nature of HTTP by storing its SessionID in an HTTP cookie or embedding it within the URL of the page. This shared session key is then used to associate data stored on the server with the browser making the request.

In some cases, it isn't necessary or desirable to require session state. A common technique is to store data in hidden form fields like this: <input type="hidden" value="some value here"/>. When the client submits the page and causes either an HTTP POST or GET request to the server, this data, along with other input form data, is sent to the server in either the POST body or the query string.

ASP.NET has taken this concept of hidden input form fields and utilized them for maintaining state for pages that participate in postback. (All ASP.NET pages that use <form runat="server"/> send the contents of the form back to the same page. Additionally, the action attribute of form is ignored when the form is marked with runat="server".) This feature is known as view state; data is stored in a special hidden <input type="hidden" name="__VIEWSTATE"/> form element. The data stored in the value attribute of the __VIEWSTATE form element consists of a base-64 encoded string that contains all the serialized ViewState data for the current page plus a Message Authentication Code (MAC). When the page is posted back to the server, the ASP.NET page framework deserializes the data in __VIEWSTATE and repopulates the ViewState state bag automatically. Data added to the view state is available when the page is posted back again. View state is useful for building complex server controls because data not usually sent as a form element can be stored in the view state and retrieved when the page is posted back.

A MAC is a key-dependent, one-way hash. A MAC is used to verify ViewState data by recomputing the MAC on postback and comparing it to the MAC stored in __VIEWSTATE. If the MACs match, the data in __VIEWSTATE is valid. If they don't match, the ViewState data is invalid and an exception is thrown.

ViewState data is accessible in much the same way that Session data is; both use a key to set or retrieve data. However, unlike Session, ViewState data is available only in pages that utilize <form runat="server"/>, which causes the page to perform a postback. The data types that can be stored in ViewState are limited to Int32, Boolean, String, Unit, and Color. Data types other than these incur significant overhead and must first either be converted to a string or be serialized using the same binary serializer used by session state.

The view state can be extremely useful in cases in which there is a costly piece of data to fetch that is necessary for the duration of the page (where duration of the page is equal to the first request and all postbacks). A great example of this is in the source code for the ASP.NET Forums. One of the controls used frequently within the Forums is a server control used for paging data. This server control (Paging.cs) allows the user to page through multiple records of data in SQL Server. One of the paging control's tasks is to keep track of the total number of available records. Using this number and the requested page size, the control can calculate the total number of pages available. The total records available are not computed on each request—instead, this data is fetched once and stored in view state, alleviating the stress on the server from making multiple requests to the database for the same information.

This code snippet from the Paging.cs file demonstrates this technique—the full source is available as part of the ASP.NET Forums:

/// <summary>
/// TotalRecords available
/// </summary>
public int TotalRecords {
   get {
      // The total records available is stuffed 
      // into ViewState so that we don't pay the 
      // lookup cost across postbacks.
      if (ViewState["totalRecords"] == null)
         return defaultTotalRecords; // 0
      return Convert.ToInt32(
         ViewState["totalRecords"].
         ToString());
   }
   set {
   // Recalculate the total number of pages in 
   // case page size changed
   TotalPages = CalculateTotalPages(
      value, PageSize);
   // set the ViewState
   ViewState["totalRecords"] = value;
   }
}

ViewState's Liability
Using ViewState does have a liability: It increases the total size of the page you must create. Although it doesn't affect the UI generated by the page, the HTML payload can increase dramatically depending upon how much view state the page uses. We recommend that you disable ViewState for pages or controls that don't require it.

You can disable the view state in a page by using <%@ Page EnableViewState="false" %>, or in a control by specifying Page.EnableViewState="false" on the server control. Without disabling view state, this code sample demonstrates using the DataGrid server control bound to a DataSet to serialize the XML document BookData.xml to base64 in an XML attribute value:

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Data" %>
<script runat="server">
   public void Page_Load (
      Object sender, EventArgs e) {
      // Load some data
      DataSet ds = new DataSet();
      ds.ReadXml(Server.MapPath("BookData.xml"));
      // Now databind to the datagrid
      DataGrid1.DataSource = ds;
      DataGrid1.DataBind();
   }
</script>
<form runat="server">
<asp:Button runat="server" Text="PostBack" />
</form>
<asp:DataGrid id="DataGrid1" runat="server" />

When this page is requested and the HTML source is viewed, the value for __VIEWSTATE contains this:

<input type="hidden" name="__VIEWSTATE" 
value="dDwxMzg3MzYyMzg7dDw7bDxpPDI+Oz47bDx0PEAwPHA
8cDxsPERhdGFLZXlzO18hSXRlbUNvdW5[?30 lines re
moved?]z47dDxwPHA8bDxUZXh0Oz47bDxQYXJpczs+Pjs+Ozs+
O3Q8cDxwPGw8VGV4dDs+O2w8Jm5ic3BcOzs+Pjs+O 
zs+O3Q8cDxwPGw8VGV4dDs+O2w8RnJhbmNlOz4+Oz47Oz47Pj4
7Pj47Pj47Pj47Phd0PzYb9Lz7N2ZqMReiGAMMnwyz" />

In this case, you're not using a view state, so you should disable it by adding an EnableViewState attribute to DataGrid:

<asp:DataGrid id="DataGrid1" runat="server" 
   EnableViewState="false" />

Now when this page is requested, the value for __VIEWSTATE is more reasonable:

<input type="hidden" name="__VIEWSTATE" 
value="dDwxMzg3MzYyMzg7O z6TQ21xg8KTWseIQ341mOOdKXg
uIw==" />

View state is a powerful technique for managing state for pages that participate in postback. However, be aware that view state has an associated cost that can easily increase the size of your page output.

Use Cookies for Client State Management
The last technique we'll examine for managing client state is the cookie. Unbeknownst to many Web developers, cookies are not an approved standard, although all major browsers support them and all Web application development technologies use them. ASP.NET utilizes cookies for two tasks: session state and forms authentication. The associated cookie for session state is .ASPXSession. The cookie stores the SessionID used to associate the request with its session data. The associated cookie for forms authentication is .ASPXAUTH. The cookie stores encrypted credentials. Credentials can be decrypted and the user re-authenticated.

You can view all the cookies on your system by opening Internet Explorer and selecting Tools | Internet Options to open the Internet Options dialog box. Click on the Settings button to open the Settings dialog box. Click on the View Files button to open Explorer and access your temporary Internet files directory. You can then sort by type Text Document or by items named Cookie.

Cookies are a great way to manage state if you can guarantee that your clients use them. They can store multiple name/value combinations as long as the value is of type string (or can be converted to string). The only limitation with cookies is the amount of data that can be stored; most browsers support a maximum cookie size of 4K.

Working with cookies in ASP.NET is simple. For example, you can use them when storing the roles that a user belongs to. Rather than fetching the user roles on each request from the database, you fetch the user roles only if a specific UserRoles cookie doesn't exist. Then create the UserRoles cookie and add the roles the user belongs to. On subsequent requests, simply open the UserRoles cookie, extract the roles, and add them to the roles the current user belongs to (see Listing 1).

The first method, Application_AuthenticateRequest, is an event delegate that gets called when ASP.NET is ready to authenticate the request. Within this method, check to see whether you have a cookie named UserRoles and whether it has a value.

If the cookie isn't found, load the roles for the user and then call the CreateRolesCookie method, passing in a string of role names. Within CreateRolesCookie, format the string into a semicolon-delimited string, encrypt it using APIs from Forms Authentication, and then store the encrypted data in the UserRoles cookie.

If the UserRoles cookie is found, first decrypt the value of the cookie, ensure that the user the cookie belongs to is the same user who is currently logged in, split the roles using a semicolon as the delimiter, and finally create a new GenericPrinicpal (authenticated identity), passing in the roles as one of the arguments.

We took a look at three techniques for managing client state: session state, view state, and cookies. Choosing the appropriate client state management technique depends on what you need to accomplish within your application. ASP.NET provides you with easy-to-use APIs for working with the three techniques.

comments powered by Disqus
Most   Popular
Upcoming Events

.NET Insight

Sign up for our newsletter.

Terms and Privacy Policy consent

I agree to this site's Privacy Policy.