The Global.asax File

Global.asax is a text file that houses application-level event handlers, declarations that pertain to all parts of the application, and other global application elements. ASP.NET applications don鈥檛 have to include Global.asax files, but most do. An application can have only one Global.asax file. That file must be located in the application鈥檚 virtual root directory.

What鈥檚 inside a Global.asax file? Global.asax supports three element types:

Of the three, the first two are used more often. Global event handlers are particularly important and are the number one reason why developers include Global.asax files in their applications. We鈥檒l discuss global directives first and global event handlers second. Then, for completeness, we鈥檒l talk about global object tags, too.

Global Directives

Global directives, also known as application directives, provide application-wide instructions to the ASP.NET compilation engine. A Global.asax file supports three types of global directives:

Global.asax can contain just one @ Application directive, but it places no limit on the number of @ Import and @ Assembly directives.

The @ Application Directive

@ Application directives serve two purposes: they enable developers to add descriptive text to applications, and they facilitate code-behind programming in Global.asax files. An @ Application directive accompanied by a Description attribute adds descriptive text, as in

<%@聽Application聽Description="My聽First聽ASP.NET聽Application" %>

ASP.NET ignores Description attributes, so descriptions declared with it are visible only to those persons with access to your Global.asax files.

The @ Application directive also supports an Inherits attribute that enables code to be removed from Global.asax and packaged in a separate DLL. Suppose, for example, that you included the following Global.asax file in an application:

<%@聽Import聽Namespace="System.Data" %>

<script聽language="C#" runat="server">
聽聽void聽Application_Start聽()
聽聽{
聽聽聽聽聽聽DataSet聽ds聽=聽new聽DataSet聽();
聽聽聽聽聽聽ds.ReadXml聽(Server.MapPath聽("GlobalData.xml"));
聽聽聽聽聽聽Application["GlobalData"]聽=聽ds;
聽聽}
</script>

Coded this way, Application_Start, which is an event handler that fires each time the application starts up, is compiled the first time Global.asax is accessed by ASP.NET. To avoid run-time compilation, you can remove Application_Start from Global.asax and code it into a class that derives from System.Web.HttpApplication:

using聽System.Web;
using聽System.Data;

public聽class聽MyApp聽:聽HttpApplication
{
聽聽聽聽public聽void聽Application_Start聽()
聽聽聽聽{
聽聽聽聽聽聽聽聽DataSet聽ds聽=聽new聽DataSet聽();
聽聽聽聽聽聽聽聽ds.ReadXml聽("GlobalData.xml");
聽聽聽聽聽聽聽聽Application["GlobalData"]聽=聽ds;
聽聽聽聽}
}

Then you compile the CS file into a DLL, place the DLL in the application root鈥檚 bin directory, and reduce Global.asax to one simple statement:

<%@聽Application聽Inherits="MyApp" %>

Code-behind offers the same benefits to Global.asax that it offers to ASPX files: it catches compilation errors before the application is deployed, and it enables developers to code handlers in C++ and other languages that ASP.NET doesn鈥檛 explicitly support.

A look behind the scenes reveals why code-behind classes used by Global.asax files derive from HttpApplication. ASP.NET starts an application running when the very first request for that application arrives. Starting an application involves launching a process named Aspnet_wp.exe (commonly referred to as the ASP.NET worker process) if it isn鈥檛 already running and creating a new application domain in that process to host the application and segregate it from other running ASP.NET applications. In the absence of code-behind, startup also involves parsing Global.asax and placing any content found there into a temporary file containing a class derived from HttpApplication, compiling the temporary file into a DLL, and instantiating the derived class. The resulting HttpApplication object handles the request that prompted the application to start up. As a performance optimization, ASP.NET maintains a pool of such objects and uses them to service incoming requests.

One implication of this design is that any code you include in Global.asax executes in the context of an HttpApplication object. That means you can call HttpApplication instance methods and access HttpApplication instance properties from anywhere in Global.asax. It also explains why using code-behind in Global.asax means deriving from System.Web.HttpApplication rather than System.Web.UI.Page. Because the system places Global.asax code in an HttpApplication-derived class, you must do the same if you want to get your code out of Global.asax and into a DLL.

The @ Import Directive

The @ Import directive serves the same purpose in Global.asax that it serves in ASPX files: it imports namespaces that ASP.NET doesn鈥檛 import by default. For example, let鈥檚 say you include the following <script> block in Global.asax:

<script聽language="C#" runat="server">
聽聽void聽Application_Start聽()
聽聽{
聽聽聽聽聽聽DataSet聽ds聽=聽new聽DataSet聽();
聽聽聽聽聽聽ds.ReadXml聽(Server.MapPath聽("GlobalData.xml"));
聽聽聽聽聽聽Application["GlobalData"]聽=聽ds;
聽聽}
</script>

