ASP.NET State Management : Working with a Session’s State (part 2) – Lifetime of a Session

Lifetime of a Session

The life of a session state begins only when the first item is added to the in-memory dictionary. The following code demonstrates how to modify an item in the session dictionary. “MyData” is the key that uniquely identifies the value. If a key named “MyData” already exists in the dictionary, the existing value is overwritten.

Session["MyData"] = "I love ASP.NET";

The Session dictionary generically contains object types; to read data back, you need to cast the returned values to a more specific type:

string tmp = (string) Session["MyData"];

When a page saves data to Session, the value is loaded into an in-memory dictionary—an instance of an internal class named SessionDictionary. Other concurrently running pages cannot access the session until the ongoing request completes.

The Session_Start Event

The session startup event is unrelated to the session state. The Session_Start event fires when the session-state module is servicing the first request for a given user that requires a new session ID. The ASP.NET runtime can serve multiple requests within the context of a single session, but only for the first of them does Session_Start fire.

A new session ID is created and a new Session_Start event fires whenever a page is requested that doesn’t write data to the dictionary. The architecture of the session state is quite sophisticated because it has to support a variety of state providers. The overall schema entails that the content of the session dictionary is serialized to the state provider when the request completes. However, to optimize performance, this procedure really executes only if the content of the dictionary is not empty. As mentioned earlier, though, if the application defines a Session_Start event handler, the serialization takes place anyway.

The Session_End Event

The Session_End event signals the end of the session and is used to perform any clean-up code needed to terminate the session. Note, though, that the event is supported only in InProc mode—that is, only when the session data is stored in the ASP.NET worker process.

For Session_End to fire, the session state has to exist first. That means you have to store some data in the session state and you must have completed at least one request. When the first value is added to the session dictionary, an item is inserted into the ASP.NET cache. The behavior is specific to the in-process state provider; neither the out-of-process state server nor the SQL Server state server work with the Cache object.

However, much more interesting is that the item added to the cache—only one item per active session—is given a special expiration policy.It suffices to say that the session-state item added to the cache is given a sliding expiration, with the time interval set to the session timeout. As long as there are requests processed within the session, the sliding period is automatically renewed. The session-state module resets the timeout while processing the EndRequest event. It obtains the desired effect by simply performing a read on the cache! Given the internal structure of the ASP.NET Cache object, this evaluates to renewing the sliding period. As a result, when the cache item expires, the session has timed out.

An expired item is automatically removed from the cache. As part of the expiration policy for this item, the state-session module also indicates a remove callback function. The cache automatically invokes the remove function which, in turn, fires the Session_End event.

Note

The items in Cache that represent the state of a session are not accessible from outside the system.web assembly and can’t even be enumerated, as they are placed in a system-reserved area of the cache. In other words, you can’t programmatically access the data resident in another session or even remove it.

Why Does My Session State Sometimes Get Lost?

Values parked in a Session object are removed from memory either programmatically by the code or by the system when the session times out or it is abandoned. In some cases, though, even when nothing of the kind seemingly happens, the session state gets lost. Is there a reason for this apparently weird behavior?

When the working mode is InProc, the session state is mapped in the memory space of the AppDomain in which the page request is being served. In light of this, the session state is subject to process recycling and AppDomain restarts. The ASP.NET worker process is periodically restarted to maintain an average good performance; when this happens, the session state is lost. Process recycling depends on the percentage of memory consumption and maybe the number of requests served. Although cyclic, no general estimate can be made regarding the interval of the cycle. Be aware of this when designing your session-based, in-process application. As a general rule, bear in mind that the session state might not be there when you try to access it. Use exception handling or recovery techniques as appropriate for your application.

Consider that some antivirus software might be marking the web.config or global.asax file as modified, thus causing a new application to be started and subsequently causing the loss of the session state. This holds true also if you or your code modify the timestamp of those files or alter the contents of one of the special folders, such as Bin or App_Code.

Note

What happens to the session state when a running page hits an error? Will the current dictionary be saved, or is it just lost? The state of the session is not saved if, at the end of the request, the page results in an error—that is, the GetLastError method of the Server object returns an exception. However, if in your exception handler you reset the error state by calling Server.ClearError, the values of the session are saved regularly as if no error ever occurred.

