Chapter 17. ASP.NET State Management

In the beginner’s mind there are many possibilities. In the expert’s mind there are few.

Shunryu Suzuki

All real-world applications of any shape and form need to maintain their own state to serve users’ requests. ASP.NET applications are no exception. However, unlike other types of applications, they need special system-level tools to achieve the result. The reason for this peculiarity lies in the stateless nature of the underlying protocol that Web applications still rely upon. As long as HTTP remains the transportation protocol for the Web, all applications will run into the same problem—figuring out the most effective way to persist state information.

Application state is a sort of blank container that each application and programmer can fill with whatever piece of information makes sense to persist: from user preferences to global settings, from worker data to hit counters, from lookup tables to shopping carts. This extremely variegated mess of data can be organized and accessed according to a number of different usage patterns. Typically, all the information contributing to the application state is distributed in various layers, each with its own settings for visibility, programmability, and lifetime.

ASP.NET provides state management facilities at four levels: application, session, page, and request. Each level has its own special container object, which is a topic we’ll cover in this chapter. In this chapter, we’ll explore the HttpApplicationState, HttpSessionState, and ViewState objects, which provide for application, session, and page state maintenance, respectively. In the next chapter, we’ll dive into the Cache object.

Note

In this chapter, we won’t discuss cookies in detail, but cookies are definitely useful for storing small amounts of information on the client. The information is sent with the request to the server and can be manipulated and re-sent through the response. The cookie is a text-based structure with simple key/value pairs, and it consumes no resources on the server. In e-commerce applications, for example, cookies are the preferred way of storing user preferences. In addition, cookies have a configurable expiration policy. The negatives for cookies are their limited size (browser-dependent, but seldom greater than 8 KB) and the fact that the user can disable them.

The Application’s State

Table 17-1 summarizes the main features of the various state objects.

Table 17-1. State Management Objects at a Glance

Object

Lifetime

Data Visibility

Location

Cache

Implements an automatic scavenging mechanism, and periodically clears less frequently used contents

Global to all sessions

Does not support Web farm or Web garden scenarios

HttpApplicationState

Created when the first request hits the Web server, and released when the application shuts down

Same as for Cache

Same as for Cache

HttpContext

Spans the entire lifetime of the individual request

Global to the objects involved with the request

Same as for Cache

HttpSessionState

Created when the user makes the first request, and lasts until the user closes the session

Global to all requests issued by the user who started the session

Configurable to work on Web farms and gardens

ViewState

Represents the calling context of each page being generated

Limited to all requests queued for the same page

Configurable to work on Web farms and gardens

The HttpApplicationState object makes a dictionary available for storage to all request handlers invoked within an application. An instance of the HttpApplicationState class is created the first time a client requests any resource from within a particular virtual directory. Each running application holds its own global state object. The most common way to access application state is by means of the Application property of the Page object. Application state is not shared across either a Web farm or Web garden.

Important

Application state exists today mostly for compatibility reasons, and I don’t know of any application where Application is used instead of the more powerful and built-in Cache object or external distributed cache engines.

Properties of the HttpApplicationState Class

The HttpApplicationState class is sealed and inherits from a class named NameObjectCollectionBase. In practice, the HttpApplicationState class is a collection of pairs, each made of a string key and an object value. Such pairs can be accessed either using the key string or the index. Internally, the base class employs a hashtable with an initial capacity of zero that is automatically increased as required. Table 17-2 lists the properties of the HttpApplicationState class.

Table 17-2. HttpApplicationState Properties

Property

Description

AllKeys

Gets an array of strings containing all the keys of the items currently stored in the object.

Contents

Gets the current instance of the object. But wait! What this property returns is simply a reference to the application state object, not a clone. It’s provided for ASP compatibility.

Count

Gets the number of objects currently stored in the collection.

Item

Indexer property, provides read/write access to an element in the collection. The element can be specified either by name or index. Accessors of this property are implemented using Get and Set methods.

StaticObjects

Gets a collection including all instances of all objects declared in global.asax using an <object> tag with the scope attribute set to Application.

Note that static objects and actual state values are stored in separate collections. The exact type of the static collection is HttpStaticObjectsCollection.

Methods of the HttpApplicationState Class

The set of methods that the HttpApplicationState class features are mostly specialized versions of the typical methods of a name/value collection. As Table 17-3 shows, the most significant extension includes the locking mechanism necessary to serialize access to the state values.

Table 17-3. HttpApplicationState Methods

Method

Description

Add

Adds a new value to the collection. The value is boxed as an object.

Clear

Removes all objects from the collection.

Get

Returns the value of an item in the collection. The item can be specified either by key or index.

GetEnumerator

Returns an enumerator object to iterate through the collection.

GetKey

Gets the string key of the item stored at the specified position.

Lock

Locks writing access to the whole collection. No concurrent caller can write to the collection object until UnLock is called.

Remove

Removes the item whose key matches the specified string.

RemoveAll

Calls Clear.

RemoveAt

Removes the item at the specified position.

Set

Assigns the specified value to the item with the specified key. The method is thread-safe, and the access to the item is blocked until the writing is +completed.

UnLock

Unlocks writing access to the collection.

Note that the GetEnumerator method is inherited from the base collection class and, as such, is oblivious to the locking mechanism of the class. If you enumerate the collection using this method, each returned value is obtained through a simple call to one of the get methods on the base NameObjectCollectionBase class. Unfortunately, that method is not aware of the locking mechanism needed on the derived HttpApplicationState class because of the concurrent access to the application state. As a result, your enumeration is thread-safe. A better way to enumerate the content of the collection is by using a while statement and the Get method to access an item. Alternatively, you can lock the collection before you enumerate.

State Synchronization

Note that all operations on HttpApplicationState require some sort of synchronization to ensure that multiple threads running within an application safely access values without incurring deadlocks and access violations. The writing methods, such as Set and Remove, as well as the set accessor of the Item property implicitly apply a writing lock before proceeding. The Lock method ensures that only the current thread can modify the application state. The Lock method is provided to apply the same writing lock around portions of code that need to be protected from other threads’ access.

You don’t need to wrap a single call to Set, Clear, or Remove with a lock/unlock pair of statements—those methods, in fact, are already thread-safe. Using Lock in these cases will only have the effect of producing additional overhead, increasing the internal level of recursion.

// This operation is thread-safe
Application["MyValue"] = 1;

Use Lock instead if you want to shield a group of instructions from concurrent writings:

// These operations execute atomically
Application.Lock();
int val = (int) Application["MyValue"];
if (val < 10)
    Application["MyValue"] = val + 1;
Application.UnLock();

Reading methods such as Get, the get accessor of Item, and even Count have an internal synchronization mechanism that, when used along with Lock, will protect them against concurrent and cross-thread readings and writings:

// The reading is protected from concurrent read/writes
Application.Lock();
int val = (int) Application["MyValue"];
Application.UnLock();

You should always use Lock and UnLock together. However, if you omit the call to UnLock, the likelihood of incurring a deadlock is not high because the Microsoft .NET Framework automatically removes the lock when the request completes or times out, or when an unhandled error occurs. For this reason, if you handle the exception, consider using a finally block to clear the lock or expect to face some delay while ASP.NET clears the lock for you when the request ends.

Tradeoffs of Application State

Instead of writing global data to the HttpApplicationState object, you can use public members within the global.asax file. Compared to entries in the HttpApplicationState collection, a global member is preferable because it is strongly typed and does not require a hashtable access to locate the value. On the other hand, a global variable is not synchronized per se and must be manually protected. You have to use language constructs to protect access to these members—for example, the C# lock operator or, in Microsoft Visual Basic .NET, the SyncLock operator.

Whatever form you choose for storing the global state of an application, some general considerations apply regarding the opportunity to store data globally. For one thing, global data storage results in permanent memory occupation. Unless explicitly removed by the code, any data stored in the application global state is removed only when the application shuts down. On one end, putting a few megabytes of data in the application’s memory speeds up access; on the other hand, doing this occupies valuable memory for the entire duration of the application.

For this reason, it is extremely important that you consider using the Cache object (which is discussed further in the next chapter) whenever you have a need for globally shared data. Unlike data stored with Application and global members, data stored in the ASP.NET Cache is subject to an automatic scavenging mechanism that ensures the data is removed when a too-high percentage of virtual memory is being consumed. In addition, the Cache object has a lot of other beneficial features that we’ll explore in the next chapter. The bottom line is that the Cache object was introduced specifically to mitigate the problem of memory occupation and to replace the Application object.

To put it down even clearer, today writing to the Application object is bad practice and is supported only to help with migration from classic ASP, where it was the common and easiest way of storing global data. In ASP.NET, Cache is the recommended solution for a single worker process and distributed caches (for example, Microsoft AppFabric Caching Services) if you’re in a Web farm context.

The Session’s State

