
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
1252 CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES
<%@ Application Language="C#" %>
<script runat="server">
void Application_Start(object sender, EventArgs e)
{
// Code that runs on application startup.
}
void Application_End(object sender, EventArgs e)
{
//Code that runs on application shutdown.
}
void Application_Error(object sender, EventArgs e)
{
// Code that runs when an unhandled error occurs.
}
void Session_Start(object sender, EventArgs e)
{
// Code that runs when a new session is started.
}
void Session_End(object sender, EventArgs e)
{
//Code that runs when a session ends.
//Note: The Session_End event is raised only when the sessionstate mode
//is set to InProc in the Web.config file. If session mode is set to
//StateServer or SQLServer, the event is not raised.
}
</script>
Looks can be deceiving, however. At runtime, the code within this <script> block is assembled into a class type deriving from System.Web.HttpApplication (if you have a background in ASP.NET 1.x, you may recall that the Global.asax code-behind file literally did define a class deriving from
HttpApplication).
As mentioned, the members defined inside Global.asax are in event handlers that allow you to interact with application-level (and session-level) events. Table 33-1 documents the role of each member.
Table 33-1. Core Types of the System.Web Namespace
Event Handler |
Meaning in Life |
Application_Start() |
This event handler is called the very first time the web application is |
|
launched. Thus, this event will fire exactly once over the lifetime of a web |
|
application. This is an ideal place to define application-level data used |
|
throughout your web application. |
Application_End() |
This event handler is called when the application is shutting down. This |
|
will occur when the last user times out or if you manually shut down the |
|
application via IIS. |
Session_Start() |
This event handler is fired when a new user logs on to your application. |
|
Here you may establish any user-specific data points. |

CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES |
1253 |
Event Handler |
Meaning in Life |
Session_End() |
This event handler is fired when a user’s session has terminated (typically |
|
through a predefined timeout). |
Application_Error() |
This is a global error handler that will be called when an unhandled |
|
exception is thrown by the web application. |
|
|
The Global Last-Chance Exception Event Handler
First, let me point out the role of the Application_Error() event handler. Recall that a specific page may handle the Error event to process any unhandled exception that occurred within the scope of the page itself. In a similar light, the Application_Error() event handler is the final place to handle an exception that was not handled by a given page. As with the page-level Error event, you are able to access the specific System.Exception using the inherited Server property:
void Application_Error(object sender, EventArgs e)
{
//Obtain the unhandled error.
Exception ex = Server.GetLastError();
//Process error here...
//Clear error when finished.
Server.ClearError();
}
Given that the Application_Error() event handler is the last-chance exception handler for your web application, it is quite common to implement this method in such a way that the user is transferred to a predefined error page on the server. Other common duties may include sending an e-mail to the web administrator or writing to an external error log.
The HttpApplication Base Class
As mentioned, the Global.asax script is dynamically generated into a class deriving from the System.Web.HttpApplication base class, which supplies some of the same sort of functionality as the System.Web.UI.Page type (without a visible user interface). Table 33-2 documents the key members of interest.
Table 33-2. Key Members Defined by the System.Web.HttpApplication Type
Property |
Meaning in Life |
Application |
This property allows you to interact with application-level variables, using the |
|
exposed HttpApplicationState type. |
Request |
This property allows you to interact with the incoming HTTP request, using the |
|
underlying HttpRequest object. |
Response |
This property allows you to interact with the incoming HTTP response, using the |
|
underlying HttpResponse object. |
Server |
This property gets the intrinsic server object for the current request, using the |
|
underlying HttpServerUtility object. |
Session |
This property allows you to interact with session-level variables, using the |
|
underlying HttpSessionState object. |
|
|

