Session State

One of the more difficult problems in Web programming is storing per-user state. Suppose you intend to write a site that lets visitors drop items into virtual shopping carts by clicking Add to Cart buttons. It sounds simple enough, but remember: the Web server sees the button clicks as a series of unrelated HTTP requests, and the requests generated by one user are mixed in with similar requests from other users. Finding a place to store the contents of your shopping carts鈥攊n memory on the server, for example, or in hidden <input> fields round-tripped to the client and back鈥攊s only half the battle; the other half involves examining each incoming request, determining whether that request came from a user for whom a shopping cart has been created, and either correlating the request to an existing shopping cart or creating a brand new shopping cart. The challenge is far less trivial than most people realize.

ASP offers a convenient and easy-to-use solution to the per-user-state problem in the form of sessions. When a user who hasn鈥檛 visited an ASP-driven site recently (typically in the last 20 minutes) submits a request to that site, ASP creates a session for that user and returns a cookie that uniquely identifies the session. In subsequent requests from the same user, ASP uses the cookie to correlate the request to the session it created earlier. If hundreds of users browse a site simultaneously, each is assigned his or her own session, and each session implements a data store that鈥檚 exposed to ASP scripts through a session object. Information written to that data store is called session state. One simple statement in an ASP script writes a value to session state or reads it back. And because each session corresponds to exactly one user, data written to session state is stored strictly on a per-user basis.

Despite its elegance, ASP鈥檚 session state implementation suffers from two fatal flaws:

For these reasons, many large sites that rely on ASP either don鈥檛 use session state or use a custom implementation that replaces ASP鈥檚 default session state provider with one of their own.

ASP.NET also uses sessions to enable Web applications to store per-user state. ASP.NET鈥檚 session state implementation is better thought out and more robust, however, and it suffers from none of the shortcomings of ASP session state. It supports a variety of storage models, enabling session state to be physically stored in-process to ASP.NET, in another process, on another machine, or even in a database, and it supports cookieless operation for the benefit of browsers that don鈥檛 support cookies (or that have cookies turned off). All in all, it鈥檚 a huge improvement over ASP and one of ASP.NET鈥檚 greatest strengths. And it鈥檚 the perfect place to store shopping carts or anything else that requires unique storage for each visitor to your site.

Using Session State

Using ASP.NET session state is simplicity itself. Pages access it through the Session property that they inherit from System.Web.UI.Page. Global.asax files access it through the Session property inherited from System.Web.HttpApplication. In both cases, the Session property maps to an instance of System.Web.SessionState.HttpSessionState specially created by ASP.NET to store data for the user who originated the request.

HttpSessionState.Add adds an item to session state. As are items stored in application state or the application cache, an 鈥渋tem鈥?is an instance of any managed type keyed by a string. The following statement adds an item named 鈥?0012552鈥?to session state and assigns it the value 鈥淨uantity=1鈥?

Session.Add聽("10012552",聽"Quantity=1");

Or, if you鈥檇 prefer, you can add it this way:

Session["10012552"]聽=聽"Quantity=1";

The Add method and [] operator are semantically equivalent. In other words, both add items to session state, and both replace an existing item if an existing key is specified.

Retrieving an item from session state is equally painless:

string聽value聽=聽Session["10012552"];

The same can be said about enumerating items and the strings that they鈥檙e keyed with:

NameObjectCollectionBase.KeysCollection聽keys聽=聽Session.Keys;
foreach聽(string聽key聽in聽keys)聽{
聽聽聽聽//聽key聽is聽the聽item's聽key
聽聽聽聽//聽Session[key]聽returns聽the聽item's聽value
聽聽聽聽聽聽聽.
聽聽聽聽聽聽聽.
聽聽聽聽聽聽聽.
}

To remove items from session state, use HttpSessionState鈥檚 Remove, RemoveAt, and RemoveAll methods. You can also use Clear, which is equivalent to RemoveAll.