The HttpSessionState class provides a dictionary-based model of storing and retrieving session-state values. Unlike HttpApplicationState, this class doesn’t expose its contents to all users operating on the virtual directory at a given time. Only the requests that originate in the context of the same session—that is, those generated across multiple page requests made by the same user—can access the session state. The session state can be stored and published in a variety of ways, including in a Web farm or Web garden scenario. By default, though, the session state is held within the ASP.NET worker process.

The ASP.NET implementation of session state provides some extremely handy facilities—such as support for cookieless browsers, Web farms, and Web gardens—and the capability of being hosted by external processes, including Microsoft SQL Server. In this way, ASP. NET session management can provide an unprecedented level of robustness and reliability. Developers can also create custom data stores for session state. For example, if you need the robustness that a database-oriented solution can guarantee but you work with Oracle databases, you need not install SQL Server as well. By writing a piece of additional code, you can support an Oracle session data store while using the same Session semantics and classes.

The extensibility model for session state offers two options: customizing bits and pieces of the existing ASP.NET session state mechanism (for example, creating an Oracle session provider or a module controlling the generation of the ID), and replacing the standard session state HTTP module with a new one. The former option is easier to implement but provides a limited set of features you can customize. The latter option is more complicated to code but provides the greatest flexibility.

The Session-State HTTP Module

Regardless of the internal implementation, the programmer has only one application programming interface (API) for session state management—the old acquaintance known as the Session object. In classic ASP, it was a COM object that was instantiated in the asp.dll ISAPI extension and injected into the memory space of the ActiveX Scripting engine called to parse and process the .asp script. It is a collection object in ASP.NET, living behind the Session property of the Page class. The exact type is HttpSessionState; it’s a class that’s not further inheritable and that implements ICollection and IEnumerable. An instance of this class is created during the startup of each request that requires session support. The collection is filled with name/value pairs read from the specified medium and attached to the context of the request—the HttpContext class. The Page’s Session property just mirrors the Session property of the HttpContext class.

If developers can simply work with one object—the Session object—regardless of other details, most of the credit goes to an HTTP module that governs the process of retrieving and storing session state with some help from special provider objects. The ASP.NET module in charge of setting up the session state for each user connecting to an application is an HTTP module named SessionStateModule. Structured after the IHttpModule interface, the SessionStateModule object provides session-state services for ASP.NET applications.

Although, as an HTTP module, it is required to supply a relatively simple programming interface—the IHttpModule interface contracts only for Init and Dispose methods—SessionStateModule does perform a number of quite sophisticated tasks, most of which are fundamental to the health and functionality of the Web application. The session-state module is invoked during the setup of the HttpApplication object that will process a given request, and it’s responsible for either generating or obtaining a unique session ID string and for storing and retrieving state data from a state provider—for example, SQL Server or the Web server’s memory.

State Client Managers

When invoked, the session-state HTTP module reads the settings in the <sessionState> section of the web.config file and determines what the expected state client manager is for the application. A state client manager is a component that takes care of storing and retrieving the data of all currently active sessions. The SessionStateModule component queries the state client manager to get the name/value pairs of a given session.

In ASP.NET, there are four possibilities for working with the session state. The session state can be stored locally in the ASP.NET worker process; the session state can be maintained in an external, even remote, process named aspnet_state.exe; and the session state can be managed by SQL Server and stored in an ad hoc database table. The fourth option entails you storing the sessions in a custom component. Table 17-4 briefly describes the various options.

Table 17-4. State Client Providers

Mode

Description

Custom

The values for all the sessions are stored in a custom data store.

InProc

The values for all the sessions are maintained as live objects in the memory of the ASP.NET worker process. This is the default option.

Off

Session state is disabled, and no state client provider is active.

SQLServer

The values for all the sessions are serialized and stored in a SQL Server table. The nstance of SQL Server can run either locally or remotely.

StateServer

The values for all the sessions are serialized and stored in the memory of a separate system process (aspnet_state.exe). The process can also run on another machine. Session values are deserialized into the session dictionary at the beginning of the request. If the request completes successfully, state values are serialized into the process memory and made available to other pages.

The SessionStateMode enum type lists the available options for the state client provider. The InProc option is by far the fastest possible in terms of access. However, bear in mind that the more data you store in a session, the more memory is consumed on the Web server, which increases the risk of performance hits. If you plan to use any of the out-of-process solutions, the possible impact of serialization and deserialization should be carefully considered. We’ll discuss this aspect in detail later in the Persist Session Data to Remote Servers section.

The session-state module determines the state provider to use based on what it reads out of the <sessionState> section of the web.config file. Next, it instantiates and initializes the state provider for the application. Each provider continues its own initialization, which is quite different depending on the type. For example, the SQL Server state manager opens a connection to the given database, whereas the out-of-process manager checks the specified TCP port. The InProc state manager, on the other hand, stores a reference to the callback function that will be used to fire the Session_End event. (I’ll discuss this further in the section Lifetime of a Session.)

Creating the HttpSessionState Object

The state module is responsible for retrieving the session state and attaching it to the context of each request that runs within the session. The session state is available only after the HttpApplication.AcquireRequestState event fires, and it gets irreversibly lost after the HttpApplication.ReleaseRequestState event. Subsequently, this means that no state is still available when Session_End fires.

The session module creates the HttpSessionState object for a request while processing the HttpApplication.AcquireRequestState event. At this time, the HttpSessionState object—a sort of collection—is given its session ID and the session dictionary. The session dictionary is the actual collection of state values that pages will familiarly access through the Session property.

If a new session is being started, such a data dictionary is simply a newly created empty object. If the module is serving a request for an existing session, the data dictionary will be filled by deserializing the contents supplied by the currently active session state provider. At the end of the request, the current content of the dictionary, as modified by the page request, is flushed back to the state provider through a serialization step. The whole process is depicted in Figure 17-1.

The session state management timeline.

Figure 17-1. The session state management timeline.

Synchronizing Access to the Session State

So when your Web page makes a call into the Session property, it’s actually accessing a local, in-memory copy of the data. What if other pages (in the same session) attempt to concurrently access the session state? In that case, the current request might end up working on inconsistent data or data that isn’t up to date.

To avoid that, the session state module implements a reader/writer locking mechanism and queues the access to state values. A page that has session-state write access will hold a writer lock on the session until the request finishes. A page gains write access to the session state by setting the EnableSessionState attribute on the @Page directive to true. A page that has session-state read access—for example, when the EnableSessionState attribute is set to ReadOnly—will hold a reader lock on the session until the request finishes.

If a page request sets a reader lock, other concurrently running requests cannot update the session state but are allowed to read. If a page request sets a writer lock on the session state, all other pages are blocked regardless of whether they have to read or write. For example, if two frames attempt to write to Session, one of them has to wait until the other finishes. Figure 17-2 shows the big picture.

Page access to the session state is synchronized, and a serialization/deserialization layer ensures that each request is served an up-to-date dictionary of values, stored at the application’s convenience.

Figure 17-2. Page access to the session state is synchronized, and a serialization/deserialization layer ensures that each request is served an up-to-date dictionary of values, stored at the application’s convenience.

Note

Concurrent access to the session state is not very common in reality. It might happen if you have a multiframe page or if your users work with two copies of the same page or multiple pages of the same application at the same time. It also happens when you use session-enabled HTTP handlers to serve embedded resources such as images or cascading style sheet (CSS) files. By default, you are protected against concurrent accesses. However, declaring the exact use of the session state that a page is going to make (read/write, readonly, or no use) is an excellent form of optimization. You do this through the EnableSessionState attribute on the @Page directive.

Properties of the HttpSessionState Class

The HttpSessionState class is defined in the System.Web.SessionState namespace. It is a generic collection class and implements the ICollection interface. The properties of the HttpSessionState class are listed in Table 17-5.

Table 17-5. HttpSessionState Properties

Property

Description

CodePage

Gets or sets the code page identifier for the current session.

Contents

Returns a reference to this object. It’s provided for ASP compatibility.

CookieMode

Details the application’s configuration for cookieless sessions. Declared to be of type HttpCookieMode. (I’ll discuss this in more detail later.)

Count

Gets the number of items currently stored in the session state.

IsCookieless

Indicates whether the session ID is embedded in the URL or stored in an HTTP cookie. It’s more specific than CookieMode.

IsNewSession

Indicates whether the session was created with the current request.

IsReadOnly

Indicates whether the session is read-only. The session is read-only if the EnableSessionState attribute on the @Page directive is set to the keyword ReadOnly.

IsSynchronized

Returns false. (See references to this later in the chapter.)

Item

Indexer property, provides read/write access to a session-state value. The value can be specified either by name or index.

Keys

Gets a collection of the keys of all values stored in the session.

LCID

Gets or sets the locale identifier (LCID) of the current session.

Mode

Gets a value denoting the state client manager being used. Acceptable values are listed in Table 17-4.

SessionID

Gets a string with the ID used to identify the session.

StaticObjects