1254 CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES
Again, given that the Global.asax file does not explicitly document that HttpApplication is the underlying base class, it is important to remember that all of the rules of the “is-a” relationship do indeed apply. For example, if you were to apply the dot operator to the base keyword within any of the members within Global.asax, you would find you have immediate access to all members of the chain of inheritance, as you see in Figure 33-3.
Figure 33-3. Remember that HttpApplication is the parent of the type lurking within Global.asax.
Understanding the Application/Session Distinction
Under ASP.NET, application state is maintained by an instance of the HttpApplicationState type. This class enables you to share global information across all users (and all pages) who are logged on to your ASP.NET application. Not only can application data be shared by all users on your site, but also if the value of an application-level data point changes, the new value is seen by all users on their next postback.
On the other hand, session state is used to remember information for a specific user (again, such as items in a shopping cart). Physically, a user’s session state is represented by the HttpSessionState class type. When a new user logs on to an ASP.NET web application, the runtime will automatically assign that user a new session ID, which by default will expire after 20 minutes of inactivity. Thus, if 20,000 users are logged on to your site, you have 20,000 distinct HttpSessionState objects, each of which is automatically assigned a unique session ID. The relationship between a web application and web sessions is shown in Figure 33-4.
As you may remember based on past experience, under classic ASP, applicationand sessionstate data is represented using distinct COM objects (e.g., Application and Session). Under ASP.NET, Page-derived types as well as the HttpApplication type make use of identically named properties (i.e., Application and Session), which expose the underlying HttpApplicationState and
HttpSessionState types.

CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES |
1255 |
Figure 33-4. The application/session state distinction
Maintaining Application-Level State Data
The HttpApplicationState type enables developers to share global information across multiple sessions in an ASP.NET application. For example, you may wish to maintain an application-wide connection string that can be used by all pages, a common DataSet used by multiple pages, or any other piece of data that needs to be accessed on an application-wide scale. Table 33-3 describes some core members of this type.
Table 33-3. Members of the HttpApplicationState Type
Members |
Meaning in Life |
Add() |
This method allows you to add a new name/value pair into |
|
the HttpApplicationState type. Do note that this method is |
|
typically not used in favor of the indexer of the |
|
HttpApplicationState class. |
AllKeys |
This property returns an array of System.String types that |
|
represent all the names in the HttpApplicationState type. |
Clear() |
This method deletes all items in the HttpApplicationState |
|
type. This is functionally equivalent to the RemoveAll() |
|
method. |
Count |
This property gets the number of item objects in the |
|
HttpApplicationState type. |
Lock(), Unlock() |
These two methods are used when you wish to alter a set of |
|
application variables in a thread-safe manner. |
RemoveAll(), Remove(), RemoveAt() |
These methods remove a specific item (by string name) |
|
within the HttpApplicationState type. RemoveAt() removes |
|
the item via a numerical indexer. |
|
|
To illustrate working with application state, create a new ASP.NET web application named AppState and insert a new Global.asax file. When you create data members that can be shared among all active sessions, you need to establish a set of name/value pairs. In most cases, the most

1256 CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES
natural place to do so is within the Application_Start() event handler of the HttpApplication- derived type, for example:
void Application_Start(Object sender, EventArgs e)
{
// Set up some application variables.
Application["SalesPersonOfTheMonth"] = "Chucky"; Application["CurrentCarOnSale"] = "Colt"; Application["MostPopularColorOnLot"] = "Black";
}
During the lifetime of your web application (which is to say, until the web application is manually shut down or until the final user times out), any user (on any page) may access these values as necessary. Assume you have a page that will display the current discount car within a Label via a button Click event handler:
protected void btnShowCarOnSale_Click(object sender, EventArgs arg)
{
lblCurrCarOnSale.Text = string.Format("Sale on {0}'s today!", (string)Application["CurrentCarOnSale"]);
}
Like the ViewState property, notice how you should cast the value returned from the HttpApplicationState type into the correct underlying type as the Application property operates on general System.Object types.
Now, given that the HttpApplicationState type can hold any type, it should stand to reason that you can place custom types (or any .NET object) within your site’s application state. Assume you would rather maintain the three current application variables within a strongly typed class named CarLotInfo:
public class CarLotInfo
{
public CarLotInfo(string s, string c, string m)
{
salesPersonOfTheMonth = s; currentCarOnSale = c; mostPopularColorOnLot = m;
}
//Public for easy access, could also make use of automatic
//property syntax.
public string salesPersonOfTheMonth; public string currentCarOnSale; public string mostPopularColorOnLot;
}
With this helper class in place, you could modify the Application_Start() event handler as follows:
void Application_Start(Object sender, EventArgs e)
{
// Place a custom object in the application data sector.
Application["CarSiteInfo"] =
new CarLotInfo("Chucky", "Colt", "Black");
}
and then access the information using the public field data within a server-side Click event handler for a Button type named btnShowAppVariables:

CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES |
1257 |
protected void btnShowAppVariables_Click(object sender, EventArgs e)
{
CarLotInfo appVars = ((CarLotInfo)Application["CarSiteInfo"]);
string appState =
string.Format("<li>Car on sale: {0}</li>", appVars.currentCarOnSale);
appState +=
string.Format("<li>Most popular color: {0}</li>", appVars.mostPopularColorOnLot);
appState +=
string.Format("<li>Big shot SalesPerson: {0}</li>", appVars.salesPersonOfTheMonth);
lblAppVariables.Text = appState;
}
Given that the current car-on-sale data is now exposed from a custom class type, your btnShowCarOnSale Click event handler would also need to be updated like so:
protected void btnShowCarOnSale_Click1(object sender, EventArgs e)
{
lblCurrCarOnSale.Text = String.Format("Sale on {0}'s today!", ((CarLotInfo)Application["CarSiteInfo"]).currentCarOnSale);
}
If you now run this page, you will find that a list of each application variable is displayed on the page’s Label types, as displayed in Figure 33-5.
Figure 33-5. Displaying application data
Modifying Application Data
You may programmatically update or delete any or all members using members of the HttpApplicationState type during the execution of your web application. For example, to delete a specific item, simply call the Remove() method. If you wish to destroy all application-level data, call
RemoveAll():


CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES |
1259 |
Working with the Application Cache
ASP.NET provides a second and more flexible manner to handle application-wide data. As you recall, the values within the HttpApplicationState object remain in memory as long as your web application is alive and kicking. Sometimes, however, you may wish to maintain a piece of application data only for a specific period of time. For example, you may wish to obtain an ADO.NET DataSet that is valid for only five minutes. After that time, you may want to obtain a fresh DataSet to account for possible database updates. While it is technically possible to build this infrastructure using HttpApplicationState and some sort of handcrafted monitor, your task is greatly simplified using the ASP.NET application cache.
As suggested by its name, the ASP.NET System.Web.Caching.Cache object (which is accessible via the Context.Cache property) allows you to define an object that is accessible by all users (from all pages) for a fixed amount of time. In its simplest form, interacting with the cache looks identical to interacting with the HttpApplicationState type:
//Add an item to the cache.
//This item will *not* expire.
Context.Cache["SomeStringItem"] = "This is the string item";
//Get item from the cache.
string s = (string)Context.Cache["SomeStringItem"]
■Note If you wish to access the Cache from within Global.asax, you are required to use the Context property. However, if you are within the scope of a System.Web.UI.Page-derived type, you can make use of the Cache object directly.
Now, understand that if you have no interest in automatically updating (or removing) an appli- cation-level data point (as seen here), the Cache object is of little benefit, as you can directly use the HttpApplicationState type. However, when you do wish to have a data point destroyed after a fixed point of time—and optionally be informed when this occurs—the Cache type is extremely helpful.
The System.Web.Caching.Cache class defines only a small number of members beyond the type’s indexer. For example, the Add() method can be used to insert a new item into the cache that is not currently defined (if the specified item is already present, Add() effectively does nothing). The Insert() method will also place a member into the cache. If, however, the item is currently defined, Insert() will replace the current item with the new type. Given that this is most often the behavior you will desire, I’ll focus on the Insert() method exclusively.
Fun with Data Caching
Let’s see an example. To begin, create a new ASP.NET web application named CacheState and insert a Global.asax file. Like an application-level variable maintained by the HttpApplicationState type, the Cache may hold any System.Object-derived type and is often populated within the Application_Start() event handler. For this example, the goal is to automatically update the contents of a DataSet every 15 seconds. The DataSet in question will contain the current set of records from the Inventory table of the AutoLot database created during our discussion of ADO.NET.
Given these design notes, set a reference to AutoLotDAL.dll (see Chapter 22) and update your Global class type like so (code analysis to follow):
<%@ Application Language="C#" %>
<%@ Import Namespace = "AutoLotConnectedLayer" %> <%@ Import Namespace = "System.Data" %>

