Most developers are familiar with the high level abstractions that ASP.NET provides for them such as Web Forms and Web Services. However, underneath these abstractions sits a very interesting and advanced architecture. Knowing the under works of this architecture not only elevates the ASP.NET developer into the advanced zone, but also helps him/her write better designed applications and solve advanced problems that occur way below the high level ASP.NET use.
This article inspects how ASP.NET (and IIS) handles requests. On the way, I will discuss in detail what happens inside the ASP.NET architecture from the moment a request leaves the browser until it goes all the way through the ASP.NET Runtime.
|Info: After the request finishes the ASP.NET Runtime, the ASP.NET page model starts executing. This is beyond the scope of this article, but it will be the topic of my next article.
A request is issued via the browser
The process begins once a user requests an ASP.NET resource via the browser. For example, let us say that a user requested the following URL: http://www.myserver.com/myapplication/mypage.aspx. The request will reach “myserver”, which has Windows Server 2003 and IIS 6.0 installed.
Kernel mode http.sys driver picks up the request
Once the request reaches IIS, it is detected by the http.sys kernel mode driver. Before going further, let us examine what the http.sys kernel mode driver is and what is it that it does.
Generally speaking, Windows provides two modes: User mode and Kernel mode. User applications run in User mode, and Operating System code runs in Kernel mode. If a user application needs to work directly with the hardware, that specific action is done by a Kernel mode process. The obvious purpose of these modes is to protect the Operating System components from being damaged by user applications. So now that we know what User mode and Kernel mode are, what is the role of the http.sys kernel mode driver?
When you create a new IIS website, IIS registers the site with http.sys, which then receives all HTTP requests for any web application within the site. The http.sys functions as a forwarder, directing the HTTP requests to the User mode process that runs the web application. In this case, the User mode process is the worker process running the application pool which the web application runs under. The http.sys implements a queuing mechanism by creating as many queues as there are application pools in IIS.
Following up with our example, once the request reaches “myserver”, http.sys picks up the request. Let us say that “myapplication” is configured to run under the application pool “myapplicationpool”; in this case, http.sys inspects the request queue of “myapplicationpool” and forwards the request to the worker process under which “myapplicationpool” is running under.
The request is forwarded to the application pool
OK, so now, the request is forwarded to the application pool as explained in the previous section. Each application pool is managed by an instance of the worker process “w3wp.exe”. The “w3wp.exe” runs, by default, under the “NetworkService” account. This can be changed as follows: right click on the application pool hosting your application--Properties--Identity tab. Recall that the application pool is run by the worker – the “w3wp.exe”. So now, the worker process takes over.
The worker process loads the ASP.NET ISAPI
The worker process “w3wp.exe” looks up the URL of the request in order to load the correct ISAPI extension. The requested resource in the URL is “mypage.aspx”. So, what happens next? A full discussion of ISAPI extensions (and filters) is beyond the scope of this article, but in short, ISAPI extensions are the IIS way to handle requests for different resources. Once ASP.NET is installed, it installs its own ISAPI extension (aspnet_isapi.dll) and adds the mapping into IIS. IIS maps various extensions to its ISAPI extensions. You can see the mappings in IIS as follows: right click on the website-Properties-Home Directory tab-Configuration button-Mappings tab. The figure below shows the mappings:
As you can see, the “.aspx” extension is mapped to the aspnet_isapi.dll extension. So now, the worker process passes the request to the aspnet_isapi extension. The aspnet_isapi extension in turn loads the HTTP Runtime and the processing of the request starts.
Before inspecting what happens inside the HTTP Runtime, let us examine some details about how the worker process loads the web application. The worker process loads the web application assembly, allocating one application domain for the application. When the worker process starts a web application (in its application domain), the web application inherits the identity of the process (NetworkService, by default) if impersonation is disabled. However, if impersonation is enabled, each web application runs under the account that is authenticated by IIS, or the user account that is configured in the web.config.
- If only anonymous access is enabled by IIS, the identity that is passed to the web application will be [machine]\IUSR_[machine].
- If only integrated Windows authentication is enabled in IIS, the identity that is passed to the web application will be the authenticated Windows user.
- If both integrated Windows authentication and anonymous access are enabled, the identity that is passed to the web application will depend on the one that was authenticated by IIS. IIS first attempts to use anonymous access to grant a user access to a web application resource. If this attempt fails, it then tries to use Windows authentication
Identity impersonate="true" username="username" password="password"
- This allows the web application to run under a specific identity.
|Info: There are differences between IIS 6.0 and IIS 5.0 in the way they handle requests. First, the http.sys kernel mode is implemented only in IIS 6.0. It is not a feature of IIS 5.0. In IIS 5.0, the request is caught directly by the aspnet_asapi module, which in turn passes the request to the worker process. The worker process and the ISAPI module communicate through pipes which cause calling overhead. Moreover, a single instance of the worker process serves all web applications; no application pools exist. As such, the model supplied by IIS 6.0 is much improved over the IIS 5.0 model. Second, the worker process in IIS 5.0 is “aspnet_wp.exe” as opposed to “w3wp.exe” in IIS 6.0. The worker process “aspnet_wp.exe” runs under the default account “ASPNET” as opposed to “NetworkService” in IIS 6.0. You can change this account by locating the
<processmodel /> element in the “machine.config” file.
|Info: IIS 7.0 presents two ways to handle ASP.NET requests. First, there is the classic way which behaves the same as IIS 6.0; this is useful in compatibility scenarios. Second, there is the new integrated way where ASP.NET and IIS are part of the same request processing pipeline. In this second way, the .NET modules and handlers plug directly into the generic request-processing pipeline, which is much more efficient than the IIS 6.0 way.
Into the HTTP Runtime
So, let us summarize what happened so far: the request has passed from the browser to http.sys, which in turn passed the request to the application pool. The worker process which is running the application pool investigates the URL of the request, and uses the IIS application extension mapping to load up the ASP.NET ISAPI “aspnet_isapi.dll”. The ASP.NET ISAPI will now load the HTTP Runtime, which is also called the ASP.NET Runtime.
OK, so now, we begin investigating what happens inside the HTTP Runtime. The entry point of the Runtime is the
HttpRuntime class. The
HttpRuntime.ProcessRequest method signals the start of the processing. In the following subsections, we will examine what happens inside the Runtime after the
ProcessRequest method is called:
A new HttpContext instance of the request is created
HttpContext lives during the lifetime of the request and is accessible via the static
HttpContext.Current property. The
HttpContext object represents the context of the currently active request, as it contains references to objects you can access during the request lifetime, such as
Cache. At any time during request processing,
HttpContext.Current gives you access to all of these objects. Moreover, the
HttpContext object contains an
Items collection which you can use to store request specific information.
HttpRuntime consults the HttpApplicationFactory class to load an HttpApplication object
HttpApplicationFactory class creates a pool of
HttpApplication objects for your ASP.NET application, and associates each request with a
HttpApplication object from that pool. If no objects exist in the pool at the time of request, then the
HttpApplicationFactory creates an object and passes it to the request. The net result is that, by now, you have your request directed to your application – running inside its AppDomain space – and you have an
HttpApplication object assigned for your request. So, now that you know what is happening, you might be wondering what the purpose of the
HttpApplication object is.
HttpApplication is the outer container for your specific web application, and it maps to the class defined in Global.asax. If you examine your Global.asax file, you will notice that it inherits from the
System.Web.HttpApplication class. There is a set of predefined events that fire during a request lifetime; you can see these events in the Global.asax file.
HttpApplication acts as the event controller of these events. Moreover,
HttpApplication maintains a list of configured HttpModules which are loaded dynamically during request processing. So, where do these events and HttpModules get executed? And, what are exactly HttpModules and HttpHandlers? The answers of these questions are found inside the HTTP Pipeline:
Inside the HTTP Pipeline
The HTTP Pipeline is just, as the name implies, a pipeline for the request to pass by. It is called a pipeline because it contains a set of HttpModules that intercept the request on its way to the HttpHandler. HTTPModules are classes that have access to the incoming request. These modules can inspect the incoming request and make decisions that affect the internal flow of the request. After passing through the specified HTTPModules, the request reaches an HTTP Handler, whose job it is to generate the output that will be sent back to the requesting browser.
Request passes through HTTP Modules
HttpModules are configured on both machine level (machine.config) and application level (web.config). There are many built-in HttpModules in ASP.NET that access the request and perform various functions. Of these HttpModules are Authentication, State Management, and Caching modules. ASP.NET 2.0 adds more modules such as Membership, Role Management, and Personalization. The figure below is taken from the “machine.config” file, and it shows how built-in modules are defined:
Developers can, of course, write their own modules and plug them in the “machine.config” if they intend to apply the modules on all their applications, or in the “web.config” of a certain application if they intend to apply the modules on that specific application. Requests inside the HTTP Pipeline will pass through all modules defined in the “machine.config” and “web.config”. As mentioned in the previous section, these modules are maintained inside the HttpApplication and are loaded dynamically at runtime.
Request hits the HTTP Handler
HTTP Handlers are the endpoints in the HTTP pipeline. The job of the HTTP Handler is to generate the output for the requested resource. For ASPX pages, this means rendering these pages into HTML and returning this HTML. HTTP Handlers can be configured at both machine level (machine.config) and application level (web.config). The figure below is taken from the “machine.config”, and it shows how HTTP Handlers are set.
As you can see from the above figure, different resources are configured to use different handlers. For ASP.NET ASPX pages, note that it is configured to use the “
PageHandlerFactory”. The job of the “
PageHandlerFactory” is to provide an instance of an HTTP Handler that can handle the request. What the “
PageHandleFactory” does is that it tries to find a compiled class that represents the requested page “mypage.aspx”. If it succeeds, then it passes this compiled class as the HTTP Handler. If there is no compiled class to represent the requested page because the request is the first one or because the page has been modified since the last request, then the “
PageHandlerFactory” compiles the requested page “mypage.aspx” and returns the compiled class. Any subsequent requests will be served by the same compiled class until the page is modified.
By now, you might be wondering how come the “
PageHandlerFactory” returns an instance of the compiled class to act as the handler for the request. Is the compiled class an actual HTTP Handler? The answer will be obvious once you inspect the code-behind of your page, “mypage.aspx.cs”. You will quickly notice that the page inherits from the “
System.Web.UI.Page” class; this class, in turn, implements the “
IHttpHandler” interface which makes it suitable as an HTTP Handler.
|Info: Page compilation is beyond the scope of this article, but it will be discussed in my next article which discusses the ASP.NET Page model.
Page executing starts
Finally, the request has been handed to the appropriate HTTP Handler as we saw in the previous section. Next, the Runtime calls the
IHttpHandler.ProcessRequest method and the ASP.NET Page life cycle starts. What happens next is outside the scope of this document, and will be explained thoroughly in the next article.
This article discussed the hidden details of what happens whenever we request an ASP.NET page via the browser. To quickly summarize the process:
- Request for “mypage.aspx” of the web application “myapplication” is issued from the browser
- Request reaches IIS and is picked up by the http.sys driver
- The http.sys driver forwards the request to the application pool which “myapplication” is configured to run under
- The worker process of the application pool “w3wp.exe” loads the ASP.NET ISAPI “aspnet_isapi.dll” by inspecting the URL and mapping the extension “.aspx” to the “aspnet_isapi” (these extensions are defined in IIS)
- The ISAPI “aspnet_isapi” loads the HTTP Runtime
- The HTTP Runtime creates an
HttpContext and associates the request with an
- The request passes through the HTTP Pipeline
- HTTP Modules are executed against the request until the request hits the ASP.NET page HTTP Handler
- Once the request leaves the HTTP Pipeline, the Page life cycle starts
The Viewstate and Page life cycle article is now posted here.