Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Global.asax in ASP.NET

0.00/5 (No votes)
20 Jul 2011 1  
The Global.asax file and its role in ASP.NET applications

The Global.asax, also known as the ASP.NET application file, is used to serve application-level and session-level events. Although its structure is pretty simple and it’s easy to have one in the project, many developers don’t know exactly how it works. This post aims at revealing some of the mystery behind this file as well as provides you with some good usage examples.

Let’s first have a look at a sample Global.asax file:

ASP.NET
<%@ Application Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>

<script runat="server">
    public Guid AppId = Guid.NewGuid();

    public String TestMessage;

    public String GetAppDescription(String eventName)
    {
        StringBuilder builder = new StringBuilder();

        builder.AppendFormat("-------------------------------------------{0}", 
                             Environment.NewLine);
        builder.AppendFormat("Event: {0}{1}", eventName, Environment.NewLine);
        builder.AppendFormat("Guid: {0}{1}", AppId, Environment.NewLine);
        builder.AppendFormat("Thread Id: {0}{1}", 
              System.Threading.Thread.CurrentThread.ManagedThreadId, Environment.NewLine);
        builder.AppendFormat("Appdomain: {0}{1}", 
              AppDomain.CurrentDomain.FriendlyName, Environment.NewLine);
        builder.AppendFormat("TestMessage: {0}{1}", TestMessage, Environment.NewLine);
        builder.Append((System.Threading.Thread.CurrentThread.IsThreadPoolThread ? 
                        "Pool Thread" : "No Thread") + Environment.NewLine);

        return builder.ToString();
    }

    void Application_Start(object sender, EventArgs e)
    {
        TestMessage = "not null";

        // Code that runs on application startup
        Trace.WriteLine(GetAppDescription("Application_Start"));
    }

    void Application_End(object sender, EventArgs e)
    {
        //  Code that runs on application shutdown
        Trace.WriteLine(GetAppDescription("Application_End"));
    }

    void Application_Error(object sender, EventArgs e)
    {
        // Code that runs when an unhandled error occurs
        Trace.WriteLine(GetAppDescription("Application_Error"));
    }

    void Application_BeginRequest(object sender, EventArgs e)
    {
        Trace.WriteLine(GetAppDescription("Application_BeginRequest"));
    }

    void Application_EndRequest(object sender, EventArgs e)
    {
        Trace.WriteLine(GetAppDescription("Application_EndRequest"));
    }
</script>

Global.asax is compiled to its own assembly – if you publish the web project from Visual Studio, you can find App_global.asax.dll in the bin directory, otherwise it will go to c:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\<appname>. After decompiling the produced assembly, we will receive something like (methods are collapsed for clarity):

C#
namespace ASP
{
    [CompilerGlobalScope]
    public class global_asax : HttpApplication
    {
        private static bool __initialized;
        public Guid AppId = Guid.NewGuid();
        public String TestMessage;

        protected DefaultProfile Profile
        {
            get
            {
                return (DefaultProfile)base.Context.Profile;
            }
        }
        // events go here
        [DebuggerNonUserCode]
        public global_asax()
        {
            if (!global_asax.__initialized)
            {
                global_asax.__initialized = true;
            }
        }
    }
}

From the above code, we can see that a new class (global_asax) was created in the ASP namespace which inherits from HttpApplication. HttpApplication is an interesting class itself. It implements the ASP.NET request pipeline, invoking HttpModules, page event handlers, etc. A quite common mistake is to think of the HttpApplication (global_asax) as a singleton shared by all the requests. The truth is that each request has its own instance of the HttpApplication class. To be more specific, the ASP.NET runtime keeps two pools of HttpApplication objects. The first is a special pool with HttpApplication objects used for application level events (such as Application_Start, Application_End, and Application_Error), the second pool contains instances used in requests to serve all other types of events. Which class is then responsible for managing those pools? Here is where the HttpApplicationFactory comes into play. There is only one instance of this class per HttpRuntime and besides managing app pools, it is also responsible for compiling Global.asax and ensuring that the Application_Start/End events are called only once.

To prove what I described, let’s use the above Global.asax file in a very simple web application. We will use System.Diagnostics.Trace so some changes to the web.config file are required:

