- Day 3: Using Web Forms
- Performing Basic State Management in Web Applications
- Using Cookies
- Using Hidden Fields and Query Strings
- Working with the Session Object
- Working with the Application Object
- Setting Up Global Objects with the global.asax File
- Configuring the Application
Setting Up Global Objects with the global.asax File
ASP.NET uses a special file, called global.asax, to establish any global objects that your Web application uses. The .asax extension denotes an application file rather than .aspx for a page file.
Each ASP.NET application can contain at most one global.asax file. The file is compiled on the first page hit to your Web application. ASP.NET is also configured so that any attempts to browse to the global.asax page directly are rejected.
Listing 3.8 shows a global.asax file that you can use to make a more complete hit counter.
Listing 3.8 global.asax: Event Handlers for the Application and Session Objects
1: <%@ language="VB" %> 2: <script runat="server"> 3: Sub Application_Start(Sender As Object, e As EventArgs) 4: 5: Application("Hits") = 0 6: Application("Sessions") = 0 7: Application("TerminatedSessions") = 0 8: 9: End Sub 10: 11: `The BeginRequest event is fired for every hit 12: `to every page in the site. 13: Sub Application_BeginRequest(Sender As Object, e As EventArgs) 14: 15: Application.Lock() 16: Application("Hits") = Application("Hits") + 1 17: Application.UnLock() 18: 19: End Sub 20: 21: Sub Session_Start(Sender As Object, e As EventArgs) 22: 23: Application.Lock() 24: Application("Sessions") = Application("Sessions") + 1 25: Application.UnLock() 26: 27: End Sub 28: 29: Sub Session_End(Sender As Object, e As EventArgs) 30: 31: Application.Lock() 32: Application("TerminatedSessions") = _ 33: Application("TerminatedSessions") + 1 34: Application.UnLock() 35: 36: End Sub 37: 38: Sub Application_End(Sender As Object, e As EventArgs) 39: 40: `Write out our statistics to a log file 41: `...code omitted... 42: 43: End Sub 44: </script>
ANALYSIS The global.asax file in Listing 3.8 contains event handlers for the Session and Application objects. Each event handler has the same signature as the Page_Load event handler.
The code in Listing 3.8 handles three Application object-related events: Start (Lines 3-9), End (Lines 38-43), and BeginRequest (Lines 13-19). Start and End are called when the Web application starts and ends, respectively. BeginRequest is called for every page hit that the site receives. Listing 3.8 updates the total number of hits in this event.
The Session Start (Lines 21-27)and End (Lines 29-36)events are handled in the middle of the listing. These two events count how many different Web users have accessed the site.
You can write a simple page to utilize the statistics that Listing 3.8 tracks. Listing 3.9 shows a page that writes out the results of the hit-counting code in the global.asax file. Figure 3.5 shows the statistics page after a few hits.
Figure 3.5 The Statistics page after some traffic.
Listing 3.9 Statistics.aspx: The Results of the Tracking in the global.asax File
<%@ page language="VB" %> <html> <body> <h2> Statistics for the Test Web Application </h2> Total hits: <% Response.Write(Application("Hits").ToString()) %> <br> Total sessions: <% Response.Write(Application("Sessions")) %> <br> Expired sessions: <% Response.Write(Application("TerminatedSessions")) %> <br> </body> </html>
If the global.asax file is modified, the Web application is restarted on the next page hit and the global.asax file is recompiled.
Adding Objects to the global.asax File
To use global objects in your ASP.NET application, add the <object> tag in the global.asax file for each one. The <object> tag has an optional attribute called scope, which determines if the added object will be created on-the-fly, associated with the Application object, or associated with the Session object.
To explore the <object> tag, let's create a simple class that stores and retrieves strings. The sample is going to associate an object of this class with the Application object in the global.asax file, so the class must be thread-safe. The term thread-safe means that many client threads can access the class at the same time without any data corruption. Because ASP.NET uses one thread per page, ensuring that the class is thread-safe is critical if multiple users browse the site at the same time.
What's a thread? To answer, let's review processes first. All Windows applications are processes that run on your computer. Processes contain their own code and memory space, and can interact with computer peripherals, such as the screen or the network card. ASP.NET runs as a process, and it executes your code, of course.
Each process contains one or more threads. A thread is like a process or an individual program, because it also executes a certain set of code. However, a thread is a "lightweight" version of a process. Threads live inside processes, and use a process's memory. The Windows operating system gives each thread a small amount of time to execute and quickly switches between threads so that it seems like more than one thread is executing at the same time. For all practical intents, the threads are running at the same time.
Because threads use their parent process's memory, they can potentially change the same object (in memory) at the same time. For two threads, A and B, thread A might add 10 to the counter object. Thread B might subtract 10 from the counter. If the two threads are switched on and off by the operating system in an "unlucky" way, the counter object could contain a scrambled result.
When each ASP.NET page is being processed, it gets its own thread. If more than one user accesses the Web site at the same time, many threads will appear even if both users are accessing the same ASP.NET page.
To prevent threads (and ASP.NET pages) from interfering with each other when accessing the same object, use the technique in the example that follows.
To make the class thread-safe, use the Synchronized method of the base collection class, Hashtable. This class is shown in Listing 3.10.
Listing 3.10 MyClass.vb: Implementing a Class to Store and Retrieve Strings in a Thread-Safe Way
Imports System Imports System.Collections Namespace TestApplication public class MyClass1 private m_col As Hashtable `m_colSync will be a thread-safe container for m_col private m_colSync As Hashtable Public Sub New() m_col = new Hashtable() m_colSync = Hashtable.Synchronized(m_col) End Sub Public Sub AddItem(Name As String, Value As String) m_colSync(Name) = Value End Sub Public Function GetItem(Name As String) As String return m_colSync(Name) End Function End Class End Namespace `note: use "vbc /out:bin\myclass.dll /t:library myclass.cs /r:system.dll" ` to compile with the command line utility
Note that the DLL file is placed in the bin directory off the project directory. The next step is to add this object to the global.asax file with the <object> tag. A short global.asax file that does this follows:
<object id="MyStringCollection" runat="server" class="TestApplication.MyClass1" scope="Application" />
The id attribute tells ASP.NET what to call our object when it's used later. The class attribute can be used to specify COM objects in addition to .NET components, but by using their ProgID.
If the listing omitted the scope attribute, a new object is created on-the-fly for every page that uses the StringCollection object.
Let's write a sample page that uses StringCollection. Listing 3.11 shows just such a page.
Listing 3.11 UseObject.aspx: Using the Global Object
<%@ language="VB" %> <script runat="server"> Sub Page_Load(Sender As Object, e As EventArgs) MyStringCollection.AddItem("FirstUser", "Joe Smith") End Sub </script> <html> <body> The name of the first user is <% Response.Write(MyStringCollection.GetItem("FirstUser")) %> </body> </html>
Putting Code Behind the global.asax File
If you use Visual Studio. NET to create your Web project, it will use the code behind feature of ASP.NET for global.asax. The code behind file that is generated is named global.asax.vb, when using the Visual Basic compiler. To use code behind in global.asax manually, use the Application directive instead of the Page directive, like this:
<@ Application Inherits="MyApplication.GlobalClass" %>
Listing 3.12 shows a code behind example for the global.asax file.
Listing 3.12 GlobalClass.vb: Implementing the Code for the global.asax File
Namespace MyApplication Imports System Imports System.Web Imports System.Web.SessionState Public class GlobalClass Inherits System.Web.HttpApplication protected Sub Session_Start(Sender As Object, e As EventArgs) Response.Write("Session started <br>") End Sub End Class `note: use "vbc /out:bin\globalclass.dll /t:library globalclass.cs /r:system.dll" ` to compile with the command line utility End Namespace
You can mix code behind and the <object> tag in the global.asax file.