Gets a collection including all instances of all objects declared in global.asax using an <object> tag with the scope attribute set to Session. Note that you cannot add objects to this collection from within an ASP.NET application—that is, programmatically.

SyncRoot

Returns a reference to this object. (See references to this property later in the chapter.)

Timeout

Gets or sets the minutes that the session module should wait between two successive requests before terminating the session.

The HttpSessionState class is a normal collection class because it implements the ICollection interface, but synchronization-wise it is a very special collection class. As mentioned, the synchronization mechanism is implemented in the SessionStateModule component, which guarantees that at most one thread will ever access the session state. However, because HttpSessionState implements the ICollection interface, it must provide an implementation for both IsSynchronized and SyncRoot. Note that IsSynchronized and SyncRoot are collection-specific properties for synchronization and have nothing to do with the session synchronization discussed previously. They refer to the ability of the collection class (HttpSessionState in this case) to work in a synchronized manner. Technically speaking, the HttpSessionState is not synchronized, but access to session state is.

Methods of the HttpSessionState Class

Table 17-6 shows all the methods available in the HttpSessionState class. They mostly relate to typical operations on a collection. In this sense, the only exceptional method is Abandon, which causes the session to be canceled.

Table 17-6. HttpSessionState Methods

Method

Description

Abandon

Sets an internal flag that instructs the session module to cancel the current session.

Add

Adds a new item to the session state. The value is boxed in an object type.

Clear

Clears all values from the session state.

CopyTo

Copies the collection of session-state values to a one-dimensional array, starting at the specified index in the array.

GetEnumerator

Gets an enumerator to loop through all the values in the session.

Remove

Deletes an item from the session-state collection. The item is identified by the key.

RemoveAll

Calls Clear.

RemoveAt

Deletes an item from the session-state collection. The item is identified by position.

When the procedure to terminate the current request is running, the session-state module checks an internal flag to verify whether the user ordered that the session be abandoned. If the flag is set—that is, the Abandon method was called—any response cookie is removed and the procedure to terminate the session is begun. Notice, though, that this does not mean that a Session_End event will fire.

First, the Session_End event fires only if the session mode is InProc; second, the event does not fire if the session dictionary is empty and no real session state exists for the application. In other words, at least one request must have been completed for the Session_End to fire when the session is closed either naturally or after a call to Abandon.

Working with a Session’s State

Now that you have grabbed hold of the session state basics, you can sharpen your skills by looking into more technically relevant aspects of session state management. Handling session state is a task that can be outlined in three steps: assigning a session ID, obtaining session data from a provider, and stuffing it into the context of the page. As mentioned, the session state module governs the execution of all these tasks. In doing so, it takes advantage of a couple of additional components: the session ID generator and session state provider. In ASP.NET, both can be replaced with custom components, as we’ll discuss later. For now, let’s tackle some of the practical issues you face when working with session state.

Identifying a Session

Each active ASP.NET session is identified using a 120-bit string made only of URL-allowed characters. Session IDs are guaranteed to be unique and randomly generated to avoid data conflicts and prevent malicious attacks. Obtaining a valid session ID algorithmically from an existing ID is virtually impossible. The generator of the session ID is a customizable system component that developers can optionally replace.

Note

An old proverb reminds us that nothing should be done only because it is doable. This motto is particularly apt here as we talk about parts of the session state management that are customizable in ASP.NET. These subsystems, such as the session ID generator, should be customized only when you have a good reason to and only when you’re certain it won’t make things worse or lower the level of security. I’ll return to this point in a moment with more details.

Generating the Session ID

A session ID is 15 bytes long by design (15x8 = 120 bits). The session ID is generated using the Random Number Generator (RNG) cryptographic provider. The service provider returns a sequence of 15 randomly generated numbers. The array of numbers is then mapped to valid URL characters and returned as a string.

If the session contains nothing, a new session ID is generated for each request and the session state is not persisted to the state provider. However, if a Session_Start handler is used, the session state is always saved, even if empty. For this reason, and especially if you’re not using the in-process session provider, define Session_Start handlers with extreme care and only if strictly necessary.

In contrast, the session ID remains the same after a nonempty session dictionary times out or is abandoned. By design, even though the session state expires, the session ID lasts until the browser session is ended. This means that the same session ID is used to represent multiple sessions over time as long as the browser instance remains the same.

Session Cookies

The SessionID string is communicated to the browser and then returned to the server application in either of two ways: using a cookie or a modified URL. By default, the session-state module creates an HTTP cookie on the client, but a modified URL can be used—especially for cookieless browsers—with the SessionID string embedded. Which approach is taken depends on the configuration settings stored in the application’s web.config file. By default, session state uses cookies.

A cookie is really nothing more than a text file placed on the client’s hard disk by a Web page. In ASP.NET, a cookie is represented by an instance of the HttpCookie class. Typically, a cookie has a name, a collection of values, and an expiration time. In addition, you can configure the cookie to operate on a particular virtual path and over secure connections (for example, HTTPS).

Important

ASP.NET takes advantage of the HTTP-only feature for session cookies on the browsers that support it—nowadays, pretty much every browser supports this. The HTTP-only feature prevents cookies from being available to client-side script, thus raising a barrier against potential cross-site scripting attacks aimed at stealing session IDs.

When cookies are enabled, the session-state module actually creates a cookie with a particular name and stores the session ID in it. The cookie is created as the following pseudo-code shows:

HttpCookie sessionCookie;
sessionCookie = new HttpCookie("ASP.NET_SessionId", sessionID);
sessionCookie.Path = "/";

ASP.NET_SessionId is the name of the cookie, and the SessionID string is its value. The cookie is also associated with the root of the current domain. The Path property describes the relative URL that the cookie applies to. A session cookie is given a very short expiration term and is renewed at the end of each successful request. The cookie’s Expires property indicates the time of day on the client at which the cookie expires. If not explicitly set, as is the case with session cookies, the Expires property defaults to DateTime.MinValue—that is, the smallest possible unit of time in the .NET Framework.

Note

A server-side module that needs to write a cookie adds an HttpCookie object to the Response.Cookies collection. All cookies found on the client and associated with the requested domain are uploaded and made available for reading through the Request.Cookies collection.

Cookieless Sessions

For the session state to work, the client must be able to pass the session ID to the server application. How this happens depends on the configuration of the application. ASP.NET applications define their session-specific settings through the <sessionState> section of the configuration file. To decide about the cookie support, you set the cookieless attribute to one of the values in Table 17-7. The listed values belong to the HttpCookieMode enumerated type.

Table 17-7. HttpCookieMode Enumerated Type

Mode

Description

AutoDetect

Use cookies only if the requesting browser supports cookies.

UseCookies

Use cookies to persist the session ID regardless of whether or not the browser supports cookies. This is the default option.

UseDeviceProfile

Base the decision on the browser’s capabilities as listed in the device profile section of the configuration file.

UseUri

Store the session ID in the URL regardless of whether the browser supports cookies or not. Use this option if you want to go cookieless no matter what.

When AutoDetect is used, ASP.NET queries the browser to determine whether it supports cookies. If the browser supports cookies, the session ID is stored in a cookie; otherwise, the session ID is stored in the URL. When UseDeviceProfile is set, on the other hand, the effective capabilities of the browser are not checked. For the session HTTP module to make the decision about cookies or the URL, the declared capabilities of the browser are used, as they result from the SupportsRedirectWithCookie property of the HttpBrowserCapabilities object. Note that even though a browser can support cookies, a user might have disabled cookies. In this case, session state won’t work properly.

With cookie support disabled, suppose that you request a page at the following URL:

http://www.contoso.com/test/sessions.aspx

What is displayed in the browser’s address bar is slightly different and now includes the session ID, as shown here:

http://www.contoso.com/test/(S(5ylg0455mrvws1uz5mmaau45))/sessions.aspx

When instantiated, the session-state module checks the value of the cookieless attribute. If the value is true, the request is redirected (HTTP 302 status code) to a modified virtual URL that includes the session ID just before the page name. When processed again, the request embeds the session ID. A special ISAPI filter—the aspnet_filter.exe component—preprocesses the request, parses the URL, and rewrites the correct URL if it incorporates a session ID. The detected session ID is also stored in an extra HTTP header, named AspFilterSessionId, and retrieved later.

Issues with Cookieless Sessions

Designed to make stateful applications also possible on a browser that does not support cookies or on one that does not have cookies enabled, cookieless sessions are not free of issues. First, they cause a redirect when the session starts and whenever the user follows an absolute URL from within an application’s page.

When cookies are used, you can clear the address bar, go to another application, and then return to the previous one and retrieve the same session values. If you do this when session cookies are disabled, the session data is lost. This feature is not problematic for postbacks, which are automatically implemented using relative URLs, but it poses a serious problem if you use links to absolute URLs. In this case, a new session will always be created. For example, the following code breaks the session:

<a runat="server" href="/test/sessions.aspx">Click</a>