XML
<?xml version="1.0"?>
<configuration>
  <system.diagnostics>
    <trace autoflush="true">
      <listeners>
        <add name="LogFile" 
          type="System.Diagnostics.TextWriterTraceListener" 
          initializeData="webapp.log"/>
      </listeners>
    </trace>
  </system.diagnostics>
  <system.codedom>
    <compilers>
      <compiler language="c#" extension=".cs" compilerOptions="/d:TRACE" 
         type="Microsoft.CSharp.CSharpCodeProvider, System, Version=4.0.0.0, 
               Culture=neutral, PublicKeyToken=b77a5c561934e089" 
         warningLevel="1"/>
    </compilers>
  </system.codedom>
  <system.web>
    <compilation debug="true" targetFramework="4.0"/>
  </system.web>
</configuration>

After the first client request, your webapp.log file should contain something similar to:

-------------------------------------------
Event: Application_Start
Guid: d8ab181d-b1f0-4733-83c1-04e6af5e6038
Thread Id: 1
Appdomain: /LM/W3SVC/3/ROOT-1-129556455331183204
TestMessage: not null
No Thread

-------------------------------------------
Event: Application_BeginRequest
Guid: 22089cce-f79a-4123-b455-e7f9e1091dd5
Thread Id: 5
Appdomain: /LM/W3SVC/3/ROOT-1-129556455331183204
TestMessage:
Pool Thread

-------------------------------------------
Event: Page_Load
Guid: 22089cce-f79a-4123-b455-e7f9e1091dd5
Thread Id: 5
Appdomain: /LM/W3SVC/3/ROOT-1-129556455331183204
TestMessage:
Pool Thread

-------------------------------------------
Event: Application_EndRequest
Guid: 22089cce-f79a-4123-b455-e7f9e1091dd5
Thread Id: 5
Appdomain: /LM/W3SVC/3/ROOT-1-129556455331183204
TestMessage:
Pool Thread

The different GUIDs for Application_Start and Application_…Request events prove that there were two global_asax instances involved in processing the request. The first one is the special one and will be stored in the special pool (probably reused for Application_End/Application_Error events), the second one is a normal instance that will be reused in further requests. Now look at the TestMessage property value – it was set only for the HttpApplication instance that was created as the first one. That assures us that the Application_Start is called only once in the web application lifetime (and not for every newly created HttpApplication instance).

So what’s the correct way of using global.asax? First, remember:

  • It’s not a singleton.
  • Application_Start/End events are called only once.
  • There is one instance of global_asax per request and additionally there are instances for special events.

Second, try to apply those guidelines to your Global.asax files:

  • All private fields should be initialized in the constructor.
  • Static fields may be initialized either in the Application_Start or a static constructor.
  • Don’t use lock blocks – your request possesses this instance of the global_asax.

You can stop reading here if what I wrote is satisfactory for you. If you still have some doubts and would like to dig deeper, keep on reading :) – I will show you how to look at the ASP.NET internals with the SOS debugger extension. Basically, you start with attaching to the w3wp.exe process that your application is running in. Then load sos (replace clr with mscorwks if it’s .NET 2.0):

.loadby sos clr

Then, let’s find the HttpApplicationFactory instance (highlighted):

0:024> !DumpHeap -type System.Web.HttpApplicationFactory
------------------------------
Heap 0
         Address               MT     Size
total 0 objects
------------------------------
Heap 1
         Address               MT     Size
000000013ff40e78 000007fee65c1900      136
total 0 objects
------------------------------
total 0 objects
Statistics:
              MT    Count    TotalSize Class Name
000007fee65c1900        1          136 System.Web.HttpApplicationFactory
Total 1 objects

Then dump its content – I highlighted the special and normal application pools (which are actually of type System.Collections.Stack):

