IIS7. Why Global Managed Modules are Disallowed

Ok, I have been pretty busy the past 10 days with both work and life, and I have not had a chance to post any blog entries. While I cannot say that I am any less busy now, I definitely have a lot of catchup and posts to make... so here goes.

This question was originally posted in the microsoft.public.platformsdk.internet.server.isapi-dev newsgroup on the msnews.microsoft.com NNTP server. Since all the technical previews at events like TechEd and PDC will be high-level concepts/details, the astute reader will always have some unanswered questions.

Now, while I cannot promise that I will answer everything about IIS7, nor can I promise that my answers will forever be correct (remember, we are talking about IIS7, an unreleased beta product), I will try to give as much currently-correct technical information as I can.

Question:

Hi!

I know this is kinda not the right group for this question, but if you know one that will be better suited, please let me know... That aside, though:

I have written a couple of ISAPI filters for our company, and they work just fine. Seeing the changes being made in IIS7, though, I thought I better get familiar with HTTPModules :-). So I just wrote an HTTPModule for IIS 6, using C#. This of course utilizes the old (current) architecture, and I'm somewhat confused about how the ISAPI callback functions "map" to the HTTPModule members.

In my current ISAPI filter, I reserve a piece of memory in GetFilterVersion, let's say 5 MB. GetFilterVersion was called on every load of the filter, so once per worker process; this means I have [#w3wp.exe} times 5 MB allocated. If I understand the HTTPModule structure right, the best match equivalent to GetFilterVersion would be the constructor, or possibly the Init() member, so the logical thing to do would be to "allocate" (as far as .NET lets me) my memory there now. There's quita a problem with this though...

As I saw it, the constructor of my module will be called once for every ASP.NET application that is being requested, and this gives me some headaches. In my case, we have about 50 worker process on the server, serving about 2500 websites. Each website has at least one appliaction, possibly more. Of course they're not all running at the same time, but still: We have a whole lot more of applications than we have worker processes. Let's just say that 800 ASP.NET appliactions are running at the time, so this would mean I'd end up with using 800 x 5 MB of memory. Definitely not desirable... The fact that I can't deliberately free my memory makes this even worse, but that's a problem I always struggle with in C# ;-)

So, is this "once per application" problem that I have now just an ASP.NET thing, because the module still lives inside the aspnet_isapi.dll? Will the behaviour be different in IIS7, and will there be a place that's only called once per w3wp.exe startup?

Thanks in advance,

Answer:

Since IIS7 is not available publicly and the API is beta (i.e. it is subject to change, and I know that it must have changed significantly since your point of reference...), I really would not spend TOO much time thinking about the details yet...  ;-) But since you are interested, this is a perfectly fine forum to ask.

Based on your line of questioning, the main points I want to emphasize are that:

  1. IIS7 functionality can be extended using native and managed code.
  2. Functionally speaking, neither extensibility point is a strict subset of the other. They each have abilities that are lacking in the other. They also share a lot of common functionality of the IIS server core.
  3. There is no direct mapping between any the following "legacy" concepts of ISAPI Filter, ISAPI Extension, ScriptMap Engine, HttpModule, and HttpHandler with the new IIS7 concepts of Global Modules, Modules (i.e. per-app capability selection list), and Handlers, though there are many conceptual similarities.
  4. Consequently, programming language remains non-negotiable. You need to choose the abilities that you need, then choose the necessary extensibility point (native code or managed code), and finally the programming language necessary for the task.

For example, I would not assume that things in IIS6 such as ISAPI Filter, ISAPI Extension, and CGI automatically transform into an HttpModule or HttpHandler in IIS7 because they definitely do not. If you think about it a little, you should realize that something like a global ISAPI Filter which triggers on every single request can never map to an HttpModule. Security considerations alone should be reason enough.

Now, in your case, I think your design requires a native code global module because it has the behavior that you need. Here is the crux of what is going on:

Conceptually, you can view IIS7 as a componentization of the IIS6 server core accomplished by:

  1. Introducing a single, new C-based API which uses pure native code. Code which implements this API are called global modules.
  2. Architecting a new server core engine which consumes .config configuration and exposes the new API from #1 as native extensibility mechanism.
  3. Rewiring all IIS6 features in terms of the new API from #1. This includes legacy extensibility mechanisms like ISAPI Extension, ISAPI Filter, and CGI as well as basic server behavior like Anonymous/Basic/Integrated Authentication, Default Document/Directory Browsing, Static File Handler, Custom Error Handler, etc. Presently, IIS7 consists of about 40+ such global modules.
  4. Rewire ASP.Net in terms of the new API from #1. Basically, move the .Net AppDomain activation code from ASPNET_ISAPI.DLL into a new global module that directly implements the new API from #1 and bypasses the entire ISAPI Extension legacy compat layer of #3. The resulting ASP.Net implementation still exposes the HttpHandler and HttpModule interfaces but has deeper access to the IIS7 request pipeline since it shims directly to the native global module instead of indirectly through an ISAPI extension API of IIS6.

At this point, the astute reader should be wondering why global modules MUST be in native code and not managed code. After all, the managed code in an HttpModule now has direct access to the native IIS7 request pipeline through the ASP.Net global module "shim" that is provided out of the box, and since the global module can execute managed code at any point, it *should* be able to run an HttpModule "globally".

Well, yes, that idea is possible, but after long design considerations, we decided to stick with the ASP.Net paradigm of application isolation through AppDomains for managed applications, so there is no such thing as "global" managed code. The security, performance, and complexity ramifications of global managed modules were just too problematic. For example, here are some of our thoughts and counterpoints:

  • Suppose IIS7 hosts only one AppDomain, the "global" AppDomain, for all managed applications so that defined HttpModules and HttpHandlers are "global" by definition. This violates the security model of every current IIS server that has >1 ASP.Net app and is not scalable nor performant.
  • Suppose IIS7 hosts multiple AppDomains and a single "global" AppDomain where global HttpModules and HttpHandlers are loaded. Now, this AppDomain is located in some dedicated process, which means that all requests filtered by global modules in managed code requires a double hop (a la IIS5 process model) and makes the dedicated process a singular point of failure, destroying the IIS Worker Process Model.
  • Suppose IIS7 hosts multiple "global" AppDomains which are distributed per Application Pool. Now, how should the supposedly singular "global HttpModule" share copies of its data structure between all processes of all Application Pools, keep them synchronized without any user intervention (remember, HttpModule authors have no idea of the process model ramifications and would not be locking anything), and do this automatically, performantly, and securely? Is it even desirable?

In the end, we decided that the implementation cost, performance penalty, and the resulting security ramifications were simply too complex. There are good situations for both native and managed modules, and we attempted to strike a reasonable balance between functionality and ease. Allowing managed code a chance to manipulate the incoming request URL/headers, participate in request authentication/authorization inline, and perform caching/logging logic cover most users' needs. Besides, we want global module developers to be a little more "responsible" since it carries huge consequences on server behavior... so native code serves to weed that out a bit. :-)

Now, if you have a bright idea on how to implement global managed modules in a secure, performant, reliable, and functional manner without destroying basic IIS tenets and remain easy to use, feel free to share your thoughts... :-) Until then, global managed modules are not allowed in IIS7.

//David