Is there a way to automatically mangle absolute URLs in links and hyperlinks so that they incorporate session information? You can use the following trick, which uses the ApplyAppPathModifier method of the HttpResponse class:

<a href='<% =Response.ApplyAppPathModifier("test/page.aspx")%>' >Click</a>

The ApplyAppPathModifier method takes a string representing a relative URL and returns an absolute URL, which embeds session information. This trick is especially useful when you need to redirect from an HTTP page to an HTTPS page, where the full, absolute address is mandatory. Note that ApplyAppPathModifier returns the original URL if session cookies are enabled and if the path is an absolute path.

Caution

You can’t use <%…%> code blocks in server-side expressions—that is, expressions flagged with the runat=server attribute. It works in the preceding code because the <a> tag is emitted verbatim, being devoid of the runat attribute. Code blocks mentioned here have nothing to do with data binding expressions <%# … %>, which are perfect legal and even desirable in server-side code. The reason why you can’t use <%…%> code blocks in server-side expressions is that the presence of the runat attribute forces the creation of a server object that is not designed for handling code blocks.

Cookieless Sessions and Security

Another issue regarding the use of cookieless sessions is related to security. Session hijacking is one of the most popular types of attacks and consists of accessing an external system through the session ID generated for another, legitimate user.

Try this: set your application to work without cookies and visit a page. Grab the URL with the session ID as it appears in the browser’s address bar, and send it immediately in an e-mail to a friend. Have your friend paste the URL in his or her own machine and click Go. Your friend will gain access to your session state as long as the session is active.

The session ID is certainly not well-protected information (and probably couldn’t work otherwise). For the safety of a system, an unpredictable generator of IDs is key because it makes it difficult to guess a valid session ID. With cookieless sessions, the session ID is exposed in the address bar and visible to all. For this reason, if you are storing private or sensitive information in the session state, it is recommended that you use Secure Sockets Layer (SSL) or Transport Layer Security (TLS) to encrypt any communication between the browser and server that includes the session ID.

In addition, you should always provide users the ability to log out and call the Abandon method when they think security has been breached in this way. This contrivance reduces the amount of time available for anybody attempting to use your session ID to exploit data stored in the session state. And, speaking of security, it is important that you configure the system to avoid the reuse of expired session IDs when cookieless sessions are used. This behavior is configurable in ASP.NET through the <sessionState> section, as you can read in the following section.

Cookieless Sessions and SEO

Cookieless sessions are also problematic from a Search-Engine Optimization (SEO) perspective. Pages based on cookieless sessions are poorly ranked by Web spiders such as Googlebot. The reason is that every time the spider attempts to crawl the page, ASP.NET generates a different session ID, which results in a different URL for the same content. So a crawler typically concludes that you have several pages with the same content and gives you a low ranking.

An effective workaround for this issue is using UseDeviceProfile (described in Table 17-7) instead of the default value. In addition, you create in web.config a browser profile for each of the major crawlers, such as Googlebot. In the profile, you just declare that any agent that contains the word “Googlebot” in the user agent string should be treated like a browser that supports cookies. In this way, ASP.NET will not append the session ID to the URL. It’s not really a clean solution, but it does work. You can add a new profile for each crawler that is not indexing your pages well enough.

Configuring the Session State

The <sessionState> section groups the settings you can apply to configure the behavior of ASP.NET session state. Here’s what it looks like:

<sessionState
    mode="[Off|InProc|StateServer|SQLServer|Custom]"
    timeout="number of minutes"
    cookieName="session identifier cookie name"
    cookieless=
         "[true|false|AutoDetect|UseCookies|UseUri|UseDeviceProfile]"
    regenerateExpiredSessionId="[True|False]"
    sessionIDManagerType="session manager type"
    sqlConnectionString="sql connection string"
    sqlCommandTimeout="number of seconds"
    allowCustomSqlDatabase="[True|False]"
    useHostingIdentity="[True|False]"
    stateConnectionString="tcpip=server:port"
    stateNetworkTimeout="number of seconds"
    customProvider="custom provider name"
    compressionEnabled="[True|False]"
    sqlConnectionRetryInterval="number of seconds">
    <providers>...</providers>
</sessionState>

Table 17-8 details the goals and characteristics of the various attributes.

Table 17-8. <sessionState> Attributes

Mode

Description

allowCustomSqlDatabase

If true, enables specifying a custom database table to store session data instead of using the standard ASPState.

compressionEnabled

Indicates whether the session state content is compressed during serialization and deserialization to and from an out-of-process provider. Compression is disabled by default and, if enabled, uses the built-in Gzip stream. This feature is available only in ASP.NET 4.

cookieless

Specifies how to communicate the session ID to clients.

cookieName

Name of the cookie, if cookies are used for session IDs.

customProvider

The name of the custom session state store provider to use for storing and retrieving session state data.

mode

Specifies where to store session state.

partitionResolverType

Indicates the type and assembly of the partition resolver component to be loaded to provide connection information when session state is working in SQLServer or StateServer mode. If a partition resolver can be correctly loaded, sqlConnectionString and stateConnectionString attributes are ignored.

regenerateExpiredSessionId

When a request is made with a session ID that has expired, if this attribute is true, a new session ID is generated; otherwise, the expired one is revived. The default is false.

sessionIDManagerType

Null by default. If set, it indicates the component to use as the generator of session IDs.

sqlCommandTimeout

Specifies the number of seconds a SQL command can be idle before it is canceled. The default is 30.

sqlConnectionString

Specifies the connection string to SQL Server.

stateConnectionString

Specifies the server name or address and port where session state is stored remotely.

stateNetworkTimeout

Specifies the number of seconds the TCP/IP network connection between the Web server and the state server can be idle before the request is canceled. The default is 10.

timeout

Specifies the number of minutes a session can be idle before it is abandoned. The default is 20.

useHostingIdentity

True by default. It indicates that the ASP.NET process identity is impersonated when accessing a custom state provider or the SQLServer provider configured for integrated security.

In addition, the child <providers> section lists custom session-state store providers. ASP.NET session state is designed to enable you to easily store user session data in different sources, such as a Web server’s memory or SQL Server. A store provider is a component that manages the storage of session state information and stores the information in an alternative media (for example, Oracle) and layout. We’ll return to this topic later in the chapter.

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:

var 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. (See Figure 17-1 to review session state loading and saving.) 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 has the content of the session dictionary being 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 aforementioned Cache object that we’ll cover in detail in the next chapter. 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. You’ll also learn more about the ASP.NET cache and related expiration policies in the next chapter. For now, 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, because 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 it’s 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 I mentioned earlier for InProc mode can be neatly solved by employing either of the two predefined out-of-process state providers: StateServer or 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 to copy 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.

Note

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 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 with an index according to an internal table, as shown in Figure 17-3.

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

Figure 17-3. 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 this advice should be taken with a grain of salt.

Storing Session Data

When working in StateServer mode, the entire content of the HttpSessionState object is serialized to an external application—a Windows 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 service named aspnet_state.exe. It normally resides in the installation folder of ASP.NET:

