One of the nice enhancements in IIS 7 (and now 8) is the ability to be able to intercept non-managed - ie. non ASP.NET served - requests from within ASP.NET managed modules. This opened up a ton of new functionality that could be applied across non-managed content using .NET code.
I thought I had a pretty good handle on how IIS 7's Integrated mode pipeline works, but when I put together some samples last tonight I realized that the way that managed and unmanaged requests fire into the pipeline is downright confusing especially when it comes to the runAllManagedModulesForAllRequests attribute. There are a number of settings that can affect whether a managed module receives non-ASP.NET content requests such as static files or requests from other frameworks like PHP or ASP classic, and this is topic of this blog post.
Native and Managed Modules
The integrated mode IIS pipeline for IIS 7 and later - as the name suggests - allows for integration of ASP.NET pipeline events in the IIS request pipeline. Natively IIS runs unmanaged code and there are a host of native mode modules that handle the core behavior of IIS. If you set up a new IIS site or application without managed code support only the native modules are supported and fired without any interaction between native and managed code.
If you use the Integrated pipeline with managed code enabled however things get a little more confusing as there both native modules and .NET managed modules can fire against the same IIS request.
If you open up the IIS Modules dialog you see both managed and unmanaged modules. Unmanaged modules point at physical files on disk, while managed modules point at .NET types and files referenced from the GAC or the current project's BIN folder.
Both native and managed modules can co-exist and execute side by side and on the same request. When running in IIS 7 the IIS pipeline actually instantiates a the ASP.NET runtime (via the System.Web.PipelineRuntime class) which unlike the core HttpRuntime classes in ASP.NET receives notification callbacks when IIS integrated mode events fire. The IIS pipeline is smart enough to detect whether managed handlers are attached and if they're none these notifications don't fire, improving performance.
However, with that functionality comes a lot of responsibility. Because every request passes through the ASP.NET pipeline if managed modules (or handlers) are attached there are possible performance implications that come with it. Running through the ASP.NET pipeline does add some overhead.
ASP.NET and Your Own Modules
When you create a new ASP.NET project typically the Visual Studio templates create the modules section like this:
<validation validateIntegratedModeConfiguration="false" />
<modules runAllManagedModulesForAllRequests="true" >
Specifically the interesting thing about this is the runAllManagedModulesForAllRequest="true" flag, which seems to indicate that it controls whether any registered modules always run, even when the value is set to false. Realistically though this flag does not control whether managed code is fired for all requests or not. Rather it is an override for the preCondition="managedHandler" flag on a particular module registered.
With the flag set to the default true setting, you can assume that every IIS request you receive ends up firing through your ASP.NET module pipeline and every module you have configured is accessed even by non-managed requests like static files. In other words, your module will have to handle all requests. So far so obvious - it does as the name suggests.
runAllManagedModulesForAllRequests="false" - different than you might think!
What's not quite so obvious is what happens when you set the runAllManagedModulesForAllRequests="false". You probably would expect that non-ASP.NET requests no longer get funneled through the ASP.NET Module pipeline. But that's not what actually happens.
For example, if I create a module like this:
<add name="SharewareModule" type="HowAspNetWorks.SharewareMessageModule" />
by default it will fire against ALL requests regardless of the runAllManagedModulesForAllRequests flag. Even if the value runAllManagedModulesForAllRequests="false", the module is fired with unmanaged requests going through it. Not quite as expected.
So what is the runAllManagedModulesForAllRequests really good for? It's essentially an override for managedHandler preCondition. If I declare my handler in web.config like this:
<add name="SharewareModule" type="HowAspNetWorks.SharewareMessageModule"
and then set runAllManagedModulesForAllRequests="false" my module only fires against managed requests. If I switch the flag to true, now my module ends up handling all IIS requests that are passed through from IIS.
The moral of the story here is that if you intend to only look at ASP.NET content, you should always set the preCondition="managedHandler" attribute to ensure that only managed requests are fired on this module. But even if you do this, realize that runAllManagedModulesForAllRequests="true" can override this setting, so your module has to anticipate handling any kind of request.
runAllManagedModulesForAllRequests and Http Application Events
Another place the runAllManagedModulesForAllRequests attribute affects is the Global Http Application object (typically in global.asax) and the Application_XXXX events that you can hook up there. So while the events there are dynamically hooked up to the application class, they basically behave as if they were set with the preCodition="managedHandler" configuration switch.
The end result is that if you have runAllManagedModulesForAllRequests="true" you'll see every Http request passed through the Application_XXXX events, and you only see ASP.NET requests with the flag set to "false".
What's all that mean?
Configuring an application to handle requests for both ASP.NET and other content requests can be tricky especially if you need to mix modules that might require both.
Couple of things are important to remember. If your module doesn't need to look at every request, by all means set a preCondition="managedHandler" on it. This will at least allow it to respond to the runAllManagedModulesForAllRequests="false" flag and then only process ASP.NET requests. There's no reason for static files to pass through your module if you're not doing anything related to static files - it only adds overhead. Use the managedHandler preCondition to pre-filter your module's input if it applies.
Look really carefully to see whether you actually need runAllManagedModulesForAllRequests="true" in your applications as set by the default new project templates in Visual Studio. Part of the reason this is the default, is because it was required for the initial versions of IIS 7 and ASP.NET 2 in order to handle MVC extensionless URLs.
However, if you are running IIS 7 or later and .NET 4.0 you can use the ExtensionlessUrlHandler instead to allow you MVC functionality without requiring runAllManagedModulesForAllRequests="true":
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*."
Oddly runAllManagedModulesForAllRequests="true" continues to be the default for Visual Studio 2012 MVC template apps, so I'm not sure why - it should be enabled only if there's a specific need to access non ASP.NET requests.
Note that on the original Windows Server 2008 and Vista (IIS 7.0) you might need a HotFix in order for ExtensionLessUrlHandler to work properly for MVC projects. On my live server I needed it (about 6 months ago), but others have observed that the latest service updates have integrated this functionality and the hotfix is not required. On IIS 7.5 and later I've not needed any patches for things to just work.
As a side note, it's interesting that when you access a static HTML resource, you can actually write into the Response object and get the output to show, which is trippy, because if you look in the IIS Module Configuration you'll find that .HTM files are mapped to a native extension dll. So for example, I can access a default.htm page, and then intercept it in a module and say in the PostRequestHandlerExecute do a Response.Write() and have that output show up in the HTTP output. That's pretty cool, but a bit of mystery on how it works…
I haven't looked closely to see how this works - whether ASP.NET just fires directly into the native output stream or whether the static requests are re-routed directly through the ASP.NET pipeline once a managed code modules are detected. Note that this doesn't work for all non ASP.NET resources - for example, I can't do the same with ASP classic requests, but it makes for an interesting demo when injecting HTML content into a static HTML page :-)
Plan for non-ASP.NET Requests
It's important to remember that if you write a .NET Module to run on IIS 7, there's no way for you to prevent non-ASP.NET requests from hitting your module. So make sure you plan to support requests to extensionless URLs, to static resources like files. Luckily ASP.NET creates a full Request and full Response object for you for non ASP.NET content. So even for static files and even for ASP classic for example, you can look at Request.FilePath or Request.ContentType (in post handler pipeline events) to determine what content you are dealing with.
As always with Module design make sure you check for filter conditions to access your module early in your code and if a filter fails immediately exit - always minimize the code that runs if your module doesn't need to process the request.
To recap here are the important things to remember about runAllManagedModulesForAllRequests:
- If runAllManagedModulesForAllRequests="true", all modules - regardless of their preCondition attribute setting fire on all requests. This also true for Application_XXXX events implement on the HttpApplication
- runAllManagedModulesForAllRequests="false" has no effect of keeping unmanaged requests from hitting modules, *unless* preCondition="managedHandler" is set
- runAllManagedModulesForAllRequests="false" does affect Application_XXXX events, causing those events to only fire on managed requests then. IOW, Application_XXXX behaves as if the 'module' implementation had a preCondition="managedHandler"