Browse DevX
Sign up for e-mail newsletters from DevX


A Low-level Look at ASP.NET Architecture : Page 3

Many developers are familiar only with the high-level .NET frameworks like Web Forms and Web services that sit at the very top level of the ASP.NET hierarchy. This article discusses the lower-level aspects of ASP.NET and explains how requests move from Web Server to the ASP.NET runtime and then through the ASP.NET HTTP pipeline to process requests.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Getting into the .NET Runtime
The actual entry points into the .NET runtime occur through a number of undocumented classes and interfaces. Little is known about these interfaces outside of Microsoft, and Microsoft folks are not eager to talk about the details, as they deem this an implementation detail that has little effect on developers building applications with ASP.NET. If you want to dig into the low level interfaces open up Reflector and point it at the System.Web.Hosting namespace. The entry point to ASP.NET occurs through a managed COM interface called from the ISAPI DLL, which receives an unmanaged pointer to the ISAPI ECB. The ECB has access to the full ISAPI interface to allow retrieving request data and sending responses back to IIS.

Figure 3: Exploring Low-level Interfaces: The figure shows the interface and call signatures for the IISAPIRuntime interface running in Reflector.
The worker processes ASPNET_WP.EXE (IIS5) and W3WP.EXE (IIS6) host the .NET runtime. The ISAPI DLL calls into a small set of unmanaged interfaces via low-level COM that eventually forward calls to an instance subclass of the ISAPIRuntime class. The first entry point to the runtime is the undocumented ISAPIRuntime class that exposes the IISAPIRuntime interface via COM to a caller. These COM interfaces are low level IUnknown-based interfaces intended for internal calls from the ISAPI extension into ASP.NET. Figure 3 shows the interface and call signatures for the IISAPIRuntime interface as shown in Lutz Roeder's excellent .NET Reflector tool. Reflector is an assembly viewer and disassembler that makes it very easy to look at metadata and disassembled code (in IL, C#, and VB). It's a great way to explore the bootstrapping process.

The IISAPIRuntime interface acts as the interface point between the unmanaged code coming from the ISAPI extension (directly in IIS 6 and indirectly via the Named Pipe handler in IIS 5). If you take a look at this class you'll find a ProcessRequest method with a signature like this:

[return: MarshalAs(UnmanagedType.I4)] int ProcessRequest([In] IntPtr ecb, [In, MarshalAs(UnmanagedType.I4)] int useProcessModel);

The ecb parameter is the ISAPI Extension Control Block (ECB) passed as an unmanaged resource to ProcessRequest. The method then takes the ECB and uses it as the base input and output interface used with the Request and Response objects. An ISAPI ECB contains all low level request information including server variables, an input stream for form variables, and an output stream used to write data back to the client. The single ecb reference basically provides access to all of the functionality an ISAPI request has access to. ProcessRequest is the entry and exit point where this resource initially makes contact with managed code.

The ISAPI extension runs requests asynchronously. In this mode the ISAPI extension immediately returns on the calling worker process or IIS thread, but keeps the ECB for the current request alive. The ECB includes a mechanism for letting ISAPI know when the request is complete (via ecb.ServerSupportFunction), which then releases the ECB. This asynchronous processing releases the ISAPI worker thread immediately, and offloads processing to a separate thread managed by ASP.NET.

ASP.NET receives this ecb reference and uses it internally to retrieve information about the current request such as server variables and POST data as well as returning output back to the server. The ecb stays alive until the request finishes or times out in IIS and ASP.NET continues to communicate with it until the request is done. Output is written into the ISAPI output stream (ecb.WriteClient()) and when the request is done, the ISAPI extension is notified of request completion to let it know that the ECB can be freed. This implementation is very efficient as the .NET classes essentially act as a fairly thin wrapper around the high performance, unmanaged ISAPI ECB.

Loading .NET—Somewhat of a Mystery
Let's back up one step here: I skipped over how the .NET runtime gets loaded. Here's where things get a bit fuzzy. I haven't found any documentation on this process and since we're talking about native code there's no easy way to disassemble the ISAPI DLL and figure it out.

