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 string, DateTime, Guid, IntPtr, TimeSpan, Boolean, byte, char, 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.
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.
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.