Achtung! IIS7 Preconditions

If you ever looked into the IIS configuration file applicationhost.config you might have encountered a setting called “precondition”.  Here is the handler entry for aspnet_isapi.dll for example:

 <add name="AXD-ISAPI-2.0" path="*.axd" verb="GET,HEAD,POST" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />

The Need for Preconditions

IIS is an application platform that supports extensibility in multiple different ways: ISAPI filters, ISAPI extensions and new with IIS7: managed modules and handlers and native module and handlers.

All these extensibility modules are all packaged into DLLs and these DLLs are then loaded into IIS worker processes.

The Bitness precondition

64-Bit processors become more and more prevalent. Soon OEMs will stop shipping 32-Bit processors. Switching over to a 64-Bit Operation System, for example Longhorn Server 64-Bit, is hard though, because a lot of software is still written for Windows 32-Bit operating systems. Longhorn Server provides a 32-Bit execution environment on top of the 64-bit Operating System called WoW64. IIS takes advantage of this and you can run 32-Bit worker processes side by side with 64-Bit worker processes. IIS7 introduces the bitness32 and bitness64 precondition to make sure you load DLLs with the right bitness in your worker processes. Without this precondition you would have to have 32-Bit and 64-Bit handler and module maps.

The RuntimeVersion preconditon

This precondition was introduced for the different versions of the .NET Framework. Currently only one version of  the .NET Framework can be loaded in a process. Trying to load another version of the .NET Framework into the same process results into ugly errors. With the RuntimeVersion precondition components that need a particular  version of the .NET Framework can be marked to be loaded only in worker processes that loaded this version of the .NET Framework already. Setting the version of the .NET Framework is now an Application Pool property and IIS worker processes use this property to preload the right version of the .NET Framework on startup.

The ManagedHandler precondition

IIS 7.0 introduces a new managed extensibility model. Handlers and Modules can now be written in managed code and directly integrated into the IIS request pipeline. But switching between managed and native code is an expensive operation. The managedHandler precondition was introduced to allow optimizing the performance of requests where no managed code needs to be involved, for example when static files (.html, .jpg etc.) are served. No managed code is called if the request is served by a native handler and every managed module is configured with the managedHandler precondition. A practical scenario is Forms authentication. The managed Forms authentication module has a managedHandler precondition and is therefore only called when ASP.NET content (e.g. *.aspx) pages are requested. If a .html page is requested the forms authentication is not called. If you want to protect all your content with forms authentication you can simply remove the managedHandler precondition from the Forms authentication module entry.

The Mode Precondition

The new managed module and managed handler extensibility allows you to add managed code, i.e. ASP.NET pages, modules and handlers, directly into the IIS7 pipeline. IIS7 needs to run the worker process in a particular way for this to work. It needs to load the .NET Framework 2.0 and it also needs to run a module called webengine.dll. Webengine.dll does all the work of hooking up managed modules with the IIS7 pipeline because IIS7 itself doesn’t know about managed code. The new way to integrate ASP.NET pages, modules and handlers is called “Integrated Mode”.

But there is still the good old way to hook up managed code in IIS7, i.e. via the ISAPI interface. ASPNET_ISAPI.DLL used to do this in IIS 5, 5.1 and 6.0. IIS7 continues to support the ISAPI hookup if you run the worker process in “classic Mode”.

As a result IIS7 introduced two preconditions called “integratedMode” and “classicMode”. A handler that has an “integratedMode” precondition associated with it will only be loaded into an Application Pool that has the “integratedMode” property set on the ApplicationPool. Handlers with the “classicMode” precondition will only be loaded into Application Pools that have the integratedMode property set to false.

Configuration Sections that Use Preconditions

There are four IIS7 configuration sections that use preconditions:

·         ISAPI filters

·         globalModules

·         handlers

·         modules

ISAPIFilters

ISAPI filter preconditions are the easiest to explain.

