global.asax file in Asp.Net Applicaition
The Global.asax, also known as the ASP.NET application file, is used to serve application-level andsession-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:
<%@ 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\
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 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.
- All
private
fields should be initialized in the constructor. Static
fields may be initialized either in theApplication_Start
or astatic
constructor.- Don’t use
lock
blocks – your request possesses this instance of theglobal_asax
.
.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.
Comments
Post a Comment