Because DataSet is defined in the System.Data namespace and System.Data isn鈥檛 imported by default, you must either fully qualify all references to DataSet by including the namespace name or place the following directive at the top of Global.asax:

<%@聽Import聽Namespace="System.Data" %>

@ Import directives in Global.asax pertain only to code in Global.asax. They do not import namespaces into other of the application鈥檚 files.

The @ Assembly Directive

The @ Assembly directive does for Global.asax what @ Assembly does for ASPX files: it identifies assemblies Global.asax uses that ASP.NET doesn鈥檛 link to by default. (For a list of default assemblies, see Chapter 5.)

As an example, suppose your Global.asax file uses classes in the System.DirectoryServices namespace. Because that namespace isn鈥檛 imported by default and because the types that belong to that namespace live in System.DirectoryServices.dll, which ASP.NET doesn鈥檛 link to by default, you need to include the following statements in Global.asax:

<%@聽Import聽Namespace="System.DirectoryServices" %>
<%@聽Assembly聽Name="System.DirectoryServices" %>

If you don鈥檛, ASP.NET will greet you with an error message the moment the application starts up.

Global Event Handlers

The most common reason for including Global.asax files in ASP.NET applications is to handle global events鈥攅vents that aren鈥檛 specific to a particular page but that apply to the application as a whole. Some global events are fired by the HttpApplication instances that process individual requests. Others are fired by HTTP modules鈥攑lug-in components that provide services such as authentication and output caching to ASP.NET. Some events fire on every request. Others fire at predictable junctures in an application鈥檚 lifetime, such as when the application starts or stops. Still others fire conditionally鈥攆or example, when an unhandled exception occurs. Regardless of when a global event fires or who fires it, you can process it by including a handler in Global.asax.

Start and End Events

ASP.NET fires global events named Start and End when an application starts and stops. To process these events, include handlers named Application_Start and Application_End in Global.asax:

<script聽language="C#" runat="server">
聽聽void聽Application_Start聽()
聽聽{
聽聽聽聽...
聽聽}

聽聽void聽Application_End聽()
聽聽{
聽聽聽聽...
聽聽}
</script>

Application_Start is called when the application receives its first request. This handler is frequently used to initialize application state or the ASP.NET application cache (both of which are introduced later in this chapter) with data that is global to the application鈥攖hat is, shared by all of its users. Application_End is called when the application shuts down. Typically, that happens when the application has run for 20 minutes without receiving an HTTP request. Application_End isn鈥檛 used all that often because ASP.NET applications don鈥檛 have to clean up after themselves by deleting objects created in Application_Start, but it鈥檚 sometimes used to write data to a persistent storage medium prior to shutdown so that the data can be reloaded the next time the application starts and to dispose of objects that encapsulate unmanaged resources such as database connections.

Later in this chapter, you鈥檒l learn about ASP.NET session state. Session state is a mechanism for storing per-user information (such as shopping carts) in Web applications and preserving it across requests. Session state services are provided by an HTTP module named SessionStateModule, which fires a Start event each time it creates a session and an End event each time a session ends. You can process these events by including handlers named Session_Start and Session_End in Global.asax:

<script聽language="C#" runat="server">
聽聽void聽Session_Start聽()
聽聽{
聽聽聽聽...
聽聽}

聽聽void聽Session_End聽()
聽聽{
聽聽聽聽...
聽聽}
</script>

Session_Start is called when a user visits your site who hasn鈥檛 been there recently (usually in the last 20 minutes). Session_End is typically called when a session times out, which by default happens 20 minutes after the last request is received from the user for whom the session was created. The most common use for Session_Start is to initialize session state with data that is unique to each user. You鈥檒l see examples later in this chapter.

Per-Request Events

Global.asax can also include handlers for events fired by HttpApplication instances. If present in Global.asax, the following methods are called in every request in response to HttpApplication events. They鈥檙e listed in the order in which they鈥檙e called.

Per-Request Global Event Handlers

Method

Description

Application_BeginRequest

Called at the beginning of each request

Application_AuthenticateRequest

Called to authenticate the caller

Application_AuthorizeRequest

Called to determine whether the caller is authorized to access the requested resource

Application_ResolveRequestCache

Called to resolve the current request by providing content from a cache

Application_AcquireRequestState

Called to associate the current request with a session and populate session state

Application_PreRequestHandlerExecute

Called to prepend content to the HTTP response

Application_PostRequestHandlerExecute

Called to append content to the HTTP response

Application_ReleaseRequestState

Called to release (store) any state associated with this session

Application_UpdateRequestCache

Called to update a cache with content returned in the response

Application_EndRequest

Called at the end of each request