When the ISAPI filter module intiailizes it reads the <isapiFilters> configuration section and goes through the list of ISAPI filters. Every filter in the list gets loaded into memory and its initialization function is called. Now what if the filter is a 32-Bit binary but you are running on a 64-Bit Longhorn system? Initialization would fail and the IIS worker process wouldn’t come up. That’s not an unlikely scenario because running 32-Bit and 64-Bit worker processes on IIS7 is a supported scenario. Just think about your legacy web application that isn’t available on 64-Bit yet?  Don’t you want to run it in a 32-Bit AppPool side-by-side with your new 64-Bit apps and take advantage of the humongous 64-Bit address space?

Preconditions come handy in this case. If you have a 64-Bit ISAPI filter you can mark your ISAPI filter with the bitness64 precondition.  This makes sure that it only gets loaded in 64-Bit worker processes because when the ISAPI filter module initializes it ignores ISAPI filters if the bitness precondition differs from the bitness of the process. Here an example:

<isapiFilters>

  <filter name="MyIsapiFilter" path="c:\myfilter.dll" preCondition="bitness64" />

</isapiFilters>

MyIsapiFilter is never loaded into a 32-Bit worker process.

globalModules

Another configuration section that works with preconditions is globalModules. The globalModules section determines which IIS modules get loaded into worker processes. With preconditions you can make sure that worker process only load modules that work in a particular Application Pool. This was especially done to make the ASP.NET side-by-side story more manageable. You might remember that the module webengine.dll is responsible to hook up the managed request pipeline with the native IIS7 pipeline. When an AppPool is configured to run this way the AppPool runs in so-called “Integrated Mode”.  “Integrated Mode” is only possible with the .NET Framework 2.0 binaries. This already explains the preconditions you see on webengine.dll in the globalModules section:

<add name="ManagedEngine" image="%windir%\Microsoft.NET\Framework\v2.0.50727\webengine.dll" preCondition="integratedMode,runtimeVersionv2.0,bitness32" />

Webengine.dll precondtions

The “integratedMode” precondition ensures that webengine.dll gets only loaded in Application Pools that are configured to run “Integrated Mode”, i.e. the Application Pool property managedPipelineMode is set to “Integrated”.

runtimeVersionv2.0 is a precondition that ensures that webengine.dll is only loaded by the IIS core if the Application Pool has the .NET Framework 2.0 version preloaded. Without ensuring this IIS or an application within IIS might have loaded a different .NET Framework version (at the time of this writing the only other supported .NET Framework version is 1.1). Loading webengine.dll would fail if a different .NET Framework version would already be loaded in the process. Only one version of the .NET Framework can be loaded into a particular process space however.

The final precondition is bitness32. I already talked about this quickly in the isapiFilter paragraph.  You might ask why there are no bitness preconditions on the other modules. The nice thing with system DLL’s living underneath the \windows directory is that they get automatically redirected based on their bitness. If running a 64-Bit worker process IIS will load global modules from the 64-Bit Windows directory whereas when running a 32-Bit worker process the modules get loaded from the 32-Bit Windows directory.

handlers

The handlers section in applicationhost.config determines how particular requests get routed. Usually this is based on the file extension. Requests that end in .asp for example get routed to the ISAPI Extension module which in turn routes it on to ASP.DLL. Here is the beginning of a handler section in applicationhost.config. I didn’t include the whole section because it is pretty big.

<handlers accessPolicy="Script, Read">

<add name="ASPClassic" path="*.asp" verb="GET,HEAD,POST" modules="IsapiModule" scriptProcessor="%windir%\system32\inetsrv\asp.dll" resourceType="File" />

<add name="PageHandlerFactory-ISAPI-1.1" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v1.1.4322\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv1.1,bitness32" />

<add name="PageHandlerFactory-ISAPI-2.0" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />

<add name="PageHandlerFactory-Integrated" path="*.aspx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.PageHandlerFactory" preCondition="integratedMode" />

</handlers>

              

The preconditions on the handlers section are usually the same as on globalModules and side-by-side of ASP.NET applications is also the major reason to have preconditions. If you worked with ASP.NET 1.1 and 2.0 on IIS6 you might have noticed that ASP.NET 2.0 has to be configured on a per application basis. Before you can run a particular ASP.NET application you have make sure the application is configured for the ASP.NET version you want to use. ASP.NET is coming with a property page that can be plugged into IIS6 to do this:

<ASP.NET UI in IIS 6.0>

What this property page does is to copy the full handlers list to applications metabase. This becomes a problem over time because a) the metabase becomes pretty big and b) handlers that get installed globally are not inherited anymore. It also doesn’t prevent you from having two applications with differing versions of ASP.NET running in the same process. The result is pretty random. Whatever application loads its version of ASP.NET first will work. The other application will fail to load. The next time the worker process recycles the second application might work but not the first one anymore.

With preconditions these problems are gone because the version of the .NET Framework to load is an Application Pool property.  Together with preconditions it enables IIS to have a single global handler list that doesn’t have to be replicated to every application that runs a different version of the .NET Framework. All that an Administrator has to do is to configure an Application Pool with the right .NET Framework version and put the applications in there. One could say that ASP.NET side-by-side is solved by Application Pools.

ASP.NET 1.1 Application Pool configuration

In the above example you see three .aspx mappings all in the same list. It depends on how the Application Pool is configured on which handler is loaded. Here is the setting for ASP.NET 1.1 applications:

<add name="PageHandlerFactory-ISAPI-1.1" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v1.1.4322\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv1.1,bitness32" />

To get these settings loaded in your Application Pool you have to

·         set the managedPipelineMode property to “classicMode”

·         set the managedRuntimeVersion property to “v1.1”

·         run the worker process on a Vista/Longhorn 32-Bit Operating System or set the Application Pool property Enable32bitAppOnWin64 to true on 64-Bit Vista or Longhorn versions

ASP.NET 2.0 Application Pool configuration for classic Mode

The second .aspx mapping is needed for Application Pools that run in classic Mode:

<add name="PageHandlerFactory-ISAPI-2.0" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />

Is only effective if

·         the managedPipelineMode property on the Application Pool is set to “classicMode”

·         the managedRuntimeVersion property on the Application Pool is set to “v2.0”

·         the worker process is running on a Vista/Longhorn 32-Bit Operating System or if the Application Pool property Enable32bitAppOnWin64 is set to true on 64-Bit Vista or Longhorn

ASP.NET 2.0 Application Pool configuration for Integrated Mode

The third mapping will be loaded for Integrated Mode.

<add name="PageHandlerFactory-Integrated" path="*.aspx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.PageHandlerFactory" preCondition="integratedMode" />

You see that ISAPI is not involved in this mapping. A managed type is used instead which tells us that Integrated Mode is enabled. To enable Integrated Mode the AppPool has to have the following settings

·         the managedPipelineMode property on the Application Pool is set to “integratedMode”

·         the managedRuntimeVersion property on the Application Pool is set to “v2.0” because otherwise webengine.dll wouldn’t load (see globalModules above).

Managed code is also bitness independent. The right version of webengine.dll has to be loaded though. On a 64-Bit operating system both versions are installed by IIS setup.

Modules

Modules are to IIS7 what ISAPI filters where to IIS6. Where handlers get executed only for a particular file extension, modules can register for and participate in particular events that get fired by the IIS core during each request, for example at BeginRequest, Authenticate, SendResponse or EndRequest.

The loading of modules is already done via the globalModules list which is global. But enabling a particular module can be done per application and that’s why IIS7 has the <modules> section. It is pretty much an enablement list for modules.

Here is a typical IIS7 modules section:

<modules>

<add name="HttpCacheModule" />