%WINDOWS%Microsoft.NETFramework[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 17-4.

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

Figure 17-4. 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 that external attackers can’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 SYSTEMCurrentControlSetServicesaspnet_stateParameters. 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.

Persist Session Data to SQL Server

Maintaining the session state in an external process certainly makes the whole ASP.NET application more stable. Whatever happens to the worker process, the session state is still there, ready for further use. If the service is paused, the data is preserved and automatically retrieved when the service resumes. Unfortunately, if the state provider service is stopped or if a failure occurs, the data is lost. If robustness is key for your application, drop the StateServer mode in favor of SQLServer.

Performance and Robustness

When ASP.NET works in SQLServer mode, the session data is stored in a made-to-measure database table. As a result, the session data survives even SQL Server crashes, but you have to add higher overhead to the bill. SQLServer mode allows you to store data on any connected machine, as long as the machine runs SQL Server 7.0 or newer. Aside from the different medium, the storage mechanism is nearly identical to that described for remote servers. In particular, the serialization and deserialization algorithm is the same, only it’s a bit slower because of the characteristics of storage. When storing data of basic types, the time required to set up the page’s Session object is normally at least 25 percent higher than in an InProc scenario. Also in regard to this issue, the more complex types you use, the more time it will take to manage the session data.

Note

When you get to make a decision between state server or SQL server storage, consider the fact that SQL Server is cluster-aware, which makes a solution based on it more robust (and also more robust across machine restarts) and more reliable than one based on a state server.

Configuring Session State for SQL Server Support

To use SQL Server as the state provider, enter the following changes in the <sessionState> section of the web.config file:

<configuration>
  <system.web>
    <sessionState
       mode="SQLServer"
       sqlConnectionString="server=127.0.0.1;integrated security=SSPI;" />
  </system.web>
</configuration>

In particular, you need to set the mode attribute (which is case sensitive) to SQLServer and specify the connection string through the sqlConnectionString attribute. Note that the sqlConnectionString attribute string must provide credentials (user ID and password or integrated security) and a server name. However, it cannot contain tokens, such as Database and Initial Catalog, unless a custom database is enabled using allowCustomSqlDatabase, as mentioned in Table 17-8. You can specify a SQL Server Initial Catalog database name or use the SQL Server Express attachDBFileName to point to an MDB file in the connection string only if the allowCustomSqlDatabase configuration setting is enabled. If that is disabled, any attempts to specify these settings in the connection string will result in an exception.

Note

The connection string for an out-of-process session state implementation (both SQLServer and StateServer) can also be specified to refer to a connection string defined in the <connectionStrings> section. The session state module first attempts to look up a connection string from the <connectionStrings> section with the name specified in the appropriate <sessionState> attribute; if it is not found, the session state attempts to use the specified string directly.

As for credentials to access the database, you can either use User ID and passwords or resort to integrated security.

Note

Whatever account you use to access session state in SQL Server, make sure that it is granted the db_datareader and db_datawriter permissions at least. Note also that to configure the SQL Server environment for storing session state, administrative privileges are required, as a new database and stored procedures need to be created.

Session state in SQL Server mode supports the specification of a custom command timeout value (in seconds) to accommodate slow-responding-server scenarios. You control it through the sqlCommandTimeout attribute, as mentioned in Table 17-8.

Creating the SQL Server Data Store

You use the aspnet_regsql.exe tool to configure the database environment by creating any needed tables, stored procedures, triggers, and jobs. In general, the tool works through the command line but also offers a visual interface. It is located in the following system folder:

%Windows%Microsoft.NETFrameworkv4.0.30319

To create the ASPState database, you must use the command line, as shown here:

aspnet_regsql.exe -S [SqlServer Instance] -E -ssadd -sstype p

The tables that get created are named ASPStateTempApplications and ASPStateTempSessions. Figure 17-5 shows a view of the session database in SQL Server.

The ASPState database in SQL Server Enterprise Manager.

Figure 17-5. The ASPState database in SQL Server Enterprise Manager.

The ASPStateTempApplications table defines a record for each currently running ASP.NET application. The table columns are listed in Table 17-9.

Table 17-9. The ASPStateTempApplications Table

Column

Type

Description

AppId

Int

Indexed field. It represents a sort of autogenerated ID that identifies a running application using the SQLServer session mode.

AppName

char(280)

Indicates the application ID of the AppDomain running the application. It matches the contents of the AppDomainAppId property on the HttpRuntime object.

The ASPStateTempSessions table stores the actual session data. The table contains one row for each active session. The structure of the table is outlined in Table 17-10.

Table 17-10. The ASPStateTempSessions Table

Column

Type

Description

SessionId

Char(88)

Indexed field. It represents the session ID.

Created

DateTime

Indicates the time at which the session was created. It defaults to the current date.

Expires

DateTime

Indicates the time at which the session will expire. This value is normally the time at which the session state was created plus the number of minutes specified in Timeout. Note that Created refers to the time at which the session started, whereas Expires adds minutes to the time in which the first item is added to the session state.

Flags

Int

Indicates action flags—initialize items or none—from the SessionStateActions enum.

LockCookie

Int

Indicates the number of times the session was locked—that is, the number of accesses.

LockDate

DateTime

Indicates the time at which the session was locked to add the last item. The value is expressed as the current Universal Time Coordinate (UTC).

LockDateLocal

DateTime

Like the previous item, except that this one expresses the system’s local time.

Locked

bit

Indicates whether the session is currently locked.

SessionItemLong

Image

Nullable field, represents the serialized version of a session longer than 7000 bytes.

SessionItemShort

VarBinary(7000)

Nllable field. It represents the values in the specified session. The layout of the bytes is identical to the layout discussed for StateServer providers. If more than 7000 bytes are needed to serialize the dictionary, the SessionItemLong field is used instead.

Timeout

int

Indicates the timeout of the session in minutes.

The column SessionItemLong, contains a long binary block of data. Although the user always works with image data as if it is a single, long sequence of bytes, the data is not stored in that format. The data is stored in a collection of 8-KB pages that aren’t necessarily located next to each other.

When installing the SQL Server support for sessions, a job is also created to delete expired sessions from the session-state database. The job is named ASPState_Job_ DeleteExpiredSessions, and the default configuration makes it run every minute. You should note that the SQLServerAgent service needs to be running for this to work.

Reverting to the Hosting Identity

The useHostingIdentity attribute (shown in Table 17-8) lets you decide about the identity to use to grant access to the SQL Server table with session state. When the SQLServer state provider is used with integrated security, the identity is the one impersonated by the ASP.NET process. This simplifies the administrative experience for intranet sites, requiring that only the ASP.NET account be granted access to protected and critical resources. The useHostingIdentity attribute defaults to true, which enables you to revert to the ASP.NET identity before making calls to the SQLServer session state provider. This will also happen if a custom provider is used.

Note

If you’re using Windows integrated authentication to access SQL Server, reverting to the host identity is the most recommended option, for security reasons. Otherwise, it is advisable that you create a specific account and grant it only rights to execute session state stored procedures and access related resources.

Customizing Session State Management

Since its beginning, the ASP.NET session state was devised to be an easy-to-customize and extensible feature. All things considered, you have the following three options to customize session state management:

  • You can stay with the default session state module but write a custom state provider to change the storage medium (for example, to a non–SQL Server database or a different table layout). In doing so, you also have the chance to override some of the helper classes (mostly collections) that are used to bring data from the store to the Session object and back.

  • You can stay with the default session state module but replace the session ID generator. But hold on! The algorithm that generates session IDs is a critical element of the application, because making session IDs too easy for attackers to guess can lead straight to session-hijacking attacks. Nonetheless, this remains a customizable aspect of session state that, properly used, can make your application even more secure.

  • You can unplug the default session state module and roll your own. This option, however, should be used as a last resort. Obviously, it provides the maximum flexibility, but it is also extremely complicated and is recommended only if strictly necessary and if you know exactly what you’re doing. We won’t cover this topic in the book.

The first option—the easiest and least complicated of all—addresses most of the scenarios for which some custom session management is desirable. So let’s tackle it first.

Building a Custom Session State Provider

A session state provider is the component in charge of serving any data related to the current session. Invoked when the request needs to acquire state information, it retrieves data from a given storage medium and returns that to the module. Invoked by the module when the request ends, it writes the supplied data to the storage layer. As mentioned, ASP.NET supports three state providers, as listed in Table 17-11.

Table 17-11. Default State Providers

Name

Class

Storage Medium

InProc

InProcSessionStateStore

Stores data as live objects in the ASP.NET Cache.

StateServer

OutOfProcSessionStateStore

Stores data as serialized objects to the memory of a Windows service named aspnet_state.exe.

SQLServer

SqlSessionStateStore

Stores data as serialized objects into a SQL Server database.

You can write your own state provider class that uses the storage medium of your choice. Note that the default state providers also make use of various helper classes to move data around. In your custom provider, you can replace these classes too, or just stick to the standard ones.

Defining the Session State Store

A state provider (also often referred to as a session state store) is a class that inherits from SessionStateStoreProviderBase. The main methods of the interface are listed in Table 17-12.

Table 17-12. Methods of the SessionStateStoreProviderBase Class

Method

Description

CreateNewStoreData

Creates an object to contain the data of a new session. It should return an object of type SessionStateStoreData.

CreateUninitializedItem

Creates a new and uninitialized session in the data source. The method is called when an expired session is requested in a cookieless session state. In this case, the module has to generate a new session ID. The session item created by the method prevents the next request with the newly generated session ID from being mistaken for a request directed at an expired session.

Dispose

Releases all resources (other than memory) used by the state provider.

EndRequest

Called by the default session state module when it begins to handle the EndRequest event.

GetItem

Returns the session item matching the specified ID from the data store. The session item selected is locked for read. The method serves requests from applications that use a read-only session state.

GetItemExclusive

Returns the session item matching the specified ID from the data store and locks it for writing. It’s used for requests originated by applications that use a read-write session state.

Initialize

Inherited from the base provider class, performs one-off initialization.

InitializeRequest

Called by the default session state module when it begins to handle the AcquireRequestState event.

ReleaseItemExclusive

Unlocks a session item that was previously locked by a call to the GetItemExclusive method.

RemoveItem

Removes a session item from the data store. It’s called when a session ends or is abandoned.

ResetItemTimeout

Resets the expiration time of a session item. It’s invoked when the application has session support disabled.

SetAndReleaseItemExclusive

Writes a session item to the data store.

SetItemExpireCallback

The default module calls this method to notify the data store class that the caller has registered a Session_End handler.

Classes that inherit the SessionStateStoreProviderBase class work with the default ASP.NET session state module and replace only the part of it that handles session-state data storage and retrieval. Nothing else in the session functionality changes.

Locking and Expiration

Can two requests for the same session occur concurrently? You bet. Requests can certainly arrive in parallel—for example, from two frames or when a user works with two instances of the same browser, the second of which is opened as a new window. To avoid problems, a state provider must implement a locking mechanism to serialize access to a session. The session state module determines whether the request requires read-only or read-write access to the session state and calls GetItem or GetItemExclusive accordingly. In the implementation of these methods, the provider’s author should create a reader/writer lock mechanism to allow multiple concurrent reads but prevent writing on locked sessions.

Another issue relates to letting the session state module know when a given session has expired. The session state module calls the method SetItemExpireCallback when there’s a Session_End handler defined in global.asax. Through the method, the state provider receives a callback function with the following prototype:

public delegate void SessionStateItemExpireCallback(
    string sessionID, SessionStateStoreData item);

It has to store that delegate internally and invoke it whenever the given session times out. Supporting expiration callbacks is optional and, in fact, only the InProc provider actually supports it. If your custom provider is not willing to support expiration callbacks, you should instruct the SetItemExpireCallback method to return false.

Note

A provider that intends to support cookieless sessions must also implement the CreateUninitialized method to write a blank session item to the data store. More precisely, a blank session item is an item that is complete in every way except that it contains no session data. In other words, the session item should contain the session ID, creation date, and perhaps lock IDs, but no data. ASP.NET generates a new ID (in cookieless mode only) whenever a request is made for an expired session. The session state module generates the new session ID and redirects the browser. Without an uninitialized session item marked with a newly generated ID, the new request will again be recognized as a request for an expired session.

Replacing the Session Data Dictionary

SessionStateStoreData is the class that represents the session item—that is, a data structure that contains all the data that is relevant to the session. GetItem and GetItemExclusive, in fact, are defined to return an instance of this class. The class has three properties: Items, StaticObjects, and Timeout.

Items indicates the collection of name/values that will ultimately be passed to the page through the Session property. StaticObjects lists the static objects belonging to the session, such as objects declared in the global.asax file and scoped to the session. As the name suggests, Timeout indicates how long, in minutes, the session state item is valid. The default value is 20 minutes.

After the session state module has acquired the session state for the request, it flushes the contents of the Items collection to a new instance of the HttpSessionStateContainer class. This object is then passed to the constructor of the HttpSessionState class and becomes the data container behind the familiar Session property.

The SessionStateStoreData class is used in the definition of the base state provider class, meaning that you can’t entirely replace it. If you don’t like it, you can inherit a new class from it, however. To both the session module and state provider, the container of the session items is merely a class that implements the ISessionStateItemCollection interface. The real class being used by default is SessionStateItemCollection. You can replace this class with your own as long as you implement the aforementioned interface.

Note

To write a state provider, you might find helpful the methods of the SessionStateUtility class. The class contains methods to serialize and deserialize session items to and from the storage medium. Likewise, the class has methods to extract the dictionary of data for a session and add it to the HTTP context and the Session property.

Registering a Custom Session State Provider

To make a custom session state provider available to an application, you need to register it in the web.config file. Suppose you have called the provider class SampleSessionStateProvider and compiled it to MyLib. Here’s what you need to enter:

<system.web>
    <sessionState mode="Custom"
      customProvider="SampleSessionProvider">
      <providers>
        <add name="SampleSessionProvider"
          type="SampleSessionStateProvider, MyLib" />
      </providers>
    </sessionState>
</system.web>

The name of the provider is arbitrary but necessary. To force the session state module to find it, set the mode attribute to Custom.

Generating a Custom Session ID

To generate the session ID, ASP.NET uses a special component named SessionIDManager. Technically speaking, the class is neither an HTTP module nor a provider. More simply, it is a class that inherits from System.Object and implements the ISessionIDManager interface. You can replace this component with a custom component as long as the component implements the same ISessionIDManager interface. To help you decide whether you really need a custom session ID generator, let’s review some facts about the default module.

The Default Behavior

The default session ID module generates a session ID as an array of bytes with a cryptographically strong random sequence of 15 values. The array is then encoded to a string of 24 URL-accepted characters, which is what the system will recognize as the session ID.

The session ID can be round-tripped to the client in either an HTTP cookie or a mangled URL, based on the value of the cookieless attribute in the <sessionState> configuration section. Note that when cookieless sessions are used, the session ID module is responsible for adding the ID to the URL and redirecting the browser. The default generator redirects the browser to a fake URL like the following one:

http://www.contoso.com/test/(S(session_id))/page.aspx

How can a request for this fake URL be served correctly? In the case of a cookieless session, the Session ID module depends on a small and simple ISAPI filter (aspnet_filter.dll) to dynamically rewrite the real URL to access. The request is served correctly, but the path on the address bar doesn’t change. The detected session ID is placed in a request header named AspFilterSessionId.

A Homemade Session ID Manager

Now that we’ve ascertained that a session ID manager is a class that implements ISessionIDManager, you have two options: build a new class and implement the interface from the ground up, or inherit a new class from SessionIDManager and override a couple of virtual methods to apply some personalization. The first option offers maximum flexibility; the second is simpler and quicker to implement, and it addresses the most compelling reason you might have to build a custom session ID generator—to supply your own session ID values.

Let’s start by reviewing the methods of the ISessionIDManager interface, which are shown in Table 17-13.

Table 17-13. Methods of the ISessionIDManager Interface

Method

Description

CreateSessionID

Virtual method. It creates a unique session identifier for the session.

Decode

Decodes the session ID using HttpUtility.UrlDecode.

Encode

Encodes the session ID using HttpUtility.UrlEncode.

Initialize

Invoked by the session state immediately after instantiation; performs one-time initialization of the component.

InitializeRequest

Invoked by the session state when the session state is being acquired for the request.

GetSessionID

Gets the session ID from the current HTTP request.

RemoveSessionID

Deletes the session ID from the cookie or from the URL.

SaveSessionID

Saves a newly created session ID to the HTTP response.

Validate

Confirms that the session ID is valid.

If you plan to roll your own completely custom session ID generator, bear in mind the following points:

  • The algorithm you choose for ID generation is a critical point. If you don’t implement strong cryptographic randomness, a malicious user can guess a valid session ID when the same session is still active, thus accessing some user’s data. (This is known as session hijacking.) A good example of a custom session ID algorithm is one that returns a globally unique identifier (GUID).

  • You can choose to support cookieless sessions or not. If you do, you have to endow the component with the ability to extract the session ID from the HTTP request and redirect the browser. You probably need an ISAPI filter or HTTP module to preprocess the request and enter appropriate changes. The algorithm you use to store session IDs without cookies is up to you.

If you are absolutely determined to have the system use your session IDs, you derive a new class from SessionIDManager and override two methods: CreateSessionID and Validate. The former returns a string that contains the session ID. The latter validates a given session ID to ensure it conforms to the specification you set. After you have created a custom session ID module, you register it in the configuration file. Here’s how to do it:

<sessionState
    sessionIDManagerType="Samples.MyIDManager, MyLib" />
</sessionState>

The View State of a Page

ASP.NET pages supply the ViewState property to let applications build a call context and retain values across two successive requests for the same page. The view state represents the state of the page when it was last processed on the server. The state is persisted—usually, but not necessarily, on the client side—and is restored before the page request is processed.

By default, the view state is maintained as a hidden field added to the page. As such, it travels back and forth with the page itself. Although it is sent to the client, the view state does not represent, nor does it contain, any information specifically aimed at the client. The information stored in the view state is pertinent only to the page and some of its child controls and is not consumed in any way by the browser.

The view state comes at a cost. At the same time, however, the view state is one of the most important features of ASP.NET, not so much because of its technical relevance but because it allows you to benefit from most of the magic of the Web Forms model. Used without strict criteria, though, the view state can easily become a burden for pages.

The StateBag Class

The StateBag class is the class behind the view state that manages the information that ASP.NET pages and controls want to persist across successive posts of the same page instance. The class works like a dictionary and, in addition, implements the IStateManager interface. The Page and Control base classes expose the view state through the ViewState property. So you can add or remove items from the StateBag class as you would with any dictionary object, as the following code demonstrates:

ViewState["FontSize"] = value;

You should start writing to the view state only after the Init event fires for the page request. You can read from the view state during any stage of the page life cycle, but not after the page enters rendering mode—that is, after the PreRender event fires.

View State Properties

Table 17-14 lists all the properties defined in the StateBag class.

Table 17-14. Properties of the StateBag Class

Property

Description

Count

Gets the number of elements stored in the object.

Item

Indexer property. It gets or sets the value of an item stored in the class.

Keys

Gets a collection object containing the keys defined in the object.

Values

Gets a collection object containing all the values stored in the object.

Each item in the StateBag class is represented by a StateItem object. An instance of the StateItem object is implicitly created when you set the Item indexer property with a value or when you call the Add method. Items added to the StateBag object are tracked until the view state is serialized prior to the page rendering. Items serialized are those with the IsDirty property set to true.

View State Methods

Table 17-15 lists all the methods you can call in the StateBag class.

Table 17-15. Methods of the StateBag Class

Method

Description

Add

Adds a new StateItem object to the collection. If the item already exists, it gets updated.

Clear

Removes all items from the current view state.

GetEnumerator

Returns an object that scrolls over all the elements in the StateBag.

IsItemDirty

Indicates whether the element with the specified key has been modified during the request processing.

Remove

Removes the specified object from the StateBag object.

The IsItemDirty method represents an indirect way to call into the IsDirty property of the specified StateItem object.

Note

The view state for the page is a cumulative property that results from the contents of the ViewState property of the page plus the view state of all the controls hosted in the page.

Common Issues with View State

Architecturally speaking, the importance of the view state cannot be denied because it is key to setting up the automatic state management feature of ASP.NET. A couple of hot issues are related to the usage of the view state, however. The most frequently asked questions about the view state are related to security and performance. Can we say that the view state is inherently secure and cannot be tampered with? How will the extra information contained in the view state affect the download time of the page? Let’s find out.

Encrypting and Securing

Many developers are doubtful about using the view state specifically because it is stored in a hidden field and left on the client at the mercy of potential intruders. Although the data is stored in a hashed format, there’s no absolute guarantee that it cannot be tampered with. The first comment I’d like to make in response to this is that the view state as implemented in ASP.NET is inherently more secure than any other hidden fields you might use (and that you were likely using, say, in old classic ASP applications). My second remark is that only data confidentiality is at risk. While this is a problem, it is minor compared to code injection.

Freely accessible in a hidden field named __VIEWSTATE, the view state information is, by default, hashed and Base64 encoded. To decode it on the client, a potential attacker must accomplish a number of steps, but the action is definitely possible. Once decoded, though, the view state reveals only its contents—that is, confidentiality is at risk. However, there’s no way an attacker can modify the view state to post malicious data. A tampered view state, in fact, is normally detected on the server and an exception is thrown.

For performance reasons, the view state is not encrypted. If it’s needed, though, you can turn the option on by acting on the web.config file, as follows:

<machineKey validation="3DES" />

When the validation attribute is set to 3DES, the view-state validation technique uses 3DES encryption and doesn’t hash the contents. If you use web.config, the settings apply to all pages in the application. You can also control encryption settings separately for each page. Furthermore, individual controls on the page can request to encrypt the view state. In case of a conflict, page settings win. You use the ViewStateEncryptionMode property, which accepts values from the ViewStateEncryptionMode enumeration. Feasible values are Auto, Always, and Never. The default value is Auto. When the value is Auto, ASP.NET encrypts the entire view state only if all controls want it encrypted. With values like Always and Never, the view state is always or never encrypted, regardless of the control settings.

Machine Authentication Check

The @Page directive contains an attribute named EnableViewStateMac, whose only purpose is making the view state a bit more secure by detecting any possible attempt at corrupting the original data. When serialized, and if EnableViewStateMac is set to true, the view state is appended with a validator hash string based on the algorithm and the key defined in the <machineKey> section of the configuration file. The resulting array of bytes, which is the output of the StateBag’s binary serialization plus the hash value, is Base64 encoded. By default, the encryption algorithm to calculate the hash is SHA1, and the encryption and decryption keys are autogenerated and stored in the Web server machine’s Local Security Authority (LSA) subsystem. The LSA is a protected component of Windows. It provides security services and maintains information about all aspects of local security on a system.

If EnableViewStateMac is true, when the page posts back, the hash value is extracted and used to verify that the returned view state has not been tampered with on the client. If it has been, an exception is thrown. The net effect is that you might be able to read the contents of the view state, but to replace it you need the encryption key, which is in the Web server’s LSA. The MAC in the name of the EnableViewStateMac property stands for Machine Authentication Check, which is enabled by default. If you disable the attribute, an attacker could alter the view-state information on the client and send a modified version to the server and have ASP.NET blissfully use that tampered-with information.

To reinforce the security of the view state, you can use the ViewStateUserKey property. The property evaluates to a user-specific string (typically, the session ID) that is known on the server and hard to guess on the client. ASP.NET uses the content of the property as an input argument to the hash algorithm that generates the MAC code.

Size Thresholds and Page Throughput

My opinion is that you should be concerned about the view state, but not for the potential security holes it might open in your code—it can let hackers exploit only existing holes. You should be more concerned about the overall performance and responsiveness of the page. Especially for feature-rich pages that use plenty of controls, the view state can reach a considerable size, measured in KB of data. Such an extra burden taxes all requests, in downloads and uploads, and ends up creating serious overhead for the application as a whole.

What is a reasonable size for an ASP.NET page? And for the view state of a page? Let’s take a look at a sample page that contains a grid control bound to about 100 records (the Customers table in the Northwind database of SQL Server):

<html>
<head runat="server">
    <title>Measure Up Your ViewState</title>
</head>
<script language="javascript">
function ShowViewStateSize()
{
    var buf = document.forms[0]["__VIEWSTATE"].value;
    alert("View state is " + buf.length + " bytes");
}
</script>
<body>
    <form id="form1" runat="server">
        <input type="button" value="Show View State Size"
               onclick="ShowViewStateSize()">
        <asp:SqlDataSource ID="SqlDataSource1" runat="server"
               SelectCommand="SELECT companyname, contactname, contacttitle
                              FROM customers"
               ConnectionString="<%$ ConnectionStrings:LocalNWind %>"
        <asp:DataGrid ID="grid" runat="server"
               DataSourceID="SqlDataSource1" />
    </form>
</body>
</html>

In ASP.NET 2.0 and beyond, the total size of the page is about 20 KB. The view state alone, though, takes up about 11 KB. If you port the same page back to ASP.NET 1.x, results are even worse. The whole page amounts to 28 KB, while the view state alone amounts to a burdensome 19 KB. Two conclusions can be drawn from these numbers:

  • Starting with ASP.NET 2.0, the view-state field appears to be more compact. And ASP.NET 2.0 was released back in 2005.

  • The view state takes up a large share of the downloaded bytes for the page. You won’t be too far from the truth if you estimate the view-state size to be about 60 percent of the entire page size.

What can you do about this? First, let’s play with some numbers to determine a reasonable goal for view-state size in our applications. All things considered, you should endeavor to keep a page size around 30 KB, to the extent that is possible of course. The ideal size for a view state is around 7 KB; it is optimal if you can keep it down to 3 KB or so. In any case, the view state, regardless of its absolute size, should never exceed 30 percent of the page size.

Note

Where do these numbers come from? “From my personal experience” would perhaps be a valid answer, but it’s not necessarily a good or exhaustive one. Let’s put it this way: the smallest you can keep a page is the best size. To me, 30 KB looks like a reasonable compromise, because most things can be stuffed into that size. Clearly, if you have 250 items to display, your page size can grow up to 1 MB or so. In the end, having a smaller or larger view state is a design choice and is mostly application-specific.

Within these boundaries, though, a few guidelines can be stated. The most important guideline is not so much that view state should be limited to a few KB, but that it should take a minimal percentage of the overall page size. Which percentage? Being the view-state helper, I’d say no more than 25 percent or 30 percent at the most.

But here I’m just throwing out numbers using a bit of common sense. If you can disable the view state altogether, do it. At the very least, you should avoid storing there the avoidable items that don’t change often and are easily cached on the server, such as a long list of countries/regions.

Programming the View State

By default, the view state is enabled for all server controls; however, this doesn’t mean that you strictly need it all the time and for all controls. The use of the view-state feature should be carefully monitored because it can hinder your code. View state saves you from a lot of coding and, more importantly, makes coding simpler and smarter. However, if you find you’re paying too much for this feature, drop view state altogether and reinitialize the state of the size-critical controls at every postback. In this case, disabling view state saves processing time and speeds up the download process.

Disabling View State

You can disable the view state for an entire page by using the EnableViewState attribute of the @Page directive. Although this is not generally a recommended option, you should definitely consider it for read-only pages that either don’t post back or don’t need state to be maintained.

<% @Page EnableViewState="false" %>

A little known aspect of view state programming is that, with the previous setting in place, all controls within the page have view state disabled no matter what their view state settings are. When view state is enabled at the page level, instead, disabling the view state on a control produces the effect of disabling it just on that control.

The net effect of this situation is that if you have 300 controls in a page and just want to have view state enabled on, say, three of them, all that you can do is disable view state on the remaining 297. To make up for this, in ASP.NET 4 a new property has been added to exercise stricter control over view state: the ViewStateMode property. The property accepts three values: Inherit, Enabled, and Disabled. If the value is Inherit, the control gets the setting of its parent. The ViewStateMode property takes precedence over EnableViewState.

Determining When to Disable View State

Let’s briefly recap what view state is all about and what you might lose if you ban it from your pages. View state represents the current state of the page and its controls just before the page is rendered to HTML. It is then serialized to a hidden field and downloaded to the client. When the page posts back, the view state—a sort of call context for the page request—is recovered from the hidden field, deserialized, and used to initialize the server controls in the page and the page itself. However, this is only the first half of the story.

After loading the view state, the page reads client-side posted information and uses those values to override most of the settings for the server controls. Applying posted values overrides some of the settings read from the view state. You understand that in this case, and only for the properties modified by posted values, the view state represents an extra burden.

Let’s examine a typical case and suppose you have a page with a text box server control. What you expect is that when the page posts back, the text box server control is automatically assigned the value set on the client. Well, to meet this rather common requirement, you don’t need view state. Let’s consider the following page:

<% @Page language="c#" %>
<form runat="server">
    <asp:textbox runat="server" viewstatemode="disabled"
        id="theInput" readonly="false" text="Type here" />
    <asp:checkbox runat="server" viewstatemode="disabled"
         id="theCheck" text="Check me" />
    <asp:button runat="server" text="Click" onclick="OnPost" />
</form>

Apparently, the behavior of the page is stateful even if view state is disabled for a couple of controls. The reason lies in the fact that you are using two server controls—TextBox and CheckBox—whose key properties—Text and Checked—are updated according to the values set by the user. For these properties, posted values override any setting that view state might have set. As a result, as long as you’re simply interested in persisting these properties you don’t need view state at all.

Likewise, you don’t need view state for all control properties that are set at design-time in the .aspx file and are not expected to change during the session. The following code illustrates this point:

<asp:textbox runat="server" id="TextBox1" Text="Some text"
             MaxLength="20" ReadOnly="true" />

You don’t need view state to keep the Text property of a TextBox up to date; you do need view state to keep up to date, say, ReadOnly or MaxLength, as long as these properties have their values changed during the page lifetime. If the two properties are constant during the page lifetime, you don’t need view state for them either.

So when is view state really necessary?

View state is necessary whenever your page requires that accessory control properties (other than those subject to posted values) are updated during the page lifetime. In this context, “updated” means that their original value changes—either the default value or the value you assign to the property at design time. Consider the following form:

<script runat="server">
   void Page_Load(object sender, EventArgs e)
   {
      if (!IsPostBack)
         theInput.ReadOnly = true;
   }
</script>

<form id="form1" runat="server">
   <asp:textbox runat="server" id="theInput" text="Am I read-only?" />
   <asp:button ID="Button1" runat="server" text="Click" onclick="OnPost" />
</form>

When the page is first loaded, the text box becomes read-only. Next, you click the button to post back. If view state is enabled, the page works as expected and the text box remains read-only. If view state is disabled for the text box, the original setting for the ReadOnly property is restored—in this case, false.

In general, you can do without view state whenever the state can be deduced either from the client or from the runtime environment. In contrast, doing without view state is hard whenever state information can’t be dynamically inferred and you can’t ensure that all properties are correctly restored when the page posts back. This is exactly what view state guarantees at the cost of extra bytes when downloading and uploading. To save those bytes, you must provide an alternate approach.

Disabling the view state can also create subtler problems that are difficult to diagnose and fix, especially if you’re working with third-party controls or, in general, controls for which you have source code access. Some ASP.NET controls, in fact, might save to the view state not just properties that are officially part of the programming interface (and that can be set accordingly), but also behavioral properties that serve internal purposes and are marked as protected or even private. Unfortunately, for these controls, you do not have the option of disabling the view state. But ASP.NET comes to the rescue with control state.

The Control State

It is not uncommon for a server control to persist information across postbacks. For example, consider what happens to a DataGrid control modified to support autoreverse sorting. When the user clicks to sort by a column, the control compares the current sort expression and the new sort expression. If the two are equivalent, the sort direction is reversed. How does the DataGrid track the current sort direction? If you don’t place the sort direction property in the control’s view state, it will be lost as soon as the control renders to the browser.

This kind of property is not intended to be used for plain configurations such as pager style or background color. It has an impact on how the control works. What if the control is then used in a page that has the view state disabled?

The control state is a special data container introduced just to create a sort of protected zone inside the classic view state. For developers of custom controls, it is safer to use the control state than the view state because application-level and page-level settings cannot affect it. If your existing custom control has private or protected properties stored in the view state, you should move all of them to the control state. Anything you store in the control state remains there until it is explicitly removed. Also the control state is sent down to the client and uploaded when the page posts back. The more data you pack into it, the more data is moved back and forth between the browser and the Web server. You should use control state, but you should do so carefully.

Programming the Control State

The implementation of the control state is left to the programmer, which is both good and bad. It is bad because you have to manually implement serialization and deserialization for your control’s state. It is good because you can control exactly how the control works and tweak its code to achieve optimal performance in the context in which you’re using it. The page’s infrastructure takes care of the actual data encoding and serialization. The control state is processed along with the view state information and undergoes the same treatment as for serialization and Base64 encoding. The control state is also persisted within the same view state’s hidden field. The root object serialized to the view state stream is actually a Pair object that contains the control state as the first element and the classic view state as the second member.

There’s no ready-made dictionary object to hold the items that form the control state. You no longer have to park your objects in a fixed container such as the ViewState state bag—you can maintain control-state data in plain private or protected members. Among other things, this means that access to data is faster because it is more direct and is not mediated by a dictionary object.

To restore control state, the Page class invokes the LoadControlState on all controls that have registered with the page object as controls that require control state. The following pseudocode shows the control’s typical behavior:

private override void LoadControlState(object savedState)
{
    // Make a copy of the saved state.
    // You know what type of object this is because
    // you saved it in the SaveControlState method.
    object[] currentState = (object[]) savedState;
    if (currentState == null)
        return;

    // Initialize any private/protected member you stored
    // in the control state. The values are packed in the same
    // order and format you stored them in the SaveControlState method.
    _myProperty1 = (int) currentState[0];
    _myProperty2 = (string) currentState[1];
    ...
}

The LoadControlState method receives an object identical to the one you created in SaveControlState. As a control developer, you know that type very well and can use this knowledge to extract any information that’s useful for restoring the control state. For example, you might want to use an array of objects in which every slot corresponds to a particular property.

The following pseudocode gives you an idea of the structure of the SaveControlState method:

private override object SaveControlState()
{
    // Declare a properly sized array of objects
    object[] stateToSave = new Object[...];

    // Fill the array with local property values
    stateToSave[0] = _myProperty1;
    stateToSave[1] = _myProperty2;
    ...

    // Return the array
    return stateToSave;
}

You allocate a new data structure (such as a Pair, a Triplet, an array, or a custom type) and fill it with the private properties to persist across postbacks. The method terminates, returning this object to the ASP.NET runtime. The object is then serialized and encoded to a Base64 stream. The class that you use to collect the control state properties must be serializable.

Keeping the View State on the Server

The more stuff you pack into the view state, the more time the page takes to download and upload because the view state is held in a hidden field. The client-side hidden field is not set in stone, but is simply the default storage medium where the view state information can be stored. Does it make sense to store the view state somewhere on the server?

Leaving the view state on the server is definitely possible and all you have to do is override a couple of protected members on the Page class. The devil is in the details, however.

You should guarantee that the correct view-state file will be served to each page instance the user retrieves via the browser’s history. This is not an issue as long as each page contains its own view state. But when the view state is stored elsewhere, unless you want to disable Back/Forward functionality, you should provide a mechanism that serves the “right” view state for the instance of a given page that the user is reclaiming. At a minimum, you need to make copies of the view state for about six to eight instances.

As you can see, what you save in the roundtrip is lost in the server’s memory or server-side I/O operations. All in all, keeping the view state on the client and inside of the page is perhaps the option that works better in the largest number of scenarios. If the view state is a problem, you have only one way out: reducing its size.

Summary

Although HTTP is a stateless protocol, Web applications can’t just do without certain forms of state. Moreover, state management is a hot topic for all real-world Web applications. Setting up an effective and efficient solution for state management is often the difference between an application being scalable or nonscalable.

One of the most-used forms of state is session state—that is, the state specific to a user and the one that’s valid as long as that user works with the application. You can store session data in the memory of the ASP.NET worker process as well as in external processes, and even in a SQL Server table or in a custom state provider. In spite of the radically different options, the top-level programming interface is identical. More importantly, the ASP.NET session state can be persisted in a Web farm or Web garden scenario as well.

In the next chapter, we’ll deal with another extremely powerful form of state container—the Cache object.

It’s worth spending a final word on a form of state management that might grow significantly in the future, especially as HTML 5 becomes widely supported by browsers—client side state. Around the HTML 5 working draft, in fact, a number of technologies are being developed for storing information on the browser in a much more powerful way than with cookies. It ranges from simple forms of isolated storage (already in Internet Explorer 8) to Web SQL databases. As usual, time will tell. And years of experience remind us that no matter how cool it could be, it only works if it is widely supported.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.19.14.243