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 the global.asax file 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 .NET 2005 doesn't list global.asax in the items that you can add to the project if there already is one.
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 "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="ProAspNet.Global" %>
We'll learn more about the syntax of global.asax in the next section. 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 Server (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 12-1.
Figure 12-1: Direct access to forbidden resources, such as *.asax files, results in a server error.
Tip | You can duplicate the line of code listed above 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, which is accessible from 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.
Four elements determine the syntax of the global.asax file: 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.
The global.asax file supports three directives: @Application, @Import, and @Assembly. The @Import and @Assembly directives work as we have seen in Chapter 3. 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 only serves 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.
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 .NET. 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="/books/2/370/1/html/2/global.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.
The server-side <object> tag lets you create new objects using a declarative syntax. As shown in the following lines of code, the <object> tag can take three forms, depending on the specified reference type:
<object runat="server" scope="..." /> <object runat="server" scope="..." prog /> <object runat="server" scope="..." class />
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 12-5. 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.
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 |
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 sub-directory; 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.
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. |
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, as we have already seen in Chapter 5. 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 12-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 = "ProAspNet20 Log"; log.WriteEntry(text.ToString(), EventLogEntryType.Error); } </script>
Figure 12-2: The Event Viewer tracks an error on an ASP.NET application.
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. System is reserved for device drivers. The Log property of the EventLog class gets and sets the log file to use, which 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. |