ASP.NET uses randomly generated session IDs, which aren鈥檛 unlike COM GUIDs (globally unique identifiers), to identify sessions. If you鈥檇 like to know what the ID of a given session is, you can retrieve it from the SessionID property of the corresponding HttpSessionState object. Another interesting HttpSessionState property is IsNewSession, which reveals whether the session ID was generated for the current request (true) or a previous request (false).

Incidentally, if you perform a multistep update on session state and are concerned that a read from another thread at just the wrong time might catch the data in an indeterminate state, don鈥檛 fret. ASP.NET locks session state when an HttpApplication instance fires an AcquireRequestState event and unlocks it following the next ReleaseRequestState event. The practical effect is that it鈥檚 impossible for two requests to read and write session state at the same time, even in the unlikely event that two requests that correspond to the same session overlap each other.

The SessionSpy Page

For a firsthand look at session state in action, check out the Web page in Figure 9-7. Called SessionSpy.aspx, it uses session state to store a count of the number of times a user visits the site. The first time you request the page, you鈥檒l be greeted as a first-time visitor and shown the ID of the session that was created for you. Each time thereafter, you鈥檒l be told how many times you鈥檝e visited the site (that is, requested the page).

The count is a simple integer stored in session state and keyed with the string 鈥淐ount鈥? SessionSpy.aspx reads the HttpSessionState object鈥檚 IsNewSession property to determine whether this access is the first one to the page and its SessionID property to get the session ID.

SessionSpy.aspx
<%@聽Page聽Language="C#" %>

<html>
聽聽<body>
聽聽聽聽<%
聽聽聽聽聽聽if聽(Session.IsNewSession聽鈹傗攤聽Session["Count"]聽==聽null)聽{
聽聽聽聽聽聽聽聽聽聽Session["Count"]聽=聽1;
聽聽聽聽聽聽聽聽聽聽Response.Write聽("Welcome!聽Because聽this聽is聽your聽first " +
聽聽聽聽聽聽聽聽聽聽聽聽聽 "visit聽to聽this聽site,聽a聽new聽session聽has聽been聽created " +
聽聽聽聽聽聽聽聽聽聽聽聽聽 "for聽you.聽Your聽session聽ID聽is " +聽Session.SessionID聽+
聽聽聽聽聽聽聽聽聽聽聽聽聽 ".");
聽聽聽聽聽聽}
聽聽聽聽聽聽else聽{
聽聽聽聽聽聽聽聽聽聽Session["Count"]聽=聽(int)聽Session["Count"]聽+聽1;
聽聽聽聽聽聽聽聽聽聽Response.Write聽("You聽have聽visited聽this聽site " +
聽聽聽聽聽聽聽聽聽聽聽聽聽聽Session["Count"]聽+ " times.聽Your聽session聽ID聽is聽still " +
聽聽聽聽聽聽聽聽聽聽聽聽聽聽Session.SessionID聽+ ".");
聽聽聽聽聽聽}
聽聽聽聽%>
聽聽</body>
</html>
Figure 9-7
The SessionSpy page.

After you鈥檝e played with the page for a few moments, start a second instance of your browser and open SessionSpy.aspx. Because the new browser instance represents a new 鈥渟ession鈥?and doesn鈥檛 share cookies with the first, you鈥檙e greeted as a first-time user. But use your browser鈥檚 New Window command (Ctrl+N in most browsers) to start a third instance, and you鈥檒l be greeted as a returning user. Why? A browser started with the New Window command doesn鈥檛 represent a new session. It shares cookies and other resources with the first instance and thus shares its session state on the server, too.

Cookieless Session State

By default, ASP.NET, like ASP, uses cookies to correlate returning users to sessions on the server. Unlike ASP, ASP.NET supports cookieless session state as well. Cookieless sessions are enabled by adding cookieless=鈥渢rue鈥?to the sessionState element in Web.config or Machine.config:

<sessionState聽cookieless="true" />

