Overriding the HttpApplication to include your own state and event handling is a matter of adding a global
application object to your site. If you ask Microsoft Visual Studio to
create a normal Web site for you (that is, click File, New, Website,
ASP.NET Web Site in Visual Studio), Visual Studio throws a singular global.asax file into your project. Global.asax includes a server-side script block to hold any code you want to add to the application object.
If you ask Visual Studio to
create an ASP.NET Web Application Project (that is, click File, New
Project, ASP.NET Web Application in Visual Studio), Visual Studio adds a
pair of files, Global.asax and Global.asax.cs,
to your application. Global.asax and Global.asax.cs have the same
relationship to each other as an ASPX file and its accompanying CS file
have. In fact, you can use Visual Studio to add the global application
object to your application if it wasn't precreated for you. When you add
a Global.asax/Global.asax.cs file pair to your application, the
application is set up and ready to handle a few application-wide events.
Remember that the Page files include the Page directive at the top of the file. The Global.asax file includes a similar directive. The Application
directive tells the runtime compiling machinery that this file is meant
to serve as the application object. Unlike pages, there can be only one
Global.asax file in your application.
Example 1 shows an example of the Global.asax.cs file deriving from HttpApplication
that Visual Studio generates for you when you click File, New, Project,
ASP.NET Web Application. The Global.asax.cs provided by Visual Studio
handles the Application_Start, Application_End, Application_Error, Begin_Request, Authenticate_Request, Session_Start, and Session_End events.
Example 1. Global.asax.cs file and stubbed-out application event handlers
public class Global : System.Web.HttpApplication { protected void Application_Start(object sender, EventArgs e){} protected void Session_Start(object sender, EventArgs e){} protected void Application_BeginRequest(object sender, EventArgs e){} protected void Application_AuthenticateRequest(object sender, EventArgs e){} protected void Application_Error(object sender, EventArgs e){} protected void Session_End(object sender, EventArgs e){} protected void Application_End(object sender, EventArgs e){} }
|
To get an idea of how these events work, the following exercise illustrates placing a piece of data in the application's dictionary and retrieving it later when the page loads.
Managing application state
Create a new Web application project named UseApplication (that is, click File, New, Project, Empty ASP.NET Web Application in Visual Studio).
Drag a GridView
onto the default page. Don't assign a data source to it yet. In later
steps, you populate it with data that is stored with the application.
Add a Global.asax/Global.asax.cs
file pair to the site by right-clicking the project in Project Explorer
(or clicking Web Site, Add New Item on the main menu). Select the Global Application Class template, as shown here:
After you add the two files, Global.asax and Global.asax.cs, to your application, you can see that the Application_Start event is already handled (although it does nothing right now).
To have some data to store with the application object.
The project name is UseDataCaching. If you haven't generated the XML
and XSD files, you do so by running the UseDataCaching project. The XML
and schema files are generated when you click the Generate XML File
button on the CacheDependencies.aspx page. Click Web Site, Add Existing
Item on the main menu and find the file QuotesCollection.cs. In addition
to importing the QuotesCollection.cs file, grab the
QuotesCollection.xml and QuotesCollection.xsd files from the
UseDataCaching\App_Data directory.
Add some code to the Application_Start event to load the quotes data and place it in the application dictionary. Server.MapPath gives you the path from which the application is executing so that you can load the XML and XSD files. Storing the data in the dictionary is very much like adding it to the cache:
void Application_Start(Object sender, EventArgs e) {
QuotesCollection quotesCollection = new QuotesCollection();
String strAppPath = Server.MapPath("");
String strFilePathXml =
strAppPath + "\\app_data\\QuotesCollection.xml";
String strFilePathSchema = strAppPath +
"\\app_data\\QuotesCollection.xsd";
quotesCollection.ReadXmlSchema(strFilePathSchema);
quotesCollection.ReadXml(strFilePathXml);
Application["quotesCollection"] = quotesCollection;
}
Update the Page_Load method in the Default.aspx page to load the data from the application's dictionary. The application state is available through the page's reference to the Application object. Accessing data in the dictionary is a matter of indexing it correctly. After loading the data from the dictionary, apply it to the DataSource property in the GridView and bind the DataGrid:
protected void Page_Load(object sender, EventArgs e)
{
QuotesCollection quotesCollection =
(QuotesCollection)Application["quotesCollection"];
GridView1.DataSource = quotesCollection;
GridView1.DataBind();
}
1. Application State Caveats
As you can see, the application state and the application
data cache seem to overlap in functionality. Indeed, they're both
available from similar scopes (from any point in the application), and
getting the data in and out involves using the right indexer. However,
the application state and the cache are different in a couple of
significant ways.
First, items that go
into the application state stay there until you remove them explicitly.
The application data cache implements more flexibility in terms of
setting expirations and other removal/refresh conditions.
In addition, putting many items into the application state dictionary inhibits the scalability of your application. To make the application state thread safe, the HttpApplicationState class includes a Lock method that you can use to make the global state thread safe. Although using the Lock method ensures that the data is not corrupted, locking the application frequently greatly reduces the number of requests it can handle.
Ideally, data going into the application
state should be read only once when it is loaded and should be changed
very infrequently, if at all. As long as you're aware of these issues,
the application state can be a useful place to store information
required by all parts of your application.
2. Handling Events
The other useful aspect of
the application object is its ability to handle application-wide events.
As you can see in the previous example, the Global.asax.cs file is the place to insert global
event handlers. Visual Studio will insert a few for you when you simply
add one to your application. The events for which Visual Studio
generates stub handlers inside Global.asax.cs include Application_Start, Application_End, Application_Error, Application_BeginRequest, Application_AuthenticateRequest, Session_Start, and Session_End. A rundown of these events follows.
2.1. Application_Start
Application_Start happens when the application is first initialized—that is, when the first request comes through. Because Application_Start
happens first (and only once) during the lifetime of an application,
the most common response for the event is to load and initialize data at
the start of the application (as with the previous example).
2.2. Application_End
The ASP.NET runtime raises Application_End
as the application is shutting down. This is a useful place to clean up
any resources that require special attention for disposal.
2.3. Application_Error
Unfortunately, bad things
sometimes happen inside Web applications. If something bad has happened
in one of your existing applications, you might already have seen the
standard pale yellow and red ASP.NET error page. Once you deploy your
application, you probably don't want clients to see this sort of page.
Intercept this event (Application_Error)
to handle the error. Sometimes, an exception can be managed locally.
Exceptions that cannot be handled locally can be handled here.
2.4. Application_BeginRequest
The Application_BeginRequest event
occurs every time a user makes a request to the application. This is a
good place to handle anything that needs to occur before the request
starts in earnest.
2.5. Application_AuthenticateRequest
The Application_AuthenticateRequest
event occurs after ASP.NET confirms the identity of the user making a
request. You know who is making the request after this event is fired.
2.6. Session_Start
The Session_Start
event occurs when a user makes an initial request to the application,
which initializes a new session. This is a good place to initialize
session variables (if you want to initialize them before the page
loads).
2.7. Session_End
This event occurs when a session is released. Sessions end when they time out or when the Abandon method is called explicitly. This event happens only for applications whose session state is being held in-process.
3. HttpApplication Events
The events listed previously are implemented in the Visual Studio default HttpApplication override (either Global.asax
or Global.asax/Global.asax.cs depending on the project type). The
application object can fire a number of other events. Table 1
shows a summary of all the events pumped through the application
object. Some of these events are handled only through Global.asax,
whereas the others are handled in HttpModules.
Table 1. Application-wide Events
Event | Reason | Order | Only in Global.asax? |
---|
Application_Start | Application is starting up. | Start of app | Yes |
Application_End | Application is ending. | End of app | Yes |
Session_Start | Session is starting. | | Yes |
Session_End | Session is ending. | | Yes |
BeginRequest | A new request has been received. | 1 | No |
AuthenticateRequest/PostAuthenticateRequest | The user has been authenticated—that is, the security identity of the user has been established. | 2 | No |
AuthorizeRequest/PostAuthorizeRequest | The user has been authorized to use the requested resource. | 3 | No |
ResolveRequestCache/PostResolveRequestCache | Occurs
between authorizing the user and invoking the handler. This is where
output caching is handled. If content is cached, the application can
bypass the entire page-rendering process. | 4 | No |
AcquireRequestState/PostAcquireRequestState | Occurs when session state needs to be initialized. | 5 | No |
PreRequestHandlerExecute | Occurs
immediately before request is sent to the handler. This is a lastminute
chance to modify the output before it heads off to the client. | 6 | No |
PostRequestHandlerExecute | Occurs following the content being sent to the client. | 7 | No |
ReleaseRequestState/PostReleaseRequestState | Occurs following request handling. This event occurs so that the system can save state used if necessary. | 8 | No |
UpdateRequestCache/PostUpdateRequestCache | Occurs following handler execution. This is used by caching modules to cache responses. | 9 | No |
EndRequest | Fires after the request is processed. | 10 | No |
Disposed | Occurs before the application shuts down. | End of app | No |
Error | Fires when an unhandled application error occurs. | When an exception occurs | No |
PreSendRequestContent | Fires before content is sent to the client. | | No |
PreSendRequestHeaders | Fires before HTTP headers are sent to the client. | | No |
The following example shows how to time requests by intercepting the BeginRequest and the EndRequest events in Global.asax.
Timing requests
Open Global.asax.cs in the UseApplication Web application.
Look for the Application_BeginRequest handler. Notice that Visual Studio includes one. However, Application_EndRequest is not stubbed out, so you need to type that one in:
protected void Application_BeginRequest(object sender, EventArgs e)
{
}protected void Application_EndRequest(object sender, EventArgs e)
{
{
Implement the Application_BeginRequest handler by getting the current date and time and storing them in the Items property of the current HttpContext. The Items property is a name/value collection that you can index in the same way that you index the cache, the session state, and the HttpApplication dictionary. Implement the EndRequest
handler by comparing the time stamp obtained from the beginning of the
request to the current date and time. Print out the amount of time taken
to process the request using Response.Write.
protected void Application_BeginRequest(object sender, EventArgs e)
{
DateTime dateTimeBeginRequest = DateTime.Now;
HttpContext ctx = HttpContext.Current;
ctx.Items["dateTimeBeginRequest"] = dateTimeBeginRequest;
}
protected void Application_EndRequest(object sender, EventArgs e)
{
DateTime dateTimeEndRequest = DateTime.Now;
HttpContext ctx = HttpContext.Current;
DateTime dateTimeBeginRequest =
(DateTime)ctx.Items["dateTimeBeginRequest"];
TimeSpan duration = dateTimeEndRequest - dateTimeBeginRequest;
Response.Write("<b>From Global.asax: This request took " +
duration.ToString() + "</b></br>");
}
You should see the duration printed in the response returned to the browser.