One of the cooler but definitely unsung feature of the IIS7 Server Core is PreConditions. So, that's what I am going to talk about... yup, I am exploring the little, non-obvious details that is IIS7.
What is a PreCondition
The basic idea of PreConditions is for a piece of code to run very early-on in request processing, quickly calculate "answers" to some "questions", and allow IIS to conditionalize subsequent request processing based on those answers. Ideally, the user expresses conditions via configuration and not programmatic manipulation, allowing the user to make powerful changes to server behavior without writing code.
PreCondition calculation happens very early on in request processing, after the user-mode worker process picks up the request from an HTTP.SYS Request Queue but before real request processing begins. Real request processing includes things like:
- Calculating the effective URL (remember, ISAPI Filters listening to SF_NOTIFY_PREPROC_HEADERS to change effective URL, as can PreBeginRequest Modules)
- Loading relevant metadata based on the URL (such as what modules are enabled and what handlers are available for the request)
- And finally driving the request pipeline, notifying modules of events, and responding to module actions and return values
But what is a PreCondition, Really?
So... you have an inkling of an idea about this "PreCondition" thing and hear that it allows the user to make powerful changes to server behavior, but the same can be said of pulling out the Server's power cord... 😉 so what distinguishes between the two? Enough on the motivations and high-level architectural discussions and get to the concrete details and examples, you say? Alright, here's the details (of course, dated and subject to RTM change)...
IIS7 Server Core allows preConditions to be defined on the following entities:
IIS7 Server Core allows the following groups of values for preConditions:
- Process Bitness: bitness32, bitness64
- Managed Extensibility Style: integratedMode, ISAPIMode
- .NET Framework Version: runtimeVersionv1.1, runtimeVersionv2.0
- Managed Code Execution: managedHandler
For example, if you assign the bitness32 preCondition to a globalModule (see this blog entry on why it is only native code), IIS will load that DLL only if the worker process is in WOW64 (32bit emulation) mode on a 64bit OS. Similarly, you can assign the managedHandler preCondition to a module to make it applicable only for "managed code" requests like .ASPX pages and NOT apply for "non-managed" requests like .GIF.
In all cases, there is exactly ONE server configuration file and list to manage, but it expresses a rich set of configurations and server behavior.
Ok, I probably lost everybody by now or you are wondering "why does this preCondition thing exist?" 😉 ... so let me just explain what they are good for and you can judge for yourself.
A major problem with IIS6, 64bit Windows, and WOW64 is that exactly one configuration is used by IIS to load DLLs used for both native 64bit as well as WOW64 32bit modes, while 64bit Windows requires DLLs to match the bitness of the process they load in.
Thus, if you configure ISAPI Filters or ISAPI Extensions (through [Wildcard] Application Mappings) and the bitness of the DLL does not match the bitness of the worker process (which only works if your file is located in a directory with WOW64 FileSystem Redirection enabled [basically just the Windows System32 folder]), you will see problems when you toggle IIS6 to launch worker process from 32bit to 64bit mode or vice versa.
For examples of the complexities and user confusion, see the following blog entries:
- HOWTO: Diagnose one cause of W3SVC failing to start with Win32 Error 193 on 64bit Windows
- HOWTO: Diagnose one cause of 503 Service Unavailable on IIS6 on 64bit Windows
- HOWTO: Diagnose one cause of 503 Service Unavailable on IIS6
The bitness32 and bitness64 preConditions neatly solve this problem by allowing one configuration to express via preCondition: "these DLLs are 32bit, and those DLLs are 64bit, so only load the 32bit DLLs for a 32bit worker process in WOW64 and 64bit DLLs for a 64bit worker process." Of course, the user is still responsible for defining the preCondition to take advantage of it, but IIS7 comes with many of them pre-defined so examples of proper usage should be easy to find.
Managed Extensibility Style
The deep, high-fidelity, highly-functional, yet highly-performant integration of managed code into core server extensibility illustrates one of the major advances in IIS7 - you can fully extend IIS7 with managed code in Integrated Mode*, while ISAPI Mode represents the partial integration possible with IIS6 and ASP.Net 2.0.
However, we are aware that not everyone nor every ASP.Net application is ready to make this jump, so we have the integratedMode and ISAPIMode preConditions to allow:
- Application Pool to specify whether it is running managed code applications in Integrated Mode or ISAPI Mode
- Handlers to specify whether it should run for requests in Integrated Mode or ISAPI Mode
The integratedMode and ISAPIMode preConditions allow you to control your migration of ASP.Net applications from ISAPI Mode to Integrated Mode and do so in a partial fashion within exactly one configuration section... so you CAN have your cake and eat it, too.
* Almost true. You cannot have managed global modules, but you are not missing out on much.
.NET Framework Version
By now, we should all know about the .NET Framework versioning problem. See this blog entry for a refresher.
Since we cannot run .Net Framework 1.1 and .Net Framework 2.0, yet for compatibility reasons their respective handlers are configured in the unified <handlers> section, how do we:
- Maintain one single <handlers> section as the place to configure all request handlers
- Conditionally load different managed handlers based on framework version
- Tie it all to different Application Pools (necessary for process isolation to support .Net Framework versioning)
Yup, you guessed it - the runtimeVersionv1.1 and runtimeVersionv2.0 preConditions.
Managed Code Execution
While IIS7 offers great programmability and extensibility by managed code, there are always situations that require pure native code speed. How do you make sure that requests that do not involve managed code for processing stay "native", yet simultaneously allow requests that leverage managed code features?
That is the problem that the managedHandler preCondition solves. It basically identifies "this module uses managed code, so if the request is not already serviced by managed code, do not use the module".
For example, even though IIS7 comes predefined with managed modules performing ASP.Net Forms Authentication, by default they have the managedHandler preCondition and only apply to managed resources and not static resources like .GIF.
This allows a single unified <modules> list to give pure native performance for .GIF files while simultaneously allowing Forms Authentication to be enabled for other managed resources. And all you need to do to have managed modules apply to all requests is to simply remove the managedHandler preCondition for that module.
Prior to IIS7, you had to shotgun a wildcard application mapping for ASPNET_ISAPI.DLL to do this. You no longer want to do this on IIS7 - just run in Integrated Mode and add/remove managedHandler your way to include/exclude managed code from IIS request processing.
Whew. Thanks for making it this far. I think I probably lost just about everyone with this topic. 🙂
But, even if this entry was just blah blah blah IIS7 blah blah blah preConditions blah blah blah, just remember that:
preCondition is a cool "man behind the curtains" technology that make a lot of things work automatically and elegantly.
IIS7... It's Everywhere You Want to Be.