How does ASP.NET correlate users and sessions when cookies are disabled? By using URL munging. Check out the screen in Figure 9-8, which was taken after a client retrieved a page from an ASP.NET application running in cookieless mode. The strange-looking value in parentheses in the browser鈥檚 address bar is the session ID. Before returning the page to the client, ASP.NET inserts the session ID into the URL. When the page posts back to the server, ASP.NET strips the session ID from the URL and uses it to associate the request with a session. URL munging isn鈥檛 perfect because there鈥檚 nothing preventing the user from editing the URL and invalidating the session ID. But it鈥檚 better than nothing, and it prevents ASP.NET session state from being unusable with browsers that don鈥檛 honor cookies.

You鈥檙e probably wondering, 鈥淚s there a way I can ask ASP.NET whether cookies are enabled in a browser, and if so, can I enable cookieless operation on the fly if I detect that a request came from a browser that doesn鈥檛 support cookies?鈥?The short answer is no and no. ASP.NET doesn鈥檛 attempt to determine whether a browser supports cookies, so if you want to know, you have to find out yourself. The usual technique is to return a cookie to a browser and use Response.Redirect to redirect to a page that checks for the cookie. If the cookie鈥檚 not there, you know the browser ignored it. Another approach is to return a cookie along with some client-side script that checks for it. Even if you determine at run time that cookies aren鈥檛 supported, however, there鈥檚 not much you can do about it as far as session state is concerned because cookieless operation can鈥檛 be enabled and disabled programmatically. If you want to know whether cookieless session state is in effect, however, you can read the IsCookieless property of any HttpSessionState object.

Bottom line? If you want session state to work with as many browsers as possible, configure your application to use cookieless session state up front. If you don鈥檛 like URL munging and don鈥檛 care that your application might not work properly with browsers that have cookies disabled (many applications test for cookie support and display a warning indicating that they might not work properly if cookies are disabled), then stick with the default: cookieless=鈥渇alse.鈥?/p>

Figure 9-8
URL containing a session ID.
Session State Process Models

Cookieless sessions are an important enhancement to ASP.NET, but even more important are ASP.NET鈥檚 new session state process models. ASP session state is always stored in memory, which makes it incompatible with Web farms (a session鈥攁nd session ID鈥攃reated on server A are undefined on server B). It also means that you lose everything in session state if the Web server goes down. That鈥檚 a big deal to a site like Amazon.com, which at any given time might have millions of dollars worth of potential sales sitting in virtual shopping carts. Web farms are a big deal, too, because setting up clusters of Web servers is a classic and relatively inexpensive way to scale an application to meet the demands of a growing customer base.

That鈥檚 why ASP.NET doesn鈥檛 limit session to memory on the Web server as ASP does. ASP.NET supports three session state process models:

Model

Description

In-proc

Stores session state in-process to ASP.NET (that is, in Aspnet_wp.exe)

State server

Stores session state in an external 鈥渟tate server鈥?process on the Web server or on a remote machine

SQL Server

Stores session state in a Microsoft SQL Server database on the Web server or on a remote machine

The default is in-proc, which is very ASP-like, but simple configuration changes applied via Web.config or Machine.config switch to the state server or SQL Server model and get session state out of the ASP.NET worker process Aspnet_wp.exe and into the location of your choosing. The sections that follow describe the necessary configuration changes and also shed light on the pros and cons of the individual process models.

In-Proc Session State

In-proc session state is the fastest, but it鈥檚 also the least robust; restart IIS or reboot your Web server and in-proc session state goes away for good. In-proc is the default because of the following statement in Machine.config:

<sessionState聽...聽mode="InProc"聽/>

To be absolutely certain that in-proc session state is in effect regardless of what might be in Machine.config, add a Web.config file to your application鈥檚 virtual root directory and include this statement in it:

<sessionState聽mode="InProc"聽/>

In-proc session state is appropriate when you prefer speed to robustness and your application runs on a single server rather than a Web farm.

State Server Session State

