The global.asax
file is used by Web applications to handle some application-level
events raised by the ASP.NET runtime or by registered HTTP modules. The global.asax
file is optional. If it is missing, the ASP.NET runtime environment
simply assumes you have no application or module event handlers defined.
To be functional, the global.asax file must be located in the root directory of the application. Only one global.asax file per application is accepted. Any global.asax files placed in subdirectories are simply ignored. Note that Microsoft Visual Studio doesn’t list global.asax in the items you can add to the project if there already is one.
Compiling global.asax
When the application is started, global.asax,
if present, is parsed into a source class and compiled. The resultant
assembly is created in the temporary directory just as any other
dynamically generated assembly would be. The following listing shows the
skeleton of the C# code that ASP.NET generates for any global.asax file:
namespace ASP
{
public class global_asax : System.Web.HttpApplication
{
//
// The source code of the "global.asax" file is flushed
// here verbatim. For this reason, the following code
// in global.asax would generate a compile error.
// int i;
// i = 2; // can't have statements outside methods
//
}
}
The class is named ASP.global_asax and is derived from the HttpApplication base class. In most cases, you deploy global.asax
as a separate text file; however, you can also write it as a class and
compile it either in a separate assembly or within your project’s
assembly. The class source code must follow the outline shown earlier
and, above all, must derive from HttpApplication. The assembly with the compiled version of global.asax must be deployed in the application’s Bin subdirectory.
Note, though, that even if you isolate the logic of the global.asax file in a precompiled assembly, you still need to have a (codeless) global.asax file that refers to the assembly, as shown in the following code:
<%@ Application Inherits="Core35.Global" %>
We’ll learn more about the syntax of global.asax in the next section, “Syntax of global.asax.” With a precompiled global application file, you certainly don’t risk exposing your source code over the Web to malicious attacks. However, even if you leave it as source code, you’re somewhat safe.
The global.asax
file, in fact, is configured so that any direct URL request for it is
automatically rejected by Internet Information Services (IIS). In this
way, external users cannot download or view the code it contains. The
trick that enables this behavior is the following line of code,
excerpted from machine.config:
<add verb="*" path="*.asax" type="System.Web.HttpForbiddenHandler" />
ASP.NET registers with IIS to handle .asax resources, but then it processes those direct requests through the HttpForbiddenHandler HTTP handler. As a result, when a browser requests an .asax resource, an error message is displayed on the page, as shown in Figure 1.
Tip
You can duplicate that line in your application’s web.config
file and block direct access to other types of resources specific to
your application. For this trick to work, though, make sure that the
resource type is redirected to ASP.NET at the IIS level. In other words,
you must first register aspnet_isapi.dll
to handle those files in the IIS metabase and then ask ASP.NET to block
any requests. You accomplish this through the IIS manager applet from
the Control Panel. |
When the global.asax
file of a running application is modified, the ASP.NET runtime detects
the change and prepares to shut down and restart the application. It
waits until all pending requests are completed and then fires the Application_End event. When the next request from a browser arrives, ASP.NET reparses and recompiles the global.asax file, and again raises the Application_Start event.
Syntax of global.asax
Four elements determine the syntax of the global.asax file. They are: application directives, code declaration blocks, server-side <object> tags, and server-side includes. These elements can be used in any order and number to compose a global.asax file.
Application Directives
The global.asax file supports three directives: @Application, @Import, and @Assembly. The @Import directive imports a namespace into an application; the @Assembly directive links an assembly to the application at compile time.
The @Application directive supports a few attributes—Description, Language, and Inherits. Description
can contain any text you want to use to describe the behavior of the
application. This text has only a documentation purpose and is
blissfully ignored by the ASP.NET parser. Language indicates the language being used in the file. The Inherits attribute indicates a code-behind class for the application to inherit. It can be the name of any class derived from the HttpApplication class. The assembly that contains the class must be located in the Bin subdirectory of the application.
Code Declaration Blocks
A global.asax file can contain code wrapped by a <script> tag. Just as for pages, the <script> tag must have the runat attribute set to server. The language attribute indicates the language used throughout:
<script language="C#" runat="server">
...
</script>
If the language
attribute is not specified, ASP.NET defaults to the language set in the
configuration, which is Microsoft Visual Basic. The source code can
also be loaded from an external file, whose virtual path is set in the Src attribute. The location of the file is resolved using Server.MapPath—that is, starting under the physical root directory of the Web application.
<script language="C#" runat="server" src="somecode.aspx.cs" />
In this case, any other code in the declaration <script> block is ignored. Notice that ASP.NET enforces syntax rules on the <script> tag. The runat attribute is mandatory, and if the block has no content the Src must be specified.
Server-Side <object> Tags
The server-side <object> tag lets you create new objects using a declarative syntax. The <object> tag can take three forms, as shown in the following lines of code, depending on the specified reference type:
<object id="..." runat="server" scope="..." class="..." />
<object id="..." runat="server" scope="..." progid="..." />
<object id="..." runat="server" scope="..." classid="..." />
In the first case, the
object is identified by the name of the class and assembly that
contains it. In the last two cases, the object to create is a COM object
identified by the program identifier (progid) and the 128-bit CLSID, respectively. As one can easily guess, the classid, progid, and class attributes are mutually exclusive. If you use more than one within a single server-side <object> tag, a compile error is generated. Objects declared in this way are loaded when the application is started.
The scope attribute indicates the scope at which the object is declared. The allowable values are defined in Table 1.
Unless otherwise specified, the server-side object is valid only within
the boundaries of the HTTP pipeline that processes the current request.
Other settings that increase the object’s lifetime are application and session.
Table 1. Feasible Scopes for Server-Side <object> Tags
Scope | Description |
---|
pipeline | Default setting, indicates the object is available only within the context of the current HTTP request |
application | Indicates the object is added to the StaticObjects collection of the Application object and is shared among all pages in the application |
session | Indicates the object is added to the StaticObjects collection of the Session object and is shared among all pages in the current session |
Server-Side Includes
An #include
directive inserts the contents of the specified file as-is into the
ASP.NET file that uses it. The directive for file inclusion can be used
in global.asax pages as well as in .aspx pages. The directive must be enclosed in an HTML comment so that it isn’t mistaken for plain text to be output verbatim:
<!-- #include file="filename" -->
<!-- #include virtual="filename" -->
The directive supports two mutually exclusive attributes—file and virtual. If the file
attribute is used, the file name must be a relative path to a file
located in the same directory or in a subdirectory; the included file
cannot be in a directory above the file with the #include directive. With the virtual attribute, the file name can be indicated by using a full virtual path from a virtual directory on the same Web site.
Note
This
technique might sound a bit outdated in the dazzling world of ASP.NET
3.5 with AJAX onboard and awaiting for the even more futuristic features
of Silverlight. However, if used for what it is supposed to
do—inserting the content of a server-side file—it can be helpful even in
ASP.NET 3.5. For example, it can be used to build text-heavy pages that
contain a lot of relatively static text. Keeping this text on a
separate resource might make it easier to manage and maintain. |
Static Properties
If you define static properties in the global.asax file, they will be accessible for reading and writing by all pages in the application:
<script language="C#" runat="server">
public static int Counter = 0;
</script>
The Counter property defined in the preceding code works like an item stored in Application—namely, it is globally visible across pages and sessions. Consider that concurrent access to Counter
is not serialized; on the other hand, you have a strong-typed, direct
global item whose access speed is much faster than retrieving the same
piece of information from a generic collection such as Application.
To access the property from a page, you must use the ASP.global_asax qualifier, shown here:
Response.Write(ASP.global_asax.Counter.ToString());
If you don’t particularly like the ASP.global_asax
prefix, you can alias it as long as you use C#. Add the following code
to a C#-based page (or code-behind class) for which you need to access
the globals:
using Globals = ASP.global_asax;
The preceding statement creates an alias for the ASP.global_asax class (or whatever name your global.asax class has). The alias—Globals in this sample code—can be used throughout your code wherever ASP.global_asax is accepted.
Response.Write(Globals.Counter.ToString());
Important
You can use the global.asax
file to handle any event exposed by the modules called to operate on
the request. Handlers for events exposed by an HTTP module must have a
name that conforms to the following scheme: ModuleName_EventName. The module name to use is defined in the <httpModules> section of the configuration file. |
Tracking Errors and Anomalies
When
an error occurs, displaying a friendly page to the user is only half
the job a good programmer should do. The second half of the work
consists of sending appropriate notifications to the system
administrator—if possible, in real time. A great help is the Error event of the HttpApplication object. Write an Application_Error event handler in your global.asax
file, and the system will call it back whenever an unhandled error
occurs in the application—either in the user code, a component’s code,
or ASP.NET code.
In the Application_Error
event handler, you first obtain specific information about the error
and then implement the tracking policy that best suits your needs—for
example, e-mailing the administrator, writing to the Windows Event Log,
or dumping errors to a text file. The Server.GetLastError method returns an Exception object that represents the unhandled exception you want to track down. URL information is contained in the Request object, and even session or application state is available.
The following code demonstrates how to write an Application_Error event handler in global.asax to report run-time anomalies to the Event Log. An example of this code in action is shown in Figure 2. The code retrieves the last exception and writes out available information to the event log. Note that the ToString method on an exception object returns more information than the Message property. Additional information includes the stack trace.
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.Text" %>
<script language="C#" runat="server">
void Application_Error(object sender, EventArgs e)
{
// Obtain the URL of the request
string url = Request.Path;
// Obtain the Exception object describing the error
Exception error = Server.GetLastError();
// Build the message --> [Error occurred. XXX at url]
StringBuilder text = new StringBuilder("Error occurred. ");
text.Append(error.ToString());
text.Append(" at ");
text.Append(url);
// Write to the Event Log
EventLog log = new EventLog();
log.Source = "Core35 Log";
log.WriteEntry(text.ToString(), EventLogEntryType.Error);
}
</script>
Your code doesn’t necessarily have to create the event source. If the source specified in the Source property does not exist, it will be created before writing to the event log. The WriteEntry
method takes care of that. Windows provides three log files:
Application, Security, and System, which is reserved for device drivers.
The Log property of the EventLog class gets and sets the log file to use. It is Application by default.
Caution
To create new event logs, applications should use the static method CreateEventSource on the EventLog
class. Note, though, that ASP.NET applications can’t create new event
logs because the running account (ASPNET or NETWORK SERVICE) doesn’t
have enough permissions. If you want your ASP.NET application to use a
custom log, create that at setup time. |