<add name="StaticCompressionModule" />

<add name="DefaultDocumentModule" />

<add name="DirectoryListingModule" />

<add name="ProtocolSupportModule" />

<add name="StaticFileModule" />

<add name="AnonymousAuthenticationModule" />

<add name="IsapiFilterModule" />

<add name="RequestFilteringModule" />

<add name="CustomErrorModule" />

<add name="IsapiModule" />

<add name="HttpLoggingModule" />

<add name="RequestMonitorModule" />

<add name="ConfigurationValidationModule" />

<add name="OutputCache" type="System.Web.Caching.OutputCacheModule" preCondition="managedHandler" />

<add name="Session" type="System.Web.SessionState.SessionStateModule" preCondition="managedHandler" />

<add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" preCondition="managedHandler" />

<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" preCondition="managedHandler" />

<add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" preCondition="managedHandler" />

<add name="RoleManager" type="System.Web.Security.RoleManagerModule" preCondition="managedHandler" />

<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" preCondition="managedHandler" />

<add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" preCondition="managedHandler" />

<add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" preCondition="managedHandler" />

<add name="Profile" type="System.Web.Profile.ProfileModule" preCondition="managedHandler" />

<add name="UrlMappingsModule" type="System.Web.UrlMappingsModule" preCondition="managedHandler" />

</modules>

The only precondition you see in the <modules> collection is called “managedHandler” and you can see that it is only applied to modules that have a managed type.

For performance reasons IIS7 doesn’t want to run managed code on every request because managed code still has some performance drawbacks. The managedHandler precondition is used to only enable a particular module if the request is going to a managed handler anyway. Every request that goes to a native handler will run through the IIS7 pipeline without managed code being executed. Here is an example: Forms Authentication is a managed module with the managedHandler precondition:

<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" preCondition="managedHandler" />

When a request to /default.aspx comes in IIS7 determines that a managed handler (System.Web.UI.PageHandlerFactory – see handler paragraph above) will execute the request. The managedHandler precondition becomes true and every module that has the managedHandler precondition can participate in the request. Browser clients would be challenged with the authentication page if Forms authentication is enabled for /foo.aspx.

If a request to /default.htm is made IIS7 will find out at the beginning of the request that it will be handled by the StaticFile handler which is a native handler. The managedHandler precondition is false and the modules which have this precondition wouldn’t be loaded for /default.htm.

But what if you want to protect /default.htm with forms authentication? Simple, just remove the managedHandler precondition and every request will have to go through the Forms Authentication module.

Note: FormsAuthentication needs some other modules to work, for example Roles, UrlAuthorization etc. It is recommended to remove all managedHandler preconditions if non-managed content is supposed to be protected with Forms Authentication.

Handler issues

There is one issue to be aware of with preconditions. What happens if a handler for a particular request is configured, for example .foo is mapped like this

<add name="FOO-Handler" path="*.foo" verb="*" modules="FooModule" preCondition=" bitness32" />

But the worker process is a 64-Bit process?

In this case the precondition doesn’t match and IIS continues to search for another handler. The last handler on the list is usually the StaticFile handler:

<add name="StaticFile" path="*" verb="*" modules="StaticFileModule,DefaultDocumentModule,DirectoryListingModule" resourceType="Either" requireAccess="Read" />

It catches pretty much all requests that weren’t handled by a handler higher up. The static file handler doesn’t serve out the request as static file though. This would be a security problem, just imagine serving out ASP or ASP.NET files as static text. The static file handler is intelligent enough to not send out any file extensions that are on the handler list. A 404.3 error is generated in this case.

Summary

Preconditions are a powerful way to introduce conditional logic into IIS configuration sections. This enables IIS7 to provide a solid side-by-side platform for .NET Framework 1.1 and 2.0 applications but also for 32-Bit and 64-Bit applications.

3 Comments

Comments have been disabled for this content.