The state server session state model is new in ASP.NET. It moves session state out of Aspnet_wp.exe and into a dedicated 鈥渟tate server鈥?process managed by the system. The state server is actually a running instance of a service named Aspnet_state.exe. If you want to use the state server model, you must do the following:

  • Start Aspnet_state.exe. You can start it manually (from the command line) by executing the following command:

    net聽start聽aspnet_state

    Or you can configure it to start automatically each time the system is started by using Windows鈥?Services control panel applet (Figure 9-9).

  • Add a mode=鈥淪tateServer鈥?attribute and a stateConnectionString attribute to the sessionState element in Machine.config or a local Web.config file. The latter of these two attributes identifies the machine that hosts the state server process.

    Figure 9-9
    The ASP.NET State service.

Here鈥檚 a Web.config file that configures an application to store session state in a state server process on the same Web server that hosts the application:

<configuration>
聽聽<system.web>
聽聽聽聽<sessionState
聽聽聽聽聽聽mode="StateServer"
聽聽聽聽聽聽stateConnectionString="tcpip=localhost:42424"
聽聽聽聽/>
聽聽</system.web>
</configuration>

And here鈥檚 one that places session state in a state server process on another machine identified by IP address:

<configuration>
聽聽<system.web>
聽聽聽聽<sessionState
聽聽聽聽聽聽mode="StateServer"
聽聽聽聽聽聽stateConnectionString="tcpip=192.168.1.2:42424"
聽聽聽聽/>
聽聽</system.web>
</configuration>

By default, ASP.NET uses port 42424 to communicate with the state server process. That鈥檚 why 鈥?2424鈥?appears in the state connection string. In the unlikely event that 42424 conflicts with another application on your Web server, you can change the port number by doing the following:

  • Add the desired port number to the registry at HKEY_LOCAL_ MACHINE\System\CurrentControlSet\Services\aspnet_state\ Parameters\Port.

  • Replace 42424 with the new port number in stateConnectionString.

As an example, here鈥檚 a Web.config file that changes the session state mode to 鈥淪tateServer鈥?and directs ASP.NET to use port 31313 to connect Aspnet_wp.exe to Aspnet_state.exe:

<configuration>
聽聽<system.web>
聽聽聽聽<sessionState
聽聽聽聽聽聽mode="StateServer"
聽聽聽聽聽聽stateConnectionString="tcpip=192.168.1.2:31313"
聽聽聽聽/>
聽聽</system.web>
</configuration>

The state server model is slower than in-proc session state because data read from and written to session state must travel across process or machine boundaries. However, the state server model prevents session state from being lost if IIS is restarted, and should the state server process be on another machine, it even allows session state to survive if the entire Web server is rebooted. Switching to the state server model is also one way to build ASP.NET applications that work with Web farms.

Figure 9-10 illustrates how the state server model solves the Web farm compatibility problem. In this example, the Web farm contains three servers. The application runs in worker processes on all three servers, but each server is configured to store session state in a state server process on a fourth machine. It鈥檚 perfectly acceptable now for data to be written to session state on server A and read back on server B because both servers refer to machine D for their session state. If you don鈥檛 want to dedicate a machine to host the state server process, no problem: simply designate one of the Web servers as the state server and point the other servers to it with stateConnectionString.

Figure 9-10
Web farm with session state in a remote state server process.
SQL Server Session State

The SQL Server process model offers the ultimate in scalability and reliability. Like the state server model, it moves session state out of Aspnet_wp.exe. But rather than store session state in an external process, the SQL Server model stores it in a Microsoft SQL Server database. You gain Web farm compatibility because you can point all your servers to a common back-end machine (the one that hosts the database). You also achieve robustness because session state is preserved no matter what鈥攅ven if IIS is restarted or the Web server is reboot颅ed. If SQL Server is clustered or otherwise configured to survive hard failures, you can reboot the database server itself and still preserve the contents of session state.

Configuring ASP.NET to use the SQL Server process model is a breeze. Here are the steps required:

  • Create the database that holds the session state. The .NET Framework SDK provides a script that creates the database for you; it鈥檚 called InstallSqlState.sql. To run it, open a command prompt window and type the following command:

    osql聽-S聽localhost聽-U聽sa聽-P聽-i聽installsqlstate.sql

    This command creates a SQL Server database named ASPState on the host machine and adds to it all the tables, stored procedures, and other infrastructure that ASP.NET uses to access the database, as shown in Figure 9-11.

  • Add a mode=鈥淪QLServer鈥?attribute and a sqlConnectionString attribute to the sessionState element in Machine.config or a local Web.config file. The latter of these two attributes provides the information ASP.NET needs to connect to the database.

    Figure 9-11
    The ASPState database.