My best guess is that the worker process bootstraps the .NET runtime from within the ISAPI extension on the first hit against an ASP.NET-mapped extension. After the runtime loads, the unmanaged code can request an instance of an ISAPIRuntime object for a given virtual path if one doesn't exist yet. Each virtual directory gets its own AppDomain and within that AppDomain the ISAPIRuntime exists from which the bootstrapping process for an individual application starts. Instantiation appears to occur over COM, because the interface methods are exposed as COM-callable methods.

To create the ISAPIRuntime instance the System.Web.Hosting.AppDomainFactory.Create() method is called when the first request for a specific virtual directory is requested. This starts the "Application" bootstrapping process. The call receives parameters for type, module name, and virtual path information for the application used by ASP.NET to create an AppDomain and launch the ASP.NET application for the given virtual directory. This HttpRuntime-derived object is created in a new AppDomain. Each virtual directory or ASP.NET application is hosted in a separate AppDomain and gets loaded only as requests hit that particular ASP.NET Application. The ISAPI extension manages these instances of the HttpRuntime objects and routes inbound requests to the right one based on the virtual path of the request.

Figure 4: ISAPI to ASP.NET: Transferring the ISAPI request into ASP.NET's HTTP pipeline involves a number of undocumented classes and interfaces, and requires several factory method calls. Each Web application/virtual directory runs in its own AppDomain, with the caller holding a proxy reference to an IISAPIRuntime interface that triggers the ASP.NET request processing.
Back in the Runtime
At this point you have an instance of ISAPIRuntime active and callable from the ISAPI extension. Once the runtime is up and running the ISAPI code calls into the ISAPIRuntime.ProcessRequest() method, which is the real entry point into the ASP.NET pipeline. The flow from there is shown in Figure 4.

Remember, ISAPI is multi-threaded so requests will come in on multiple threads through the reference returned by ApplicationDomainFactory.Create(). Listing 1 shows the disassembled code from the IsapiRuntime.ProcessRequest method that receives an ISAPI ecb object and server type as parameters. The method is thread-safe, so multiple ISAPI threads can safely call this single returned object instance simultaneously.

The actual code here is not important—and keep in mind that this is disassembled internal framework code that you'll never deal with directly and that might change in the future. It's meant to demonstrate what's happening behind the scenes. ProcessRequest receives the unmanaged ECB reference and passes it on to the ISAPIWorkerRequest object in charge of creating the Request Context for the current request as shown in Listing 2.

The System.Web.Hosting.ISAPIWorkerRequest class is an abstract subclass of HttpWorkerRequest, whose job it is to create an abstracted view of the input and output that serves as the input for the Web application. Notice another factory method here: CreateWorkerRequest, which receives the type of worker request object to create as its second parameter. There are three different versions: ISAPIWorkerRequestInProc, ISAPIWorkerRequestInProcForIIS6, ISAPIWorkerRequestOutOfProc. This object gets created on each incoming hit and serves as the basis for the Request and Response objects, which will receive their data and streams from the data provided by the WorkerRequest.

The ISAPIRuntime.ProcessRequest() method is the first entry point into managed ASP.NET
The abstract HttpWorkerRequest class is meant to provide a high level abstraction around the low level interfaces regardless of where the data comes from, whether that's a CGI Web server, the Web Browser control, or some custom mechanism you use to feed the data to the HTTP runtime. The key is that ASP.NET can retrieve the information consistently.

In the case of IIS the abstraction centers on an ISAPI ECB block. In the request processing, ISAPIWorkerRequest hangs on to the ISAPI ECB and retrieves data from it as needed. For example, Listing 2 shows how a query string value is retrieved.

ISAPIWorkerRequest implements a high level wrapper method that calls into lower level Core methods, which are responsible for performing the actual access to the unmanaged APIs—or the "service level implementation." The Core methods are implemented in the specific ISAPIWorkerRequest instance subclasses and thus provide the specific implementation for the environment that it's hosted in. This makes for an easily pluggable environment where additional implementation classes can be provided later as newer Web server interfaces or other platforms are targeted by ASP.NET. There's also a helper class called System.Web.UnsafeNativeMethods. Many of these methods operate on the ISAPI ECB structure performing unmanaged calls into the ISAPI extension.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date