In the previous article IIS 6.0 article we have learnt how IIS routes web request to ASP.Net ISAPI “aspnet_isapi.dll“.In this article we will looks at find how web requests flow through the ASP.NET framework , from Web Server, through ISAPI all the way up the request handler and your code.ISAPI is a low level unmanged Win32 API. The interfaces defined by the ISAPI spec are very simplistic and optimized for performance. They are very low level – dealing with raw pointers and function pointer tables for callbacks – but they provide he lowest and most performance oriented interface that developers and tool vendors can use to hook into IIS.ISAPI tends to be used primarily as a bridge interface to provide Application Server type functionality to higher level tools. For example, ASP and ASP.NET both are layered on top of ISAPI.
As a protocol ISAPI supports both ISAPI extensions and ISAPI Filters. Extensions are a request handling interface and provide the logic to handle input and output with the Web Server – it’s essentially a transaction interface. ASP and ASP.NET are implemented as ISAPI extensions. ISAPI filters are hook interfaces that allow the ability to look at EVERY request that comes into IIS and to modify the content or change the behavior of functionalities like Authentication. Incidentally ASP.NET maps ISAPI-like functionality via two concepts: Http Handlers (extensions) and Http Modules (filters). We’ll look at these later in more detail.
IIS (5.0, 6.0) and ASP.Net
In IIS 5 hosts aspnet_isapi.dll directly in the inetinfo.exe process or one of its isolated worker processes (as discussed in previous article).While processing the Web Requests,IIS picks up the request and forwards the request to aspnet_isapi.dll. After that it is forwarded to the Worker process (asp_wp.exe) via Named Pipe calls.Worker process manages the pipeline through request flows. All asp.net software components like HttpApplication, Session run by the instance of Worker Process.
In case of IIS 6.0 the request is processed by HTTP.SYS driver and then passed to the asp.net worker process. HTTP.SYS is a kernel level driver close to operating system. By passing the request directly to asp.net worker process, asp.net bypass the overhead of an extra out-of-process call and automatically enforces application isolation.
With IIS 5.0 then applications are pooled in one application pool which is hosted by DLLHost.exe. But in case of IIS 6.0 where IIS 6.0 operates in worker process isolation mode, up to 2000 application pools can be created where each application pool can be configured separately.
In IIS 6.0, ISAPI extensions run in the application pool worker process. The .NET Runtime also runs in this same process, so communication between the ISAPI extension and the .NET runtime happens in-process which is inherently more efficient than the named pipe interface that IIS 5 must use.
ASP.Net Worker Process
The worker processes ASPNET_WP.EXE (IIS5) and W3WP.EXE (IIS6) host the .NET runtime and the ISAPI DLL calls into small set of unmanged 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 which exposes the IISAPIRuntime interface via COM to a caller.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 and module name and virtual path information for the application which is 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 they get loaded only as requests hit the 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.
At this point we 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.
Note : Remember ISAPI is multi-threaded so requests will come in on multiple threads through the reference that was returned by ApplicationDomainFactory.Create().
HttpRuntime, HttpContext, and HttpApplication
When a request hits, it is routed to the ISAPIRuntime.ProcessRequest() method. This method in turn calls HttpRuntime.ProcessRequest that does several important things (look at System.Web.HttpRuntime.ProcessRequestInternal with Reflector):
- Create a new HttpContext instance for the request
- Retrieves an HttpApplication Instance
- Calls HttpApplication.Init() to set up Pipeline Events
- Init() fires HttpApplication.ResumeProcessing() which starts the ASP.NET pipeline processing
First a new HttpContext object is created and it is passed the ISAPIWorkerRequest that wrappers the ISAPI ECB. The Context is available throughout the lifetime of the request and ALWAYS accessible via the static HttpContext.Current property. As the name implies, the HttpContext object represents the context of the currently active request as it contains references to all of the vital objects you typically access during the request lifetime: Request, Response, Application, Server, Cache. At any time during request processing HttpContext.Current gives you access to all of these object.
The HttpContext object also contains a very useful Items collection that you can use to store data that is request specific. The context object gets created at the begging of the request cycle and released when the request finishes, so data stored there in the Items collection is specific only to the current request. A good example use is a request logging mechanism where you want to track start and end times of a request by hooking the Application_BeginRequest and Application_EndRequest methods in Global.asax.
Once the Context has been set up, ASP.NET needs to route your incoming request to the appropriate application/virtual directory by way of an HttpApplication object. Every ASP.NET application must be set up as a Virtual (or Web Root) directory and each of these ‘applications’ are handled independently.
Each request is routed to an HttpApplication object. The HttpApplicationFactory class creates a pool of HttpApplication objects for your ASP.NET application depending on the load on the application and hands out references for each incoming request. The size of the pool is limited to the setting of the MaxWorkerThreads setting in machine.config’s ProcessModel Key, which by default is 20.
The pool starts out with a smaller number though; usually one and it then grows as multiple simulataneous requests need to be processed. The Pool is monitored so under load it may grow to its max number of instances, which is later scaled back to a smaller number as the load drops.
HttpApplication is the outer container for your specific Web application and it maps to the class that is defined in Global.asax. It’s the first entry point into the HTTP Runtime that you actually see on a regular basis in your applications. If you look in Global.asax (or the code behind class) you’ll find that this class derives directly from HttpApplication.
HttpApplication’s primary purpose is to act as the event controller of the Http Pipeline and so its interface consists primarily of events. The event hooks are extensive and include:
- Handler Execution
Each of these events are also implemented in the Global.asax file via empty methods that start with an Application_ prefix. For example, Application_BeginRequest(), Application_AuthorizeRequest(). These handlers are provided for convenience since they are frequently used in applications and make it so that you don’t have to explicitly create the event handler delegates.
It’s important to understand that each ASP.NET virtual application runs in its own AppDomain and that there inside of the AppDomain multiple HttpApplication instances running simultaneously, fed out of a pool that ASP.NET manages. This is so that multiple requests can process at the same time without interfering with each other.
AppDomain ID stays steady while thread and HttpApplication Ids change on most requests, although they likely will repeat. HttpApplications are running out of a collection and are reused for subsequent requests so the ids repeat at times. Note though that Application instance are not tied to a specific thread – rather they are assigned to the active executing thread of the current request.
Threads are served from the .NET ThreadPool and by default are Multithreaded Apartment (MTA) style threads. You can override this apartment state in ASP.NET pages with the ASPCOMPAT=”true” attribute in the @Page directive. ASPCOMPAT is meant to provide COM components a safe environment to run in and ASPCOMPAT uses special Single Threaded Apartment (STA) threads to service those requests. STA threads are set aside and pooled separately as they require special handling.
Since HttpApplication objects are all running in the same AppDomain ,thi is how ASP.NET can guarantee that changes to web.config or individual ASP.NET pages get recognized throughout the AppDomain. Making a change to a value in web.config causes the AppDomain to be shut down and restarted. This makes sure that all instances of HttpApplication see the changes made because when the AppDomain reloads the changes from ASP.NET are re-read at startup. Any static references are also reloaded when the AppDomain so if the application reads values from App Configuration settings these values also get refreshed.
Any requests that are already in the pipeline processing will continue running through the existing pipeline, while any new requests coming in are routed to the new AppDomain. In order to deal with ‘hung requests’ ASP.NET forcefully shuts down the AppDomain after the request timeout period is up even if requests are still pending. So it’s actually possible that two AppDomains exist for the same HttpApplication at a given point in time as the old one’s shutting down and the new one is ramping up. Both AppDomains continue to serve their clients until the old one has run out its pending requests and shuts down leaving just the new AppDomain running.
HttpContext, HttpModules and HttpHandlers
The HttpApplication itself knows nothing about the data being sent to the application – it is a merely messaging object that communicates via events. It fires events and passes information via the HttpContext object to the called methods. The actual state data for the current request is maintained in the HttpContext object mentioned earlier. It provides all the request specific data and follows each request from beginning to end through the pipeline.Once the pipeline is started, HttpApplication starts firing events one by one.Each of the event handlers is fired and if events are hooked up those handlers execute and perform their tasks. The main purpose of this process is to eventually call the HttpHandler hooked up to a specific request. Handlers are the core processing mechanism for ASP.NET requests and usually the place where any application level code is executed. Remember that the ASP.NET Page and Web Service frameworks are implemented as HTTPHandlers and that’s where all the core processing of the request is handled. Modules tend to be of a more core nature used to prepare or post process the Context that is delivered to the handler. Typical default handlers in ASP.NET are Authentication, Caching for pre-processing and various encoding mechanisms on post processing.
HTTP modules and HTTP handlers are an integral part of the ASP.NET architecture. While a request is being processed, each request is processed by multiple HTTP modules (for example, the authentication module and the session module) and is then processed by a single HTTP handler. After the handler has processed the request, the request flows back through the HTTP modules. Each module receives the http request and has full control over it. The module can play with the request in any way it sees fit. Once the request passes through all of the HTTP modules, it is eventually served by an HTTP handler. The HTTP handler performs some processing on it, and the result again passes through the HTTP modules in the pipeline.
To create an HTTP module, you must implement the IHttpModule interface in you class and add module in web.config as mentioned below
<httpModules> <add type="[COM+ Class], [Assembly]" name="[ModuleName]" /> <remove type="[COM+ Class], [Assembly]" name="[ModuleName]" /> <clear /> </httpModules>
HTTPHandlers are used to process individual endpoint requests. Handlers enable the ASP.NET framework to process individual HTTP URLs or groups of URL extensions within an application. Unlike modules, only one handler is used to process a request. All handlers implement the IHttpHandler interface, which is located in the System.Web namespace. Handlers are somewhat analogous to Internet Server Application Programming Interface (ISAPI) extensions.
The following is the snippet from the machine.config fileYou can see in the above configuration that all the requests for .aspx files are processed by the System.Web.UI.PageHandlerFactory class. Similarly all the requests for .config and other files, which should not be directly accessible to the clients, are handled by the System.Web.HttpForbiddenHandler class. As you might have already guessed, this class simply returns an error to the client stating that these kinds of files are not served.
<httpHandlers> <add verb="*" path="trace.axd" type="System.Web.Handlers.TraceHandler"/> <add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory"/> <add verb="*" path="*.ashx" type="System.Web.UI.SimpleHandlerFactory"/> <add verb="*" path="*.config" type="System.Web.HttpForbiddenHandler"/> <add verb="GET,HEAD" path="*" type="System.Web.StaticFileHandler"/> . . . . . . . . . . . . </httpHandlers>
You might be thinking about the use of such a handler. Well, what if you want to introduce a new kind of server scripting language or dynamic server file such as asp, aspx? You can write your own handler for this.
To implementing HTTP handler following steps are required
- Write a class which implements IHttpHandler interface
- Register this handler in web.config or machine.config file.
- Map the file extension (.aspx) to ASP.NET ISAPI extension DLL (aspnet_isapi.dll) in Internet Services Manager.
Maintaining session state is one of the most common tasks that Web applications perform. HTTP handlers also need to have access to the session state. But session state is not enabled by default for HTTP handlers. In order to read and/or write session data, HTTP handlers are required to implement one of the following interfaces:
An HTTP handler should implement the IRequiresSessionState interface when it requires read-write access to the session data. If a handler only needs read access to session data, then it should only implement the IReadOnlySessionState interface.
In the next article of this series we will learn ASP.Net Basic..