The following Web.config file configures an ASP.NET application to store session state in a SQL Server database on the Web server:

<configuration>
聽聽<system.web>
聽聽聽聽<sessionState
聽聽聽聽聽聽mode="SQLServer"
聽聽聽聽聽聽sqlConnectionString="server=localhost;uid=sa;pwd="
聽聽聽聽/>
聽聽</system.web>
</configuration>

The next one does the same, but it points ASP.NET to a SQL Server database on a remote machine named 鈥淗awkeye鈥?

<configuration>
聽聽<system.web>
聽聽聽聽<sessionState
聽聽聽聽聽聽mode="SQLServer"
聽聽聽聽聽聽sqlConnectionString="server=hawkeye;uid=sa;pwd="
聽聽聽聽/>
聽聽</system.web>
</configuration>

Performance-wise, the SQL Server model is the slowest of them all, but in return for speed it virtually guarantees that session state won鈥檛 be lost. Figure 9-12 shows how the SQL Server option factors into Web farms. A dedicated server holds the state database, and all the Web servers point ASP.NET to that database. If you intend to use ASP.NET to build large, industrial-strength e-commerce sites, this is the architecture you want.

Figure 9-12
Web farm with session state in a remote SQL Server database.
State Servers, SQL Servers, and Serializable Objects

One gotcha to be aware of if you plan to use the state server or SQL Server session state model is that both require types stored in session state to be serializable. Serialization is the process of writing out the data that defines an object instance to a designated storage medium for the purpose of re-creating, or rehydrating, it at a later time or in another place. ASP.NET must be able to serialize objects stored in session state if it鈥檚 to transfer them to a state server process or a SQL Server database. And it must be able to deserialize them if they鈥檙e to be read back. Nonserializable types work just fine with the in-proc session state model, but try to write a nonserializable type to session state using either of the other process models and ASP.NET will throw an exception.

Here鈥檚 an example to help clarify. Suppose you write a class named ShoppingCart to serve as a virtual container for items that users select from your site:

public聽class聽ShoppingCart
{
聽聽...
}

Defined this way, a ShoppingCart instance can be written to session state without any problem as long as session state is stored in-proc:

ShoppingCart聽cart聽=聽new聽ShoppingCart聽();
Session["MyShoppingCart"]聽=聽cart;

But the same code throws an exception if you switch to the state server or SQL Server model. To remedy that, make the class serializable by adding a Serializable attribute:

[Serializable]
public聽class聽ShoppingCart
{
聽聽...
}

This quick-and-easy change enables ASP.NET to serialize and deserialize ShoppingCart objects using System.Runtime.Serialization.Formatters.Binary.BinaryFormatter, better known as the .NET Framework鈥檚 binary formatter. When you create custom data types with the intention of storing them in session state, always include a Serializable attribute unless you鈥檙e certain you鈥檒l only use the types in-proc. It鈥檚 never harmful, and it will pay off in spades if you or anyone else attempts to write an instance of the class to session state in an application that uses the state server or SQL Server model.

Session Lifetimes

ASP.NET creates sessions for you. It also deletes them (in reality, hands them over to the garbage collector) when it鈥檚 done. Knowing when to delete them requires a bit of guesswork. ASP.NET can鈥檛 know definitively when a session is no longer needed because it has no way of knowing whether a given request is that user鈥檚 last. So it does the next best thing: if a prescribed time period elapses and ASP.NET receives no requests from a user for whom a session was created, it discards the corresponding session. The default time-out period is 20 minutes, as specified in Machine.config:

<sessionState聽...聽timeout="20" />