Persist Session Data to Remote Servers

The session state loss problem that we mentioned earlier for InProc mode can be neatly solved by employing either of the two predefined out-of-process state providers—StateServer and SQLServer. In this case, though, the session state is held outside the ASP.NET worker process and an extra layer of code is needed to serialize and deserialize it to and from the actual storage medium. This operation takes place whenever a request is processed.

The need of copying session data from an external repository into the local session dictionary might tax the state management process to the point of causing a 15 percent to 25 percent decrease in performance. Note, though, that this is only a rough estimate, and it’s closer to the minimum impact rather than to the maximum impact. The estimate, in fact, does not fully consider the complexity of the types actually saved into the session state.

Caution

When you get to choose an out-of-process state provider (for example, StateServer and SQLServer), be aware that you need to set up the runtime environment before putting the application in production. This means either starting a Windows service for StateServer or configuring a database for SQLServer. No preliminary work is needed if you stay with the default, in-process option.

State Serialization and Deserialization

When you use the InProc mode, objects are stored in the session state as live instances of classes. No real serialization and deserialization ever takes place, meaning that you can actually store in Session whatever objects (including COM objects) you have created and access them with no significant overhead. The situation is less favorable if you opt for an out-of-process state provider.

In an out-of-process architecture, session values are to be copied from the native storage medium into the memory of the AppDomain that processes the request. A serialization/deserialization layer is needed to accomplish the task and represents one of the major costs for out-of-process state providers. How does this affect your code? First, you should make sure that only serializable objects are ever stored in the session dictionary; otherwise, as you can easily imagine, the session state can’t be saved and you’ll sustain an exception, moreover.

To perform the serialization and deserialization of types, ASP.NET uses two methods, each providing different results in terms of performance. For basic types, ASP.NET resorts to an optimized internal serializer; for other types, including objects and user-defined classes, ASP.NET makes use of the .NET binary formatter, which is slower. Basic types are stringDateTimeGuidIntPtrTimeSpanBooleanbytechar, and all numeric types.

The optimized serializer—an internal class named AltSerialization—employs an instance of the BinaryWriter object to write out one byte to denote the type and then the value. While reading, the AltSerialization class first extracts one byte, detects the type of the data to read, and then resorts to a type-specific method of the BinaryReader class to take data. The type is associated to an index according to an internal table, as shown in Figure 1.

 

Figure 1. The serialization schema for basic types that the internal AltSerialization class uses.

 


Note

While Booleans and numeric types have a well-known size, the length of a string can vary quite a bit. How can the reader determine the correct size of a string? The BinaryReader.ReadString method exploits the fact that on the underlying stream the string is always prefixed with the length, encoded as an integer seven bits at a time. Values of the DateTime type, on the other hand, are saved by writing only the total number of ticks that form the date and are read as an Int64 type.

 

As mentioned, more complex objects are serialized using the relatively slower BinaryFormatter class as long as the involved types are marked as serializable. Both simple and complex types use the same stream, but all nonbasic types are identified with the same type ID. The performance-hit range of 15 percent to 25 percent is a rough estimate based on the assumption that basic types are used. The more you use complex types, the more the overhead grows, and reliable numbers can be calculated only by testing a particular application scenario.

In light of this, if you plan to use out-of-process sessions, make sure you store data effectively. For example, if you need to persist an instance of a class with three string properties, performancewise you are probably better off using three different slots filled with a basic type rather than one session slot for which the binary formatter is needed. Better yet, you can use a type converter class to transform the object to and from a string format. However, understand that this is merely a guideline to be applied case by case and with a grain of salt.

Caution