These handlers let you customize ASP.NET by plugging into the request processing pipeline. For example, Application_ResolveRequestCache and Application_UpdateRequestCache could be used to implement a custom output cache. Application_AuthenticateRequest and Application_AuthorizeRequest provide hooks for modifying ASP.NET鈥檚 security apparatus. The event handlers Application_PreRequestHandlerExecute and Application_PostRequestHandler颅Execute enable HTTP responses to be modified before they鈥檙e returned to clients. The following Global.asax file uses the latter of these two methods to place a copyright notice at the bottom of each and every page (assuming, of course, that your pages use HTML flow layout rather than absolute positioning):

<script聽language="C#" runat="server">
聽聽void聽Application_PostRequestHandlerExecute聽(Object聽sender,聽EventArgs聽e)
聽聽{
聽聽聽聽聽聽HttpApplication聽app聽=聽(HttpApplication)聽sender;
聽聽聽聽聽聽app.Context.Response.Write聽("<hr><center><i>" +
聽聽聽聽聽聽聽聽聽 "Copyright聽漏聽2002聽by聽Me,聽Myself,聽and聽I</i></center>");
聽聽}
</script>

Outputting a copyright notice this way rather than duplicating it in every ASPX file lets you change it in one place to modify it everywhere it shows up.

Error Events

The events listed above fire in each and every request. HttpApplication also defines an Error event that fires if ASP.NET throws an unhandled exception. You can process Error events by including an Application_Error handler in Global.asax. Here鈥檚 a Global.asax file that logs unhandled exceptions in the NT event log. It uses the FCL鈥檚 System.Diagnostics.EventLog class to write to the event log:

<%@聽Import聽Namespace="System.Diagnostics" %>聽

<script聽language="C#" runat="server">聽
聽聽void聽Application_Error聽(Object聽sender,聽EventArgs聽e)
聽聽{
聽聽聽聽聽聽//聽Formulate聽a聽message聽to聽write聽to聽the聽event聽log
聽聽聽聽聽聽string聽msg聽= "Error聽accessing " +聽Request.Path聽+ "\n" +
聽聽聽聽聽聽聽聽聽聽Server.GetLastError聽().ToString聽();

聽聽聽聽聽聽//聽Write聽an聽entry聽to聽the聽event聽log
聽聽聽聽聽聽EventLog聽log聽=聽new聽EventLog聽();
聽聽聽聽聽聽log.Source聽= "My聽ASP.NET聽Application";
聽聽聽聽聽聽log.WriteEntry聽(msg,聽EventLogEntryType.Error);
聽聽}
</script>

It鈥檚 not unwise to include a handler like this one in every ASP.NET application so that you can detect unhandled exceptions by periodically checking the NT event log. You could even modify the handler to send an e-mail message to a system administrator to apprise him or her of unhandled exceptions (a sure sign of a sick or buggy application) the moment they occur.

Don鈥檛 be surprised if you encounter a Global.asax file containing an event handler that鈥檚 not mentioned here. HttpApplication fires a few other events that I haven鈥檛 listed because they鈥檙e rarely used or used internally by ASP.NET. Plus, ASP.NET can be extended with HTTP modules that fire global events of their own. HTTP modules can also sink global events, which is precisely how the HTTP modules built into ASP.NET work much of their magic. A full discussion is beyond the scope of this chapter, but further information regarding HTTP modules and events is available in the Microsoft .NET Framework SDK.

Global Object Tags

Global object tags create object instances declaratively. Suppose you want a new instance of ShoppingCart created for each user that visits your site. Rather than do this:

<script>
聽聽void聽Session_Start聽()
聽聽{
聽聽聽聽聽聽Session["MyShoppingCart"]聽=聽new聽ShoppingCart聽();
聽聽}
</script>

you can do this:

<object聽id="MyShoppingCart" class="ShoppingCart" scope="session"
聽聽runat="server" />

Assuming ShoppingCart has an Add method, a Web form could add an item to a user鈥檚 shopping cart by doing this:

MyShoppingCart.Add聽(...);

This code might not make a lot of sense right now, but it鈥檒l make plenty of sense by the end of the chapter.

An <object> tag鈥檚 Scope attribute assigns a scope to the object instances it creates. Scope=鈥淎pplication鈥?creates one object instance, which is shared by all users of the application. Scope=鈥淪ession鈥?creates one object instance per session (that is, per user). Scope=鈥淧ipeline鈥?creates a unique instance of the object for each and every request.

ASP.NET doesn鈥檛 create objects declared with <object> tags unless it has to鈥攖hat is, until they鈥檙e requested for the first time. 鈥淟azy instantiation鈥?prevents objects from being created unnecessarily if the application doesn鈥檛 use them.