The Global.asax, also known as the ASP.NET application file, is used to handle 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 providing you with some good usage examples.
Let’s first have a look at a sample Global.asax file:
<%@ 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):
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 ASP namespace which inherits from the 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 ASP.NET runtime keeps two pools of HttpApplication objects. First is a special pool with HttpApplication objects used for application level events (such as Application_Start, Application_End and Application_Error(Application_Error is served by normal HttpApplication instance – thanks Denis for spotting the mistake here!)), second pool contains instances used in requests to serve all other types of events. Which class is then responsible for managing those pools? Here the HttpApplicationFactory comes into play. There is only one instance of this class per HttpRuntime and beside managing app pools it is also responsible for compiling Global.asax and ensuring that 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 the System.Diagnostics.Trace so some changes to the web.config file are required:
<?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 cotain 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 event), the second one is a normal instance that will be reused in any 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? Firstly, 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
Secondly, 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 static constructor.
- Don’t use lock blocks – your request possesses this instance of the glboal_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 the ASP.NET applications. The simple web project is available for download here.
>ASP.NET runtime keeps two pools of HttpApplication objects. First is a special pool with HttpApplication objects used for application level events (such as Application_Start, Application_End and Application_Error)
sure about Application_Error?
public class MvcApplication : System.Web.HttpApplication
{
private int _value;
protected void Application_Start()
{
_value = 123;
protected void Application_Error()
{
int result = _value;
result is always 0
Hi Denis,
Thanks for your comment. You are completely right – Application_Error is served by one of the normal (not special) HttpApplication instances. I updated the post with adequate remark.
Sebastian
Great artical about the global.asax. I was always wondering why I’m not able to persist things in static properties between sessions or requests. And now you’ve explained to me in the most simplest way. Thanks. Ondrej
Brilliant, thanks.
Nice post! Many thanks
Thanks for clarifying! Very useful. Just wondering for this part:
“To be more specific ASP.NET runtime keeps two pools of HttpApplication objects. First is a special pool with HttpApplication objects used for application level events (such as Application_Start, Application_End, second pool contains instances used in requests to serve all other types of events.”
You menitoned HttpApplication objectS. Should it be just one HttpApplication object there, since only for first instance Application_Start is called?
Going further, what happens with that object, can it be also used for processing requests? Or, if my application creates lets say four HttpApplication instances, by default, only three of them will be used for processing requsts? Thanks
Hi Ilija,
Those are very good questions. Unfortunately I don’t have a definitive answer for the first one. Logically there should be one instance of such an object – the only situation that comes to my mind when there might exist more instances is when application is being reloaded, but I haven’t tested it yet.
The special application object is not used in a normal request processing – they are stored in different pools. And yes, if your application creates four instances of HttpApplication, three of them will be used for processing requests.