Introduction
In a stateless environment like a Web application, understanding the concept of session state has no real meaning. Nonetheless, effective state management is a must-have feature for most Web applications. Microsoft ASP.NET, as well as many other server-side programming environments, provide an abstraction layer that allows applications to store persistent data on a per-user and per-application basis.
It's important to note that a web application's session state is the data that the application caches and retrieves across different requests. A session represents all requests sent by the user while connected to the site, and session state is the collection of persistent data generated and consumed by the user during the session. The state of each session is independent of each other and ceases to exist when the user session ends.
Session state has no correspondence with any of the logical entities that make up the HTTP protocol and specification. Sessions are an abstraction layer built by server-side development environments such as traditional ASP and ASP.NET. The way ASP.NET displays session state and how session state is implemented internally depends on the platform's infrastructure. Therefore, traditional ASP and ASP.NET implement session state in completely different ways, and further improvements and enhancements are expected in the next version of ASP.NET.
This article discusses how to implement session state in ASP.NET 1.1 and how to optimize session state management in managed Web applications.
ASP.NET Session State Overview
Session state is not part of the HTTP infrastructure. That is, there should be a structural component that binds session state with each incoming request. The runtime environment (traditional ASP or ASP.NET) can accept keywords like Session and use it to indicate the block of data stored on the server. To successfully resolve calls to a Session object, the runtime environment must add session state to the calling context of the request being processed. How this is done varies between platforms, but it is fundamental to stateful web applications.
In traditional ASP, session state is implemented as free-threaded COM objects contained in the asp.dll library. (Are you curious? The CLSID of this object is actually D97A6DA0-A865-11cf-83AF-00A0C90C2BD8.) This object stores data organized as a collection of name/value pairs. The "name" placeholder represents the key used to retrieve the information, while the "value" placeholder represents what is stored in the session state. Name/value pairs are grouped by session ID so that each user sees only the name/value pairs that he or she created.
In ASP.NET, the programming interface for session state is almost the same as in traditional ASP. But their basic implementations are completely different. The former is more flexible, scalable and has stronger programming capabilities than the latter. Before we delve into ASP.NET session state, let's briefly review some of the structural capabilities of the ASP.NET session infrastructure.
In ASP.NET, any incoming HTTP request is piped through the HTTP module. Each module can filter and modify the large amount of information carried by the request. The information associated with each request is called the "call context", which is represented by the HttpContext object in programming. We should not think of the request context as another container of state information, although the Items collection it provides is just a data container. The HttpContext object differs from all other state objects (for example, Session, Application, and Cache) in that it has a limited lifetime beyond the time required to handle the request. When a request passes through a series of registered HTTP modules, its HttpContext object will contain a reference to the state object. When the request can finally be processed, the associated calling context is bound to the specific session (Session) and global state objects (Application and Cache).
The HTTP module responsible for setting each user's session state is SessionStateModule. The structure of this module is designed based on the IHttpModule interface, which provides a large number of session state-related services for ASP.NET applications. Includes generating session IDs, cookieless session management, retrieving session data from external state providers, and binding data to the request's calling context.
The HTTP module does not store session data internally. Session state is always saved in an external component called a "state provider". The state provider completely encapsulates the session state data and communicates with other parts through the methods of the IStateClientManager interface. The session state HTTP module calls methods on this interface to read and save session state. ASP.NET 1.1 supports three different state providers, as shown in Table 1.
Table 1: Status Client Provider
Provider Description
InProc session values remain active objects in the memory of the ASP.NET worker process (aspnet_wp.exe or w3wp.exe in Microsoft® Windows Server® 2003). This is the default option.
StateServer session values are serialized and stored in memory in a separate process (aspnet_state.exe). The process can also run on other computers.
SQLServer session values are serialized and stored in Microsoft® SQL Server® tables. Instances of SQL Server can run locally or remotely.
The session state HTTP module reads the currently selected state provider from the <sessionState>; section of the web.config file.
<sessionState mode="InProc | StateServer | SQLServer />;
Depending on the value of the mode attribute, the session state will be retrieved from and stored in different processes through different steps. By default, the session state is stored in In the local ASP.NET worker process. In special cases, it is stored in a dedicated slot of the ASP.NET Cache object (not accessible programmatically). Session state can also be stored externally, even in a remote process ( For example, in a Windows NT service named aspnet_state.exe). The third option is to store the session state in a dedicated database table managed by SQL Server 2000.
The HTTP module deserializes the session value at the beginning of the request
., making them dictionary objects. The dictionaries (actually objects of type HttpSessionState) are then accessed programmatically through the Session property exposed by the classes (for example, HttpContext and Page). The binding lasts until the request ends. If the request completes successfully, all status values are serialized back to the status provider and available for other requests.
Figure 1 illustrates the communication between the requested ASP.NET page and the session values
.. The code used by each page is related to the Session attribute on the page class. The programming method is almost the same as that of traditional ASP.
Figure 1: Session state architecture in ASP.NET 1.1
The physical value of session state is locked for the time required to complete the request. This lock is managed internally by the HTTP module and is used to synchronize access to session state.
The session state module instantiates the application's state provider and initializes it with information read from the web.config file. Next, each provider will continue its own initialization operations. Depending on the type of provider, its initialization operations will vary greatly. For example, the SQL Server state manager will open a connection to a given database, while the out-of-process manager will check the specified TCP port. On the other hand, the InProc state manager will store a reference to the callback function. This action is performed when an element is removed from the cache and is used to trigger the application's Session_OnEnd event.
Synchronous access to session state
What happens when a Web page makes a very simple and intuitive call to the Session property? Many operations are performed in the background, as shown in the following cumbersome code:
int siteCount = Convert.ToInt32(Session["Counter"]);
The above code actually accesses the session value created by the HTTP module in local memory. A replica reads data from a specific state provider (see Figure 1). What happens if other pages also try to access the session state synchronously? In this case, the current request may stop processing inconsistent or stale data. To avoid this, the session state module will implement a reader/writer locking mechanism and queue access to state values. Pages with write permissions on session state will retain the writer lock for that session until the request terminates.
A page can request write permission for session state by setting the EnableSessionState property of the @Page directive to true. (This is the default setting). However, a page can also have read-only access to session state, for example, when the EnableSessionState property is set to ReadOnly. In this case, the module will retain the reader lock for that session until the request for that page ends. As a result concurrent reads will occur.
If a page request sets a reader lock, other concurrent requests in the same session will not be able to update the session state, but will at least be able to read. That is, if a read-only request is currently being processed for a session, the pending read-only request will have higher priority than a request requiring full access. If a page request sets a writer lock for session state, all other pages will be blocked regardless of whether they want to read or write content. For example, if two frames try to write to the Session at the same time, one frame must wait until the other one has finished before it can write.
Comparing State Providers
By default, ASP.NET applications store session state in the memory of a worker process, specifically in a dedicated slot of the Cache object. When InProc mode is selected, session state is stored in slots within the Cache object. This slot is marked as a private slot and cannot be accessed programmatically. In other words, if you enumerate all items in the ASP.NET data cache, no objects similar to the given session state will be returned. Cache objects provide two types of slots: private slots and public slots. Programmers can add and handle public slots, but private slots can only be used by the system (specifically, classes defined in the system.web part).
The state of each active session occupies a dedicated slot in the cache. The slot is named based on the session ID, and its value is an instance of an internal undeclared class named SessionStateItem. The InProc state provider gets the session ID and retrieves the corresponding element in the cache. The contents of the SessionStateItem object are then entered into the HttpSessionState dictionary object and accessed by the application through the Session property. Note that there is a bug in ASP.NET 1.0 that makes the Cache object's private slots programmatically enumerable. If you run the following code under ASP.NET 1.0, you will be able to enumerate the items corresponding to the objects contained in each currently active session state.
foreach(DictionaryEntry elem in Cache)
{
Response.Write(elem.Key + ": " + elem.Value.ToString());
}
This bug has been resolved in ASP.NET 1.1 and when you enumerate cached contents, no system slots will be listed anymore.
InProc is probably the fastest access option by far. But keep in mind that the more data stored in a session, the more memory the web server consumes, potentially increasing the risk of performance degradation. If you plan to use any out-of-process solution, you should carefully consider the possible effects of serialization and deserialization. The out-of-process solution uses a Windows NT service (aspnet_state.exe) or a SQL Server table to store session values. Therefore, session state remains outside the ASP.NET worker process, and additional layers of code are required to serialize and deserialize between the session state and the actual storage medium. This happens whenever a request is processed and must then be optimized to the highest degree.
Because the session data needs to be copied from the external repository to the local session dictionary, the request results in performance degradation ranging from 15% (out-of-process) to 25% (SQL Server). Note that while this is only a rough estimate, it should be close to the minimum impact, and the maximum impact will be much higher than this. In fact, this estimate does not fully take into account the complexity of the types actually saved in the session state.
In the out-of-process storage scenario, session state survives longer, making the application more powerful because it protects against Microsoft® Internet Information Services (IIS) and ASP.NET failures. By separating session state from applications, you can also more easily extend existing applications into Web Farm and Web Garden architectures. Additionally, session state is stored in an external process, essentially eliminating the risk of periodic data loss due to process loops.
Here's how to use Windows NT services. As mentioned above, the NT service is a process named aspnet_state.exe, usually located in the C:WINNTMicrosoft.NETFrameworkv1.1.4322 folder.
The actual directory depends on the version of Microsoft® .NET Framework you are actually running. Before using the state server, you should ensure that the service is ready and running on the local or remote computer that is used as the session storage device. The state service is part of and installed with ASP.NET, so you don't need to run an additional installer. By default, the status service is not running and needs to be started manually. The ASP.NET application will attempt to establish a connection to the state server immediately after it is loaded. Therefore, the service must be ready and running, otherwise an HTTP exception will be thrown. The following image shows the properties dialog box for the service.
Figure 2: ASP.NET State Server Properties dialog box
ASP.NET applications need to specify the TCP/IP address of the computer where the session state service is located. The following settings must be entered into the application's web.config file.
<configuration>;
<system.web>;
<sessionState
mode="StateServer"
stateConnectionString="tcpip=expoware:42424" />;
</system.web>;
</configuration>;
The stateConnectionString attribute contains the computer's IP address and the port used for data exchange. The default computer address is 127.0.0.1 (localhost) and the default port is 42424. You can also indicate the computer by name. Using a local or remote computer is completely transparent to the code. Note that non-ASCII characters cannot be used in the name, and the port number is mandatory.
If you use out-of-process session storage, the session state will still exist and be available for future use regardless of what happens to the ASP.NET worker process. If the service is interrupted, the data will be retained and automatically retrieved when the service is restored. However, if the status provider service stops or fails, data will be lost. If you want your application to be powerful, use SQLServer mode instead of StateServer mode.
<configuration>;
<system.web>;
<sessionState
mode="SQLServer"
sqlConnectionString="server=127.0.0.1;uid=<user id>;;pwd=<password>;;" />;
</system.web>;
</configuration>;
You can specify the connection string through the sqlConnectionString attribute. Note that the attribute string must contain the user ID, password, and server name. It cannot contain tags such as Database and Initial Catalog because this information defaults to a fixed name. User IDs and passwords can be replaced with integrated security settings.
How to create a database? ASP.NET provides two pairs of scripts to configure the database environment. The first pair of scripts are named InstallSqlState.sql and UninstallSqlState.sql and are located in the same folder as the Session State NT service. They create a database named ASPState and several stored procedures. However, the data is stored in the SQL Server temporary storage area TempDB database. This means that if the SQL Server computer is restarted, session data will be lost.
To work around this limitation, use a second pair of scripts. The second pair of scripts are named InstallPersistSqlState.sql and UninstallPersistSqlState.sql. In this case, the ASPState database is created, but the tables are created in the same database and are also persistent. When you install SQL Server support for sessions, a job is also created to delete expired sessions in the session state database. The job is named ASPState_Job_DeleteExpiredSessions and is always running. Please note that for this job to work properly, the SQLServerAgent service needs to be running.
No matter which mode you choose, the way session state operations are coded does not change. You can always work on the Session property and read and write values as normal. All differences in behavior are handled at a lower level of abstraction. State serialization is perhaps the most important difference between session modes.
State Serialization and Deserialization
When using in-process mode, objects are stored in session state as active instances of their respective classes. If no real serialization and deserialization occurs, it means that you can actually store any object you create in the Session (including objects that cannot be serialized and COM objects), and accessing them will not be too expensive. If you choose an out-of-process state provider, it's another story.
In an out-of-process architecture, session values are copied from local storage media (external AppDomain database) to the memory of the AppDomain that handles the request. A serialization/deserialization layer is required to accomplish this task and represents one of the major costs of out-of-process state providers. The main impact this situation has on your code is that only serializable objects can be stored in the session dictionary.
ASP.NET uses two methods to serialize and deserialize data, depending on the type of data involved. For basic types, ASP.NET uses an optimized internal serializer; for other types, including objects and user-defined classes, ASP.NET uses the .NET binary formatter. Basic types include strings, datetimes, Boolean values, bytes, characters, and all numeric types. For these types, using a tailor-made serializer is faster than using the default common .NET binary formatter.
The optimized serializer is not publicly released or documented. It is just a binary reader/writer and uses a simple but effective storage architecture. The serializer uses the BinaryWriter class to write a byte representation of the type, and then writes a byte representation of the value corresponding to that type. When reading serialized bytes, the class first extracts a byte, detects the data type to be read, and then calls the type-specific ReadXxx method on the BinaryReader class.
Note that the size of boolean and numeric types is well known, but not for strings. On the underlying data stream, the string is always prefixed with a fixed length (a 7-bit integer code written at a time), and the reader uses this fact to determine the correct size of the string. The date value is saved by writing only the total number of tokens that make up the date. Therefore, to serialize the session, the date should be of type Int64.
You can use the BinaryFormatter class to perform serialization operations on more complex objects (as well as custom objects) as long as the containing class is marked as serializable. All non-basic types are identified by the same type ID and stored in the same data stream as the basic types. Overall, serialization operations can result in a performance degradation of 15% to 25%. Note, however, that this is a rough estimate based on the assumption that basic types are used. The more complex the types used, the greater the overhead.
Efficient session data storage is difficult to implement without extensive use of primitive types. So, at least in theory, using three session slots to save three different string properties of an object is better than serializing the entire object. But what if the object you want to serialize contains 100 properties? Do you want to use 100 slots, or just one slot? In many cases, a better approach is to convert a complex type into multiple simpler types. This approach is based on type converters. A "type converter" is a lightweight serializer that returns the key properties of a type as a collection of strings. Type converters are external classes that are bound to a base class using attributes. It is up to the type writer to decide which properties are saved and how. Type converters are also helpful for ViewState storage and represent a more efficient method of session storage than binary formatters.
Session Life Cycle
An important point about ASP.NET session management is that the session state object's life cycle begins only when the first item is added to the in-memory dictionary. An ASP.NET session is considered started only after the following code snippet is executed.
Session["MySlot"] = "Some data";
The Session dictionary usually contains the Object type. To read data backwards, the returned value needs to be converted to a more specific type.
string data = (string) Session["MySlot"];
When the page saves data to the Session, the value will be loaded into the specially crafted dictionary class contained in the HttpSessionState class. The contents of the dictionary are loaded into the state provider when the currently processed request is completed. If the session state is empty because the data was not put into the dictionary programmatically, the data will not be serialized to the storage medium and, more importantly, will not be served in ASP.NET Cache, SQL Server, or NT State Services Create a slot in to track the current session. This is for performance reasons, but has an important impact on how session IDs are handled: a new session ID will be generated for each request until some data is stored in the session dictionary.
When it is necessary to connect session state with a request being processed, the HTTP module retrieves the session ID (if it is not the initiating request) and looks for it in the configured state provider. If no data is returned, the HTTP module generates a new session ID for the request. This can be easily tested with the following page:
<%@ Page Language="C#" Trace="true" %>;
</html>;
<body>;
<form runat="server">;
<asp:button runat="server" text="Click" />;
</form>;
</body>;
</html>;
Whenever you click the button and return to the page, a new session ID will be generated and tracking information will be recorded.
Figure 3: In an application that does not store data in a session dictionary, a new session ID is generated for each request.
What about the Session_OnStart event? Will the event be raised for every request as well? If your application defines a Session_OnStart handler, session state is always saved, even if the session state is empty. Therefore, the session ID is always constant for all requests after the first request. Use the Session_OnStart handler only when absolutely necessary.
If a session times out or is abandoned, its session ID does not change the next time the stateless application is accessed. It is designed so that the session ID persists until the end of the browser session, even if the session state expires. That is, the same session ID is always used to represent multiple sessions as long as the browser instance is the same.
The Session_OnEnd event marks the end of a session and is used to execute any cleanup code required to terminate the session. Note, however, that this event is only supported in InProc mode, that is, only when session data is stored in an ASP.NET worker process. For the Session_OnEnd event to be raised, a session state must first exist, which means that some data must be stored in the session state, and at least one request must be completed.
In InProc mode, session state added to the cache as items is given a variable expiration time policy. Variable expiration means that if an item is not used within a certain period of time, it will be deleted. The expiration time of any requests processed during this period will be reset. The session state item's interval is set to the session timeout. The technique used to reset the session state expiration time is very simple and intuitive: the session HTTP module simply reads the session state items stored in the ASP.NET Cache. If the internal structure of the ASP.NET Cache object is known, the module will perform calculations to reset the variable expiration time. So when the cached item expires, the session has timed out.
Expired items will be automatically removed from the cache. The state session module also represents a delete callback function as part of the expiration time policy for this project. The cache will automatically call the delete function, which will then raise the Session_OnEnd event. If an application performs session management through an out-of-process component, the end event will never be raised.
Cookieless Sessions
Each active ASP.NET session is identified using a 120-bit string consisting only of the characters allowed by the URL. The session ID is generated using a random number generator (RNG) cryptographic provider. The service provider returns a sequence of 15 randomly generated numbers (15 bytes x 8 bits = 120 bits). The array of random numbers is then mapped to valid URL characters and returned as a string.
The session ID string is sent to the browser and returned to the server application in one of two ways: using a cookie (just like in traditional ASP) or a modified URL. By default, the session state module will create an HTTP cookie on the client side, but a modified URL that embeds the session ID string can be used (especially for browsers that do not support cookies). Which method is used depends on the configuration settings stored in the application's web.config file. To configure session settings, you can use the <sessionState> section and the Cookieless attribute.
<sessionState cookieless="true|false" />;
By default, the Cookieless attribute is false, indicating that cookies are used. In fact, a cookie is just a text file placed on the client's hard drive by a Web page. In ASP.NET, a cookie is represented by an instance of the HttpCookie class. Typically, a cookie contains a name, a set of values, and an expiration time. When the Cookieless attribute is set to false, the session state module will actually create a cookie named ASP.NET_SessionId and store the session ID in it. The following pseudocode shows the process of creating a cookie:
HttpCookie sessionCookie;
sessionCookie = new HttpCookie("ASP.NET_SessionId", sessionID);
sessionCookie.Path = "/";
The expiration time of session Cookie is very short, and the expiration time is updated after each request is successful. The Expires attribute of the cookie indicates the expiration time of the cookie on the client. If the session cookie is not set explicitly, the Expires property defaults to DateTime.MinValue, which is the smallest unit of time allowed by the .NET Framework.
To disable session cookies, set the Cookieless attribute to true in the configuration file as follows:
<configuration>;
<system.web>;
<sessionState Cookieless="true" />;
</system.web>;
</configuration>;
At this point, assume that you request the page at the following URL:
http://www.contoso.com/sample.aspxThe
actual content displayed in the browser address bar will be different and now contains the session ID, as follows Shown in:
http://www.contoso.com/(5ylg0455mrvws1uz5mmaau45)/sample.aspx
When instantiating the session state HTTP module, the module checks the value of the Cookieless attribute. If true, redirects the request (HTTP 302) to a modified virtual URL containing the session ID immediately preceding the page name. When the request is processed again, the session ID will be included in the request. If a request is made to start a new session, the HTTP module generates a new session ID and then redirects the request. If the request is posted back, the session ID already exists because postbacks use relative URLs.
The disadvantage of using cookieless sessions is that session state is lost if an absolute URL is called. When using cookies, you can clear the address bar, go to another application, and then return to the previous application and retrieve the same session value. If you do this while session cookies are disabled, session data will be lost. For example, the following code will interrupt the session:
<a runat="server" href="/code/page.aspx">;Click</a>;
If you need to use an absolute URL, please use some tricks to manually change the session ID added to the URL. You can call the ApplyAppPathModifier method on the HttpResponse class.
<a runat="server"
href=<% =Response.ApplyAppPathModifier("/code/page.aspx")%>; >;Click</a>;
The ApplyAppPathModifier method will use a string representing the URL and return the absolute URL embedding the session information. This technique is particularly useful when, for example, you need to redirect from an HTTP page to an HTTPS page.
Summary
Session state was originally introduced with traditional ASP as a dictionary-based API that enabled developers to store custom data during a session. In ASP.NET, session state supports two main features: cookieless session ID storage and transfer, and state providers where the session data is actually stored. To implement these two new capabilities, ASP.NET leverages the HTTP module to control the binding between session state and the context of the request being processed.
In traditional ASP, using session state means using cookies. This is no longer the case in ASP.NET, as a cookieless architecture can be used. With the power of the HTTP module, the requested URL can be decomposed so that it contains the session ID and then redirected. Next, the HTTP module extracts the session ID from the URL and uses it to retrieve any stored state.
The physical state of a session can be stored in three locations: in-process memory, out-of-process memory, and SQL Server tables. The data must be serialized/deserialized before it can be used by the application. The HTTP module copies the session value from the provider into the application's memory at the beginning of the request. After the request is completed, the modified status is returned to the provider. This data communication will have varying degrees of adverse effects on performance, but will greatly enhance reliability and stability and make support for Web Farm and Web Garden architectures easier to implement.