1260 CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES
<script runat="server">
// Define a static-level Cache member variable. static Cache theCache;
void Application_Start(Object sender, EventArgs e)
{
//First assign the static 'theCache' variable. theCache = Context.Cache;
//When the application starts up,
//read the current records in the
//Inventory table of the AutoLot DB.
InventoryDAL dal = new InventoryDAL(); dal.OpenConnection(@"Data Source=(local)\SQLEXPRESS;" +
"Initial Catalog=AutoLot;Integrated Security=True"); DataTable theCars = dal.GetAllInventory(); dal.CloseConnection();
//Now store DataTable in the cache. theCache.Insert("AppDataTable",
theCars, null, DateTime.Now.AddSeconds(15), Cache.NoSlidingExpiration, CacheItemPriority.Default,
new CacheItemRemovedCallback(UpdateCarInventory));
}
// The target for the CacheItemRemovedCallback delegate. static void UpdateCarInventory(string key, object item,
CacheItemRemovedReason reason)
{
InventoryDAL dal = new InventoryDAL(); dal.OpenConnection(@"Data Source=(local)\SQLEXPRESS;" +
"Initial Catalog=AutoLot;Integrated Security=True"); DataTable theCars = dal.GetAllInventory(); dal.CloseConnection();
// Now store in the cache. theCache.Insert("AppDataTable",
theCars, null, DateTime.Now.AddSeconds(15), Cache.NoSlidingExpiration, CacheItemPriority.Default,
new CacheItemRemovedCallback(UpdateCarInventory));
}
...
</script>
First, notice that the Global type has defined a static Cache member variable. The reason is that you have defined two shared members that need to access the Cache object. Recall that static members do not have access to inherited members, therefore you can’t use the Context property!
Inside the Application_Start() event handler, you fill a DataTable and place the object within the application cache. As you would guess, the Context.Cache.Insert() method has been overloaded a number of times. Here, you supply a value for each possible parameter. Consider the following commented call to Add():

CHAPTER 33 ■ ASP.NET STATE MANAGEMENT TECHNIQUES |
1261 |
theCache.Add("AppDataTable", |
// Name used to identify item in the cache. |
theCars, |
// Object to put in the cache. |
null, |
// Any dependencies for this object? |
DateTime.Now.AddSeconds(15), |
// How long item will be in cache. |
Cache.NoSlidingExpiration, |
// Fixed or sliding time? |
CacheItemPriority.Default, |
// Priority level of cache item. |
// Delegate for CacheItemRemove event.
new CacheItemRemovedCallback(UpdateCarInventory));
The first two parameters simply make up the name/value pair of the item. The third parameter allows you to define a CacheDependency type (which is null in this case, as you do not have any other entities in the cache that are dependent on the DataTable).
■Note The ability to define a CacheDependency type is quite interesting. For example, you could establish a dependency between a member and an external file. If the contents of the file were to change, the type can be automatically updated. Check out the .NET Framework 3.5 SDK documentation for further details.
The next three parameters are used to define the amount of time the item will be allowed to remain in the application cache and its level of priority. Here, you specify the read-only Cache. NoSlidingExpiration field, which informs the cache that the specified time limit (15 seconds) is absolute. Finally, and most important for this example, you create a new CacheItemRemovedCallback delegate type, and pass in the name of the method to call when the DataSet is purged. As you can see from the signature of the UpdateCarInventory() method, the CacheItemRemovedCallback delegate can only call methods that match the following signature:
static void UpdateCarInventory(string key, object item, CacheItemRemovedReason reason)
{
}
So, at this point, when the application starts up, the DataTable is populated and cached. Every 15 seconds, the DataTable is purged, updated, and reinserted into the cache. To see the effects of doing this, you need to create a Page that allows for some degree of user interaction.
Modifying the *.aspx File
Update the UI of your initial *.aspx file as shown in Figure 33-6.
In the page’s Load event handler, configure your GridView to display the current contents of the cached DataTable the first time the user posts to the page (be sure to import the AutoLotConnectedLayer namespace within your code file):
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
carsGridView.DataSource = (DataTable)Cache["AppDataTable"]; carsGridView.DataBind();
}
}