ASP.NET provides an event-based programming model that simplifies Web programming. In my previous article "ASP.NET: Revolution, not evolution," I demonstrated a simple ASP.NET Greeter application that handled a single button click event. In that application, event handlers were attached to the button Web server control programmatically.
In this article, I’ll expand on that application to demonstrate how to attach event handlers declaratively for control-level and page-level events. I’ll also discuss the overall structure of an ASP.NET application and demonstrate the events you can handle throughout its life cycle.
Declaratively attaching event handlers
Let’s take a look at the revised Greeter page. Recall that the page is split into the UI, in Greeter.aspx shown in Listing A, and the logic, in the GreeterLogic class in Greeter.aspx.cs, shown in Listing B. The UI and logic are joined through inheritance specified in the @ Page directive at the top of Greeter.aspx. Rather than attach the button click handler manually via a delegate in GreeterLogic.OnInit (I’ve left this code in OnInit, commented out at Note 1), the event handler is attached declaratively in Greeter.aspx. The <asp:Button> Web server control, declared inside the <form> tag, contains the attribute OnClick="Button_Click". When the ASP.NET run time processes Greeter.aspx, it generates a .NET class that automatically does what I previously did manually: It creates a delegate for the Button_Click method in the base class GreeterLogic and adds it to the button click event.
A bit less work is a good thing, right? Maybe, but there’s another consideration—one to watch out for. Because the .NET class is created dynamically on the first access to the page, errors, like a typo in the event handler name, won’t be discovered until run time.
The base class of GreeterLogic, System.Web.UI.Page, exposes several page-level events. These events provide notifications during the page life cycle, such as when the page is initialized, loaded, and unloaded. GreeterLogic includes handlers for each of these events. These event handlers could have been attached manually. In fact, GreeterLogic.OnInit contains the code to do just this, commented out, at Note 2. Instead, the page-level event handlers are attached automatically though the AutoEventWireup attribute in the @ Page directive at the top of Greeter.aspx. With AutoEventWireup set to true, event handlers within the page are automatically attached to page events. This is a bit less work for you, but again with considerations. You can’t choose your own names. The event handlers must have predictable names of the form Page_Event. If you misname an event handler, it won’t be attached to an event, it won’t be called, and no error or warning will be generated. If you opt for AutoEventWireup and your event handler isn’t being called, the first thing to check is the event handler name.
Calls to each page-level event handler in GreeterLogic are logged to a file using the utility Log class in Log.cs, shown in Listing C. The LogCall method logs the call by examining the run-time call stack through the System.Diagnostics.StackTrace class. See the inline documentation in Log.cs for the details. The Page_Load event handler also writes an entry to the log to note the state of the page’s IsPostBack property. The Page.IsPostBack property enables you to determine whether the page is being loaded in response to an initial request or in response to a post-back when a form within the page is submitted. We’ll look at the log file later to see the complete sequence of events.
An ASP.NET application, as in classic ASP, is composed of the files in a physical root directory and the files in all of the subdirectories underneath. Typically, an IIS virtual directory references the root directory, although the root directory may be directly underneath the IIS home directory. In addition to .aspx pages, the root directory may contain two optional files: Web.config and Global.asax. ASP.NET stores application configuration settings as XML inside Web.config. Typical configuration settings include the authentication method to be used for the application and the session timeout value.
Global.asax, shown in Listing D, contains code to handle application-level events, much like the Global.asa file in classic ASP. The <script> tag within Global.asax contains the application-level event handlers. The first group of event handlers manages application start and end events. A start event is raised the first time any page within the application is requested. The application end event is raised when the application shuts down.
The next group of event handlers manages session start and end events. A new session is created within the application each time a unique browser instance accesses an application page. The session exists until it either times out or is closed explicitly by the application. The session start and end events delineate the life of the session.
The ASP.NET run time processes Global.asax similar to the way it processes an .aspx file. A .NET type named Global_asax is generated from Global.asax. Global_asax is derived from System.Web.HttpApplication and placed in the ASP namespace. The library assembly containing Global_asax is placed in a temporary directory for ASP.NET files, for example:
C:\WINNT\Microsoft.NET\Framework\v1.0.3328\Temporary ASP.NET Files\aspnetevents\118b3fbe\9c41abcd
HttpApplication exposes several application-level events. These events provide additional notifications during the application life cycle, such as when each request begins and ends and when authentication and authorization occurs. The handlers for these events have names of the form Application_Event. Besides proper naming, no overt action is needed to attach these event handlers. The @ Application directive at the top of Global.asax does not support an AutoEventWireup attribute.
The application-level event handlers could have been placed in a separate C# file, as the page-level event handlers are, and joined to the generated Global_asax class through inheritance by adding an Inherits attribute to the @ Application directive. Since there isn’t anything else inGlobal.asax, I chose to put the event handlers there. However, because the ASP.NET run time doesn’t process Global.asax until the first application page is requested, a syntax error within the <script> tag code will be detected later rather than sooner.
Like the page-level event handlers in GreeterLogic, calls to each application-level event handler in Global.asax are logged to a file using Log.LogCall. The @ Imports directive at the top of Global.asax imports TechRepublic namespace, containing the Log class, just like a using TechRepublic; directive would in C#.
Start-to-end event sequence
With the Log and Greeter library assemblies compiled and placed in the application’s bin subdirectory, we’re ready to run the application by opening a browser and requesting Greeter.aspx (Figure A). After entering your name and clicking the Get Greeting button, open and save Global.asax. This updates the file modification time and prompts the ASP.NET run time to close the session and application. The log file, shown in Listing E, now contains the complete sequence of events. Note that not all application and page event handlers are called. For example, if all goes well, neither the Application_Error nor the Page_Error event handler will be called. The log shows two page requests: one for initial access and one for post-back. You can see the page’s post-back state underneath the Page_Load entries. The second page request includes a call to GreeterLogic.Button_Click.
|Initial state of Greeter screen|
If you’re really curious about the internals of ASP.NET, you can change the private constants m_showTrace in Global.asax and Greeter.cs to true, recompile, and rerun the application. This log file, shown in Listing F, shows the call stack trace at each event handler call. Many of the ASP.NET run-time classes and methods you’ll see are private or internal to their assemblies, and you won’t find them documented in MSDN. However, I found it to be interesting insight into the ASP.NET run time.
Is ASP.NET a step in the right direction?
ASP.NET represents a complete rethinking. Are these changes an improvement for you? What do you like most about ASP.NET? Send us an e-mail with your thoughts and experiences or post a comment below.