- Types of State
- Application State
- Session State
- Cookie State
- View State
- Summary
10.2 Application State
Application state is something that should be used with care, and in most cases, avoided altogether. Although it is a convenient repository for global data in a Web application, its use can severely limit the scalability of an application, especially if it is used to store shared, updateable state. It is also an unreliable place to store data, because it is replicated with each application instance and is not saved if the application is recycled. With this warning in mind, let's explore how it works.
Application state is accessed through the Application property of the HttpApplication class, which returns an instance of class HttpApplicationState. This class is a named object collection, which means that it can hold data of any type as part of a key/value pair. Listing 10-1 shows a typical use of application state. As soon as the application is started, it loads the data from the database. Subsequent data accesses will not need to go to the database but will instead access the application state object's cached version. Data that is prefetched in this way must be static, because it will not be unloaded from the application until the application is recycled or otherwise stopped and restarted.
Listing 10-1: Sample Use of Application State for Data Prefetching
// Inside of global.asax void Application_Start(object src, EventArgs e) { DataSet ds = new DataSet(); // population of dataset from ADO.NET query not shown // Cache DataSet reference Application["FooDataSet"] = ds; } // In some page within the application private void Page_Load(object src, EventArgs e) { DataSet ds = (DataSet)(Application["FooDataSet"]); // ... MyDataGrid.DataSource = ds; // ... }
Because it is likely that multiple clients will be serviced by the same application, there is a potential for concurrent access to application state. The HttpApplicationState class protects access to its collection of objects with an instance of the HttpApplicationStateLock class, a derivative of the ReadWriteObjectLock class. This class provides two alternate mechanisms for locking, one for reading and one for writing. Multiple reader locks may be acquired simultaneously, but to acquire a writer lock, all other locks must be released first. This type of locking mechanism is particularly useful for protecting state in the application state bag because it allows multiple readers to pass through concurrently, and restricts access only when a request tries to write to the state bag. The general usage model of application-level state is to update it infrequently and read it frequently, so concurrent readers are a common occurrence.
In traditional ASP, it was always on the shoulders of the developer to call Lock and Unlock on the application object whenever it was modified or accessed. In ASP.NET, however, these calls are made implicitly for you whenever you insert items into or read items from the state bag in the form of either AcquireWrite() or AcquireRead(), depending on whether an item is being inserted or accessed. There is typically no need to explicitly call Lock() and UnLock() when working with the application state bag. These methods do exist, however, and internally calling the Lock() method acquires a writer lock on the internal HttpApplicationStateLock class. It is important to note that making explicit calls to Lock() and UnLock() defeats the multiple-reader efficiency of this new locking mechanism and should therefore be avoided in most cases.
The one case in which you still need to explicitly call the Lock() and UnLock() methods on the application state bag is when you are updating a shared piece of state. For example, Listing 10-2 shows a sample page that uses shared, updateable application state. In this example, each time the page is accessed, the string identifying the client browser type (Re-quest.Browser.Browser) is used as an index into the HttpApplicationState collection, where a count is maintained to keep track of how many times this page was accessed with each client browser type. The page then renders a collection of paragraph elements displaying the browser names along with how many times each browser was used to access this page. These statistics continue to accumulate for the lifetime of the application. Note that before the value in the application state bag is retrieved and updated, Application.Lock() is called, and once the update is complete, Application.UnLock() is called. This acquires a writer lock on the application state bag and guarantees that the value will not be read while the update is being performed. If we did not take care to call Lock, a potential race condition would exist, and the value keeping track of the number of browser hits for a particular browser type would not necessarily be correct.
Listing 10-2: Sample Use of Application State
<%@ Page Language='C#' %> <script runat='server'> private void Page_Load(object sender, System.EventArgs e) { Application.Lock(); // Modify a value in the HttpApplicationState collection if (Application[Request.Browser.Browser] != null) Application[Request.Browser.Browser] = (int)Application[Request.Browser.Browser] + 1; else Application[Request.Browser.Browser] = 1; Application.UnLock(); // Print out the values in HttpApplicationState // to show client browser access statistics for (int i=0; i<Application.Count; i++) Response.Output.Write("<p>{0} : {1} hits</p>", Application.GetKey(i), Application[i]); } </script>
In almost every scenario that would have used application state in a traditional ASP application, it makes more sense to use the data cache in ASP.NET, discussed in Chapter 9. The most common need for application state is to provide a share point for accessing global, read-only data in an application. By placing global, read-only data in the data cache instead of in application state, you gain all the benefits of cache behavior, with the same ease of access provided by application state. Probably the most compelling advantage of the data cache over application state is memory utilization. If the memory utilization of the ASP.NET worker process approaches the point at which the process will be bounced automatically (the recycle limit), the memory in the data cache will be scavenged, and items that have not been used for a while will be removed first, potentially preventing the process from recycling. If, on the other hand, data is stored exclusively in application state, ASP.NET can do nothing to prevent the process from recycling, at which point all of the application state will be lost and must be restored on application start-up.
The one feature of application state that cannot be replaced by the data cache is the ability to have shared updateable state, as shown earlier in Listing 10-2. Arguably, however, this type of state should not be used at all in a Web application, because it inherently limits scalability and is unreliable as a mechanism for storing meaningful data. In the previous example, we were using application state to save statistics on browser type access. This information is maintained only as long as the application is running, and it is stored separately in each instance of the application. This means that when the process recycles, the data is lost. It also means that if this application is deployed in a Web farm (or a Web garden), separate browser statistics will be kept for each running instance of the application across different machines (or CPUs). To more reliably collect this type of statistical information, it would make more sense to save the data to a central database and avoid application state altogether.