0:024> !do 000000013ff40e78
Name:        System.Web.HttpApplicationFactory
MethodTable: 000007fee65c1900
EEClass:     000007fee63147a8
Size:        136(0x88) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_64\System.Web\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Web.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fee8b1d6e8  4000bf3       78       System.Boolean  1 instance                1 _inited
000007fee8b169d0  4000bf4        8        System.String  0 instance 000000013ff41070 _appFilename
000007fee8b1f8a0  4000bf5       10 ...tions.ICollection  0 instance 0000000000000000 _fileDependencies
000007fee8b1d6e8  4000bf6       79       System.Boolean  1 instance                1 _appOnStartCalled
000007fee8b1d6e8  4000bf7       7a       System.Boolean  1 instance                0 _appOnEndCalled
000007fee65c1988  4000bf8       18 ...pApplicationState  0 instance 000000013ff410c8 _state
000007fee8b18358  4000bf9       20          System.Type  0 instance 000000013ff40e48 _theApplicationType
000007fee8b2d360  4000bfa       28 ...Collections.Stack  0 instance 000000013ff40f00 _freeList
000007fee8b1c8b8  4000bfb       60         System.Int32  1 instance                1 _numFreeAppInstances
000007fee8b1c8b8  4000bfc       64         System.Int32  1 instance                1 _minFreeAppInstances
000007fee8b2d360  4000bfd       30 ...Collections.Stack  0 instance 000000013ff40f98 _specialFreeList
000007fee8b1c8b8  4000bfe       68         System.Int32  1 instance                1 _numFreeSpecialAppInstances
... continued ...

To find out which HttpApplication instances are stored in the particular pool, you need to dump the pool and then its internal array content:

0:024> !do 000000013ff40f00
Name:        System.Collections.Stack
MethodTable: 000007fee8b2d360
EEClass:     000007fee87561c0
Size:        40(0x28) bytes
File:        C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fee8b1aed8  4000b9f        8      System.Object[]  0 instance 000000013ff40f28 _array
000007fee8b1c8b8  4000ba0       18         System.Int32  1 instance                1 _size
000007fee8b1c8b8  4000ba1       1c         System.Int32  1 instance                1 _version
000007fee8b15b28  4000ba2       10        System.Object  0 instance 0000000000000000 _syncRoot
0:024> !da 000000013ff40f28
Name:        System.Object[]
MethodTable: 000007fee8b1aed8
EEClass:     000007fee869fdb8
Size:        112(0x70) bytes
Array:       Rank 1, Number of elements 10, Type CLASS
Element Methodtable: 000007fee8b15b28
[0] 000000013ff76bc8
[1] null
[2] null
[3] null
[4] null
[5] null
[6] null
[7] null
[8] null
[9] null

To check that this instance is the one that we got in the log file, we need to look through its properties and compare, for example, the GUID value:

0:024> !do 000000013ff76bc8
Name:        ASP.global_asax
MethodTable: 000007ff00147a80
EEClass:     000007ff001b25f0
Size:        240(0xf0) bytes
File:        C:\Windows\Microsoft.NET\Framework64\v4.0.30319\
             Temporary ASP.NET Files\root\8fbb8387\1608fccf\
             assembly\dl3\7d155608\c7473357_d746cc01\App_global.asax.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
000007fee65c1988  4000b84        8 ...pApplicationState  0 instance 000000013ff410c8 _state
... some fields here ...
000007fee8b38738  4000002       d8          System.Guid  1 instance 000000013ff76ca0 AppId
000007fee8b169d0  4000003       d0        System.String  0 instance 0000000000000000 TestMessage
000007fee8b1d6e8  4000001       40       System.Boolean  1   static                1 __initialized
0:024> dd 000000013ff76ca0
00000001`3ff76ca0  22089cce 4123f79a f9e755b4 d51d09e1
         // which is 22089cce-f79a-4123-b455-e7f9e1091dd5
00000001`3ff76cb0  00000000 00000000 e8b21c98 000007fe
00000001`3ff76cc0  3ff76d10 00000001 00000000 00000000
00000001`3ff76cd0  00000000 00000000 00000000 00000000
00000001`3ff76ce0  00000000 00000000 00000000 00000000
00000001`3ff76cf0  00000001 00000000 00000002 3f3851ec
00000001`3ff76d00  00000001 00000000 00000000 00000000
00000001`3ff76d10  e8b21f18 000007fe 00000003 00000000

You may perform the same steps to examine the special application pool.

I hope that this post helped you better understand the Global.asax file and its role in ASP.NET applications. A simple web project is available for download here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here