Application State
You can share information throughout your application by using the HttpApplicationState class, most commonly accessed through the Application property of the HttpContext object. This class exposes a key-value dictionary of objects that you can use to store both .NET Framework objects and scalar values related to multiple Web requests from multiple clients.
This topic provides an overview of application state; discusses how to use application state; introduces application state collections; and covers application state synchronization.
Application State Overview
An ASP.NET application is the sum of all files, pages, handlers, modules, and code that reside in a given virtual directory and its subdirectories and that users can request through that virtual directory hierarchy.
For example, if you develop an application that calculates investment benefits for your company's intranet, you could publish it in a virtual directory named \Invest on a Web server. The directory structure for such an application might look something like this:
\Invest
\bin
\image
\xml
An instance of the HttpApplicationState class is created the first time any client requests a URL resource from within the virtual directory namespace of a particular ASP.NET application. This is the case for each Web application that is stored on the computer. Access to this per-application instance is provided through an HttpContext property named Application. All HttpModules and HttpHandlers, such as an ASP.NET page, have access to an instance of the context and therefore have access to the Application property during a given Web request.
ASP.NET provides the following application-state support:
- An easy-to-use state facility that is compatible with earlier versions of ASP, works with all .NET-supported languages, and is consistent with other .NET Framework APIs.
- An application-state dictionary that is available to all request handlers invoked within an application. Unlike Internet Information Services (IIS) and earlier versions of ASP, where only pages can access application state, all IHttpHandler and IHttpModule instances can store and retrieve global variables within the dictionary.
- A simple and intuitive synchronization mechanism that enables developers to easily coordinate concurrent access to variables stored in the application state.
- Application-state values that are accessible only from code running within the context of the originating application. Other applications running on the system cannot access or modify the values.
The most common way to access application state is by means of the Application property of the Page object.
Using Application State
Application-state variables are, in effect, global variables for a given ASP.NET application. Like client-side application developers, ASP.NET programmers should always consider the impact of storing anything as a global variable.
The following issues are particularly important in this context:
- The memory impact of storing something in application state. The memory occupied by variables stored in application state is not released until the value is either removed or replaced, unlike for an individual Web page, in which all resources are torn down at the conclusion of a Web request. Keeping seldom-used 10 MB recordsets in application state permanently, for example, is not the best use of system resources. For this extreme example you can find better solutions using the ASP.NET Cache.
- The concurrency and synchronization implications of storing and accessing a global variable within a multithreaded server environment. Multiple threads within an application can access values stored in application state simultaneously. You should always be careful to ensure that if an application-scoped object is free-threaded, it contains built-in synchronization support. All custom objects that target the common language runtime are free-threaded. If an application-scoped object is not free-threaded, you must ensure that explicit synchronization methods are coded around it to avoid deadlocks, race conditions, and access violations.
- The scalability implications of storing and accessing a global variable within a multithreaded server environment. Locks should be used whenever an attempt is made to write or update a file. Locks that protect global resources are themselves global, and code running on multiple threads accessing global resources ultimately ends up contending on these locks. This causes the operating system to block the worker threads until the lock becomes available. In high-load server environments, this blocking can cause severe thread thrashing on the system. On multiprocessor systems, it can lead to processor underutilization (since all the threads for a processor theoretically can be stalled while waiting for a shared lock) and significant drops in overall scalability.
- The life-cycle implications of information stored in application state. The .NET Framework application domain or the process hosting a .NET-based application can be torn down and destroyed at any moment during application execution (as a result of crashes, code updates, scheduled process restarts, and so on). Because data stored in application state is not durable, it is lost if the host containing it is destroyed. If you want state to survive these types of failures, you should store it in a database or other durable store.
- Application state is not shared across a Web farm (in which an application is hosted by multiple servers) or a Web garden (in which an application is hosted by multiple processes on the same server). Variables stored in application state in either of those scenarios are global only to the particular process in which the application is running. Each application process can have different values. Therefore, you cannot rely on application state to store unique values or update global counters, for example, in Web farm and Web garden scenarios.
Despite these issues, well-designed application-level variables can be very powerful in Web applications. You can do a one-time (or infrequent) loading and calculation of information and then use application state to cache it for speedy, in-memory access during later Web requests.
For example, a stock market Web site might fetch extensive financial stock information (perhaps 40 MB of data) from a database every 5 minutes during the day and then cache it in application state where all subsequent lookup requests can access it. The result is a dramatic improvement in per-request performance, since incoming requests do not require cross-process, cross-computer, or database round trips. On the other hand, a better use of resources for large, transient data blocks might be to use the cache.
Application State Collections
The HttpApplicationState class exposes two state collections: Contents and StaticObjects.
The Contents collection exposes all variable items that have been added to the application-state collection directly through code. For example:
' Visual Basic code from within a page, a handler, or Global.asax.
Application("Message") = "MyMsg"
Application("AppStartTime") = Now
[C#]
// C# code from within a page, a handler, or Global.asax.
Application["Message"] = " MyMsg";
Application["AppStartTime"] = DateTime.Now;
For compatibility with earlier versions of ASP, these variables also can be accessed using the Contents property of the application object, as in the following example.
' Visual Basic code from within a page, a handler, or Global.asax.
Application.Contents("Message") = " MyMsg"
Application.Contents("AppStartTime") = Now
[C#]
// Visual Basic code from within a page, a handler, or Global.asax.
Application.Contents["Message"] = " MyMsg";
Application.Contents["AppStartTime"] = DateTime.Now;
The StaticObjects collection exposes all variable items that have been added to the application-state collection through <
object runat="server">
tags in the Global.asax file with a scope of application. For example:
' Global.asax definition.
<object runat="server" scope="application" ID="MyInfo" PROGID="MSWC.MYINFO">
</OBJECT>
Objects cannot be added to the StaticObjects collection from anywhere else within an ASP.NET application. The collection throws a NotSupportedException if you attempt to add objects directly through code.
Note that the .NET page compiler automatically injects member references into all objects stored within the StaticObjects collection at page-compilation time so that developers can access these application objects at page-request time without having to reference the Application collection. For example:
<html>
</body>
Application Level Title: <%= MyInfo.Title %>
<body>
</html>
Application State Synchronization
Multiple threads within an application can simultaneously access values stored in application state. Consequently, when you create something that needs to access application-state values, you must always ensure that the application-state object is free-threaded and performs its own internal synchronization or else performs manual synchronization steps to protect against race conditions, deadlocks, and access violations.
The HttpApplicationState class provides two methods, Lock and Unlock, that allow only one thread at a time to access application-state variables.
Calling Lock on the Application object causes ASP.NET to block attempts by code running on other worker threads to access anything in application state. These threads are unblocked only when the thread that called Lock calls the corresponding Unlock method on the Application object.
The following code example demonstrates the use of locking to guard against race conditions.
' Visual Basic code from within a page, a handler, or Global.asax.
Application.Lock()
Application("SomeGlobalCounter") = _
CType(Application("SomeGlobalCounter"), Integer) + 1
Application.UnLock()
[C#]
// C# code from within a page, a handler, or Global.asax.
Application.Lock();
Application["SomeGlobalCounter"] =
(int)Application["SomeGlobalCounter"] + 1;
Application.UnLock();
If you do not explicitly call Unlock, the .NET Framework automatically removes the lock when the request completes, when the request times out, or when an unhandled error occurs during request execution and causes the request to fail. This automatic unlocking prevents the application from deadlocking.