Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp 2008 And The .NET 3.5 Platform [eng]

.pdf
Скачиваний:
78
Добавлен:
16.08.2013
Размер:
22.5 Mб
Скачать

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():

1258 CHAPTER 33 ASP.NET STATE MANAGEMENT TECHNIQUES

private void CleanAppData()

{

//Remove a single item via string name.

Application.Remove["SomeItemIDontNeed"];

//Destroy all application data!

Application.RemoveAll();

}

If you wish to simply change the value of an existing application-level variable, you only need to make a new assignment to the data item in question. Assume your page now supports a new Button type that allows your user to change the current hotshot salesperson by reading in a value from a TextBox named txtNewSP. The Click event handler is as you would expect:

protected void btnSetNewSP_Click(object sender, EventArgs e)

{

// Set the new Salesperson.

((CarLotInfo)Application["CarSiteInfo"]).salesPersonOfTheMonth = txtNewSP.Text;

}

If you run the web application, you will find that the application-level variable has been updated. Furthermore, given that application variables are accessible from all user sessions, if you were to launch three or four instances of your web browser, you would find that if one instance changes the current hotshot salesperson, each of the other browsers displays the new value on postback.

Understand that if you have a situation where a set of application-level variables must be updated as a unit, you risk the possibility of data corruption (given that it is technically possible that an application-level data point may be changed while another user is attempting to access it!). While you could take the long road and manually lock down the logic using threading primitives of the System.Threading namespace, the HttpApplicationState type has two methods, Lock() and

Unlock(), that automatically ensure thread safety:

// Safely access related application data.

Application.Lock(); Application["SalesPersonOfTheMonth"] = "Maxine";

Application["CurrentBonusedEmployee"] = Application("SalesPersonOfTheMonth"); Application.Unlock();

Handling Web Application Shutdown

The HttpApplicationState type is designed to maintain the values of the items it contains until one of two situations occurs: the last user on your site times out (or manually logs out) or someone manually shuts down the website via IIS. In each case, the Application_End() method of the HttpApplication-derived type will automatically be called. Within this event handler, you are able to perform whatever sort of cleanup code is necessary:

void Application_End(Object sender, EventArgs e)

{

//Write current application variables

//to a database or whatever else you need to do...

}

Source Code The AppState project is included under the Chapter 33 subdirectory.

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();

}

}