You can change the session time-out period in three ways. Option number one is to edit Machine.config. Option number two is to place a statement like this one, which sets the session time-out to 60 minutes, in a local Web.config file:

<sessionState聽timeout="60" />

And option number three is to write a time-out value (in minutes) to the Timeout property of an HttpSessionState object:

Session.Timeout聽=聽60;

Which route you should choose depends on the desired scope of the change. Setting the time-out interval in Machine.config changes the default for all ASP.NET applications on the Web server. Setting it in a local Web.config file changes it for a single application, and setting it with Session.Timeout changes it for an individual session. The proper time-out interval is both subjective and application-specific. Twenty minutes is fine for most applications, but if you鈥檇 like a user to be able to go out to lunch and come back to find his or her shopping cart still full (assuming you鈥檙e storing shopping carts in session state), then you might want to up the time-out interval to an hour or more.

You can see session time-outs in action by calling up Figure 9-7鈥檚 SessionSpy.aspx in your browser, refreshing it a time or two, waiting for 20 minutes, and then refreshing the page again. Because your session timed out while you were away, you鈥檒l be greeted as a first-time visitor. Increase the session time-out, and you鈥檒l be able to stay away for longer periods of time.

An application can explicitly close a session by calling the session鈥檚 Abandon method:

Session.Abandon聽();

This option is sometimes used by sites that permit users to log out (or that forcibly log them out) after completing a transaction.

Disabling Session State

Session state can also be disabled altogether. Session state exacts a modest price in both memory and performance, so if you don鈥檛 use it, you should disable it. You can disable session state for an individual page (ASPX file) with the following @ Page directive:

<%@聽Page聽EnableSessionState="false" %>

You can disable it for an entire application by including this statement in Web.config:

<sessionState聽mode="Off" />

Or you can disable it for all applications by adding a mode=鈥淥ff鈥?attribute to the sessionState element in Machine.config.

A Word on Web Farms

Moving session state to a remote machine and pointing all your Web servers to that machine is essential to building Web applications that are compatible with server farms. But there鈥檚 something else you have to do to deploy an ASP.NET application on a Web farm. First, a bit of background.

Each server鈥檚 Machine.config file contains a machineKey element that assigns values to a pair of cryptographic keys:

<machineKey聽...聽validationKey="AutoGenerate"聽decryptionKey="AutoGenerate" />

When configured to prevent tampering by appending hashes to view state values and forms authentication cookies (a topic I鈥檒l cover in Chapter 10), ASP.NET uses validationKey to generate the hashes. If the protection level is sufficiently high, ASP.NET goes even further and uses decryptionKey to encrypt view state and authentication cookies. 鈥淎utoGenerate鈥?tells ASP.NET to generate a random key and store it in the host machine鈥檚 Local Security Authority (LSA). Randomly generated keys are fine for single-server installations, but in a Web farm, each server must use identical keys; otherwise, a value encrypted on one machine can鈥檛 be unencrypted on another.

Before deploying an ASP.NET application on a Web farm, you should make the following configuration change on every server in the Web farm. The change can be made in Machine.config or in a local Web.config file:

Here鈥檚 a sample Web.config file that, if used on every server on which your application is installed, configures each server to use identical validation and encryption keys:

<configuration>
聽聽<system.web>
聽聽聽聽<machineKey
聽聽聽聽聽聽validationKey="DD2B3BB0B07F4FE6917B60DAFEB0D01532C1C3BB07F533A1"
聽聽聽聽聽聽decryptionKey="C89EFEF650CA4D9C9BC986061211329A9717DC2260BC6199"
聽聽聽聽/>
聽聽</system.web>
</configuration>

Values for validationKey and decryptionKey should by cryptographically strong to make values encrypted with them difficult to break. Various tools are available for producing cryptographically strong keys. You can even write your own key generator using the FCL鈥檚 System.Security.Cryptography.RNGCryptoServiceProvider class. (RNG stands for Random Number Generator.) However you derive your keys, be sure to apply them in a CONFIG file or your Web farm鈥揷ompatible application might not be so Web farm鈥揷ompatible after all.