In this article I'm going to describe the bridge between managed and unmanaged worlds and how the two collaborate to set up the processing environment needed by ASP.NET requests to be processed.
In the previous as well as first article of this series I've introduced the first steps performed by a generic web request once accepted by the web server, and what route it takes when it is identified as an ASP.NET
resource request. You've seen how different releases of IIS
deal with incoming ASP.NET
-related requests until they finally dispatch them to an unmanaged Win32
component called aspnet_isapi.dll
, which acts as a bridge between the web server and the managed ASP.NET
infrastructure.
In this article I'm going to resume the discussion exactly where I left it and start delving into the managed part of the processing environment I've presented in the previous article as those black boxes labeled ASP.NET Http Runtime Environment
.
In the previous article I've explained how IIS 5
and IIS 6
manage ASP.NET
requests. Regardless of how they deal with worker processes generation, management and recycling, all the information pertaining to the request is in the end forwarded to the aspnet_isapi.dll
extension.
This is the bridge between the unmanaged and managed world and is the least documented part of all the ASP.NET
architecture.
As for the lack of documentation available on most parts of this topic, what I'm going to explain may not be completely correct, especially for what concerns the unmanaged parts of the infrastructure. However, my considerations on the undocumented topics are based on some tools which helped a lot in understanding and making sense of the inner workings of the framework:
Going back to the bridge between the unmanaged and managed worlds, once the CLR
has been loaded - either by the ISAPI
Extension in case of IIS 6
process model or by the worker process in case of IIS 5
process model - the black magic occurs through a bunch of unmanaged COM
interfaces calls that the ASP.NET ISAPI
component makes to a couple of managed classes contained in the System.Web.Hosting
namespace, the AppManagerAppDomainFactory
class and the ISAPIRuntime
class, which expose some methods via COM-callable interfaces.
Before delving into the technical details of the interactions occurring between these classes let's see on the surface what actually happens for a request to be processed. I have introduced the previous two classes because the entry points in the processing of a request can be roughly grouped into two categories:
AppDomain
, in case one doesn't exist yet, to represent the application to which the request is targeted - this is accomplished by the AppManagerAppDomain
class. ISAPIRuntime
class. Though the two categories are equally important, the first consists of interactions which are supposed to happen without the interference of the developer and which concern mostly the hosting environment of the application, while the second is the most configurable part of the infrastructure, which I will delve into completely in the following article of this series.
Under another point of view, the first category comprises operations performed only once during the lifecycle of an application, that is, at startup, while the second consists of interactions occurring at each request targeting that specific application.
As seen in the previous article, an ASP.NET
application is reserved and wrapped into an entity called Application Domain, aka AppDomain
, which turns out to be represented by a class of the ASP.NET
architecture, the AppDomain
class. When a request to a particular application arrives, an AppDomain
has to be setup, if it doesn't exist. This usually happens if the incoming request is the first one targeting that particular application, or if for some reasons the corresponding AppDomain
has been shut down, which may happen for several reasons that I will talk about later. Note that only one AppDomain
exists for a single ASP.NET
application, which is itself mapped one-to-one with an IIS application - either created over a physical/virtual directory. So how does an instance of this class get created?
Using Reflector it's possible to realize that the AppDomain
class has a private constructor which throws a NotSupportedException exception. Therefore this is obviously not the way to go. The entry point for the instantiation of an AppDomain
is in fact the AppManagerAppDomainFactory.Create
method. To find it out it's necessary to use a profiler which keeps track of the call stack generated when instantiating the object. Figure 1 displays a screenshot of JetBrains dotTrace Profiler, which shows the call stack generated to instantiate the AppDomain
of a web application hosted on IIS. A lot of classes participate in the process, but it's something a developer will probably never have to put his hands on.
Note that the AppManagerAppDomainFactory
class is instantiated only once during and by the CLR initialization process. As its name suggests, it's used as the entry point for creating AppDomains
as well as other critical objects.
Figure 2 shows the same call stack as the previous image, this time captured from the output of the Call Tree view of Microsoft CLR Profiler. This provides some more information about the instantiation of the AppDomain
. Actually, the highlighted line shows that there are two instances of the AppDomain
class created by the principal thread.
So, how does this additional instance get created, and why? Unluckily it's not an easy question. The call stack shown in the previous images represents the steps followed for instantiating the AppDomain
which corresponds to the actual application being executed, so this additional instance looks like a helper object used for some purpose which is difficult to understand - as far as I can guess, it's used to host all the objects which don't belong to a specific application, and therefore are hosted in an isolated AppDomain
, like the AppManagerAppDomainFactory
class. As for the AppManagerAppDomainFactory
class, this helper AppDomain
is instantiated only once during and by the CLR
initialization, and very very early during this stage, as shown in Figure 3.
Figure 3 represents a screenshot of Red Gate ANTS Profiler. It shows that the additional AppDomain
instance is created very early and during the initialization of the CLR. It is obviously created earlier than the AppManagerAppDomainFactory
class, since it is probably its container. The ID of the singleton AppManagerAppDomainFactory
instance, in fact, in the profiling session of the previous figure turns out to be 52.
Another way to see the instantiation process of the two AppDomains
- which I present for completeness - is the Allocation Graph view provided by Microsoft CLR
Profiler. It creates a graphical flow representation of the classes taking place in the process, as shown in Figure 4.
This represents a compressed view which cuts out all the classes between the <root> element - the CLR
- and the Thread
class. However, it shows clearly the two routes followed when creating the two AppDomain
instances.
Another information about AppDomain
instances that can be obtained from the previous figures is that each instance occupies exactly 100 bytes in memory. Not that it matters much, but one might think that an AppDomain
is a huge class since it has to host an entire application. Actually, it provides a lot of services but doesn't store a lot of data.
By now, the helper AppDomain
instance has been created, as well as an instance of the AppManagerAppDomainFactory
class, whose Create
method is the entry point of the call stack shown in figures 1 and 2.
This method is exposed and called via COM
, thus unmanaged code. The AppManagerAppDomainFactory
class implements the IAppManagerAppDomainFactory
interface, whose structure is represented in Figure 5.
The interface is decorated with the ComImport
and InterfaceType
attribute, which bind the type to an unmanaged interface type. When this method is called it produces the call stack of figures 1 and 2 which in the end triggers the creation of an instance of the AppDomain
class, the one used to host the web application targeted by the request.
Once the AppDomain
is up and running all that's left to do - and it's much more than what's been done so far - is processing the request. This is probably the most interesting part of the ASP.NET
architecture, but before approaching the fun part I wanted to introduce some core topics.
In this article I've introduced some very low level topics about the ASP.NET
infrastructure, those concerning the interface between the unmanaged world - represented by the ASP.NET ISAPI
extension - and the managed world, represented by the AppDomain
hosting the web application. I've presented the mechanisms by which an AppDomain
gets instantiated and which classes take part in the process. In the next article I will discuss of managed code only by presenting the HTTP Pipeline
, which is in my opinion the most attractive chapter of the ASP.NET
architecture, and what actually makes ASP.NET
different from all the other web development frameworks out there. I hope you enjoyed reading this article as much as I did writing it. My advice is to fire up some profiler and try out this stuff by yourself, which is the best way to fully understand how it all works.