In classic ASP, storing an ADO Recordset object in the session state was a potentially dangerous action because of threading issues. Fortunately, in ASP.NET no thread-related issues exist to cause you to lose sleep. However, you can’t just store any object to Session and be happy. If you use an out-of-process scheme, you ought to pay a lot of attention to storing DataSet objects. The reason has to do with the serialization process of the DataSet class. Because the DataSet is a complex type, it gets serialized through the binary formatter. The serialization engine of the DataSet, though, generates a lot of XML data and turns out to be a serious flaw, especially for large applications that store a large quantity of data. In fact, you can easily find yourself moving megabytes of data for each request. Just avoid DataSet objects in ASP.NET 1.x out-of-process sessions and opt for plain arrays of column and row data. In ASP.NET 2.0, set the RemotingFormat property before you store it.

Storing Session Data

When working in StateServer mode, the entire content of the HttpSessionState object is serialized to an external application—a Microsoft Windows NT service named aspnet_state.exe. The service is called to serialize the session state when the request completes. The service internally stores each session state as an array of bytes. When a new request begins processing, the array corresponding to the given session ID is copied into a memory stream and then deserialized into an internal session state item object. This object really represents the contents of the whole session. The HttpSessionState object that pages actually work with is only its application interface.

As mentioned, nonbasic types are serialized and deserialized using the system’s binary formatter class, which can handle only classes explicitly marked to be serializable. This means that COM objects, either programmatically created or declared as static objects with a session scope in global.asax, can’t be used with an out-of-process state provider. The same limitation applies to any nonserializable object.

Configuring the StateServer Provider

Using out-of-process storage scenarios, you give the session state a longer life and your application greater robustness. Out-of-process session-state storage basically protects the session against Internet Information Services (IIS) and ASP.NET process failures. By separating the session state from the page itself, you can also much more easily scale an existing application to Web farm and Web garden architectures. In addition, the session state living in an external process eliminates at the root the risk of periodically losing data because of process recycling.

As mentioned, the ASP.NET session-state provider is a Windows NT service named aspnet_state.exe. It normally resides in the installation folder of ASP.NET:

%WINDOWS%\Microsoft.NET\Framework\[version]

As usual, note that the final directory depends on the .NET Framework version you’re actually running. Before using the state server, you should make sure that the service is up and running on the local or remote machine used as the session store. The state service is a constituent part of ASP.NET and gets installed along with it, so you have no additional setup application to run.

By default, the state service is stopped and requires a manual start. You can change its configuration through the properties dialog box of the service, as shown in Figure 2.

 

Figure 2. The properties dialog box of the ASP.NET state server.

 

 

An ASP.NET application needs to specify the TCP/IP address of the machine hosting the session-state service. The following listing shows the changes that need to be made to the web.config file to enable the remote session state:

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

Note that the value assigned to the mode attribute is case sensitive. The format of the stateConnectionString attribute is shown in the following line of code. The default machine address is 127.0.0.1, while the port is 42424.

stateConnectionString="tcpip=server:port"
 

The server name can be either an IP address or a machine name. In this case, though, non-ASCII characters in the name are not supported. Finally, the port number is mandatory and cannot be omitted.

Important

The state server doesn’t offer any authentication barrier to requestors, meaning that anyone who can get access to the network is potentially free to access session data. To protect session state and make sure that it is accessed only by the Web server machine, you can use a firewall, IPSec policies, or a secure net 10.X.X.X so external attackers couldn’t gain direct access. Another security-related countermeasure consists of changing the default port number. To change the port, you edit the Port entry under the registry key: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\aspnet_state\Parameters. Writing the port in the web.config file isn’t enough.

 

The ASP.NET application attempts to connect to the session-state server immediately after loading. The aspnet_state service must be up and running; otherwise, an HTTP exception is thrown. By default, the service is not configured to start automatically. The state service uses .NET Remoting to move data back and forth.

Note

The ASP.NET state provider runs under the ASP.NET account. The account, though, can be configured and changed at will using the Service Control Manager interface. The state service is slim and simple and does not implement any special features. It is limited to holding data and listens to the specified port for requests to serve. In particular, the service isn’t cluster-aware (that is, it doesn’t provide a failover monitor to be error tolerant) and can’t be used in a clustered world when another server takes on the one that fails.

Finally, note that by default the state server listens only to local connections. If the state server and Web server live on different machines, you need to enable remote connections. You do this through another entry in the same registry key as mentioned earlier. The entry is AllowRemoteConnection, and it must be set to a nonzero value.