Extending IIS 7 Runtime Infrastructure and Management Components

 

Michael Jurek
Microsoft Corporation

July 2006

Updated February 2007

Applies to:
   Microsoft Internet Information Services 7.0
   Microsoft Windows Vista
   Microsoft Windows Server (Code Name "Longhorn")

Summary: This article demonstrates how you can leverage the modular architecture of Microsoft Internet Information Services 7.0 (IIS 7) to extend the functionality of the core Web server. In addition, it takes advantage of new management service and tool extensibility to create a compelling sample solution that increases the Web server feature set. (11 printed pages)

The solution has been tested on the RTM version of Microsoft Windows Vista.

Contents

Introduction
Scenario
Runtime Classes
Management Classes
Not Production Code!
Conclusion

Download the associated code sample, IIS7ExtensibilitySampleCode.exe.

Introduction

From a developer's perspective, previous versions of Microsoft Internet Information Services (IIS) were similar to monolithic black boxes. They worked only the way they were intended to work, although their behavior could be modified by way of a set of configuration settings saved in the metabase. To some extent, they could be extended by way of ISAPI filters and ISAPI extensions, native code mechanisms that are too difficult to be used by the average developer.

IIS 6 brought some modularization: IIS was split between user-mode application services and kernel-mode low-level services (such as I/O and caching). As a result, IIS 6 was much more robust and scalable. However, its extensibility remained unchanged.

IIS 7 provides total modularization of user-mode application services. Almost all aspects of IIS request processing have been extracted to separate modules. For example, you can find modules for caching, logging, authentication, and authorization. These modules can be easily added, removed, extended, or replaced by your own extension modules.

These modules can be written in either native or managed code. Although some niche scenarios require native code only, the majority of tasks can be achieved in managed code, too. Managed-code developers will be pleased to know that managed-code extensibility uses the same object model they know from ASP.NET programming: Modules use the IHttpModule interface, handlers use the IHttpHandler interface, both work with the HttpContext class, and so on.

Because much of the functionality has been moved to modules, the role of the IIS 7 core has been largely reduced. It is a very lightweight pipeline processing engine with a relatively simple task. At server startup, it reads configuration files and starts monitoring changes in these files. As a new request arrives, it proceeds as follows (see Figure 1 for a simplified illustration):

  • It constructs the HttpContext class that the modules will be working with. HttpContext is a starting point providing access to request, response, headers, cookies, and other entities.
  • It instantiates modules listed in configuration files and calls their Init method.
  • During the Init call, modules can register hooks for various events in the processing pipeline.
  • The engine starts firing events early in the processing pipeline—authentication, authorization, resolving cache, and so on. Modules that have registered for these events are given the chance to do whatever they want.
  • The pipeline enters the ExecuteHandler stage. The correct request handler selected by the request path and HTTP verb is executed (its Execute method is called). The handler should perform the actual work based on the request data and prepare the response for the client.
  • The engine continues firing events late in the processing pipeline—updating cache, logging, and so on. Modules that have registered for these events are given the chance to do whatever they want.
  • The response stream is handed to kernel-level I/O components and processing ends.

This modularization offers tremendous opportunities for extensibility. It is also a key enabler for other IIS 7 improvements, such as a clean and well-factored configuration system or increased security due to a reduction of the attack surface.

Click here for larger image

Figure 1. Simplified schema of request processing pipeline (Click on the picture for a larger image)

Scenario

We want to dynamically add copyright information, the download date, or other text to pictures served by our Web server, as demonstrated in Figure 2.

Click here for larger image

Figure 2. Image-copyright module in action (Click on the picture for a larger image)

We will use managed code for developing the custom handler. As part of the sample, we will also develop a server-side management module and client-side plug-in for IIS Manager. The sample will use the standard configuration mechanism of .NET Framework and IIS 7.

The complete solution code, including installation instructions, is available for download here.

Runtime Classes

Runtime classes are used by the Web server itself. Our sample has three configuration classes and one handler class.

Configuration Classes

Our sample uses the standard .NET Framework and IIS 7 configuration system. The simple configuration file for our Web site is as follows:

<?xml version="1.0"?>
<configuration>
   <system.webServer>
      <imageCopyright enabled="true" cacheDuration="10">
         <labels>
            <add name="copyright" text="(c) Michael Jurek"
includeDate="true" top="-50" left="5" fontName="Tahoma" fontSize="35"
foreColor="yellow" opacity="100"/>
            <add name="watermark" text="DEMO" includeDate="false"
top="200" left="400" fontName="Arial" fontSize="300" foreColor="Blue"
opacity="15" />
         </labels>
      </imageCopyright>
   </system.webServer>
</configuration>

The configuration defines the basic settings that enable module functionality and govern caching, followed by the definitions of individual labels. Each label specifies the name, content (the text and whether the current date should be included), position (top and left coordinates), and font (family, size, foreground color and opacity).

For the core IIS 7 configuration system to run correctly, the schema of all configuration sections must be described in an XML file placed into the %SystemRoot%\system32\inetsrv\config\schema folder (note that this is custom XML-based description format, similar to XSD schema).

<configSchema>
   <sectionSchema name="system.webServer/imageCopyright">
      <attribute name="enabled" type="bool" defaultValue="false"/>
      <attribute name="cacheDuration" type="int" defaultValue="3600"/>
      <element name="labels">
         <collection addElement="add" removeElement="remove"
         <clearElement="clear">
            <attribute name="name" required="true" isUniqueKey="true" 
type="string" validationType="nonEmptyString"/>
            <attribute name="text" required="true" type="string"/>
            <attribute name="includeDate" 
type="bool"defaultValue="false"/>
            <attribute name="top" required="true" type="int"/>
            <attribute name="left" required="true" type="int"/>
            <attribute name="fontName" type="string" 
defaultValue="Arial"/>
            <attribute name="fontSize" type="int" defaultValue="50"/>
            <attribute name="foreColor" type="string" 
defaultValue="yellow"/>
            <attribute name="opacity" type="int" defaultValue="100"/>
         </collection>
      </element>
   </sectionSchema>
</configSchema>

In addition, if we want convenient access to configuration settings from managed code, we have to define three managed classes: for the configuration section itself, for the collection of labels, and for an individual label. All three classes follow the same pattern: They inherit from abstract base classes in the Microsoft.Web.Administration namespace and provide strongly typed access to configuration entities. The source code of ImageCopyrightSection class serves as a good illustration:

public class ImageCopyrightSection : ConfigurationSection
{
   private LabelsCollection _labels;

   public int CacheDuration
   {
      get { return (int)GetAttribute("cacheDuration").Value; }
      set { GetAttribute("cacheDuration").Value = value; }
   }
  
   public bool Enabled
   {
      get {return (bool) GetAttribute("enabled").Value; }
      set {GetAttribute("enabled").Value=value;}
   }
     
   public LabelsCollection Labels
   {
      get
      {
         if (this._labels == null)
         {
            this._labels = 
(LabelsCollection)base.GetCollection("labels", 
typeof(LabelsCollection));
         }
         return this._labels;
      }
   }
}

For these classes to work correctly, the section must be registered in the <configSections> part of either the applicationHost.config or web.config file. The former is preferable, on balance, because it is machine-wide and immediately applies to all Web sites:

<sectionGroup name="system.webServer" ... >
   ...       
   <section name="imageCopyright" 
type="ImageCopyrightService.Configuration.ImageCopyrightSection,
   ImageCopyrightService,Version=1.0.0.0, Culture=neutral, 
PublicKeyToken=31957db4a69d47bf"/>
</sectionGroup>

HTTP Handler

If you have ever written HTTP handler for ASP.NET 1.1 or 2.0, you will already be familiar with this class. Here, the concept of the HTTP modules and handlers has been extended from ASP.NET ISAPI application to the whole IIS 7 server.

HTTP handler invoked in the ExecuteHandler pipeline stage usually performs the actual work, although this is more or less a convention; you could do the same in other pipeline stages, too. There is a list of handlers defined in the config files. Each handler in the list has an associated path and HTTP verb. During handler mapping, each handler registration is checked against the request data; once the match is found, the handler is chosen. Therefore, it is necessary to register our handler for *.jpg files in the <handlers> section of applicationHost.config before the StaticFile handler that has wildcard mapping for all requests:

<add name="ImageCopyrightHandler" path="*.jpg" verb="GET"
type="ImageCopyrightService.ImageCopyrightHandler,
ImageCopyrightService, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=31957db4a69d47bf"preCondition="integratedMode"/>
<add name="StaticFile" path="*" verb="*" 
modules="StaticFileModule,DefaultDocumentModule,
DirectoryListingModule" ResourceType="Either" requireAccess="Read" />

HTTP handler is a .NET class that implements the IHttpHandler interface. It must implement IsReusable, which is a property that determines whether this handler instance may be reused in subsequent requests. ProcessRequest is the core method that receives reference to HttpContext and is expected to produce a meaningful HttpResponse, based on data in HttpRequest. In this case, ProcessRequest extracts necessary file information, reads in the image file, modifies the bitmap using GDI+ graphics library, sets caching policy, and sends the modified bitmap to the response stream. The code fragment looks like the following:

public class ImageCopyrightHandler : IHttpHandler
{
...

public void ProcessRequest(HttpContext context)
{
      // Get configuration information
      ImageCopyrightSection section = 
GetImageCopyrightSection(context);
      // Create modified bitmap
      Bitmap bitmap = new Bitmap(context.Request.PhysicalPath);
      if (section.Enabled && (section.Labels.Count > 0))
      {
         Graphics graphics = Graphics.FromImage(bitmap);
         foreach (LabelElement label in section.Labels)
            ModifyImage(bitmap, graphics, label);
         graphics.Dispose();
      }
      //Ensure correct caching behavior
      if (section.CacheDuration > 0)
      {
         HttpCachePolicy cachePolicy = context.Response.Cache;
         cachePolicy.SetExpires(System.DateTime.Now.AddSeconds(section.
CacheDuration));
         cachePolicy.SetCacheability(HttpCacheability.ServerAndPrivate);
         cachePolicy.SetValidUntilExpires(true);
      }
      //Send to client
      context.Response.ContentType = "image/jpeg";
      bitmap.Save(context.Response.OutputStream, ImageFormat.Jpeg);
      bitmap.Dispose();
}
}

Management Classes

Although our solution is now fully functional, it would not be complete without management features. In this part, we will write both server-side and client-side classes for achieving this goal.

Module Provider

The module provider is a very simple server-side class that provides basic configuration information about available management plug-ins. It must provide the name of the custom configuration section that will be managed, the friendly name of the added functionality, the type of the server-side service class, the definition of the client-side plug-in class, and the management scopes supported by our extension:

public class ImageCopyrightModuleProvider : ConfigurationModuleProvider
{
   protected override string ConfigurationSectionName
   {
      get { return "system.webServer/imageCopyright"; }
   }

   public override string FriendlyName
   {
      get { return Resources.FriendlyName; }
   

   public override Type ServiceType
   {
      get { return typeof(ImageCopyrightModuleService); }
   }
        
   public override ModuleDefinition 
GetModuleDefinition(ImanagementContext
   context)
   {
      return new ModuleDefinition(this.Name,
      "ImageCopyrightService.Management.Client.ImageCopyrightModule,
      ImageCopyrightService.Management.Client, Version=1.0.0.0,
      Culture=neutral, PublicKeyToken=31957db4a69d47bf");
   }

   public override bool SupportsScope(ManagementScope scope)
   {
      return true;
   }
}

Module providers are listed in the <moduleProviders> section of the administration.config file. The server-side administration modules actually used on the server or Web site are listed in the <modules> section, so we have to add the following entries to both sections:

<moduleProviders>
   ...
   <add name="ImageCopyright"
   type="ImageCopyrightService.Management.Server.
ImageCopyrightModuleProvider,
   ImageCopyrightService, Version=1.0.0.0, Culture=neutral,
      PublicKeyToken=31957db4a69d47bf" />
</moduleProviders>
<modules>
   ...
   <add name="ImageCopyright"/>
</modules>

After restarting IIS Manager, the client-side plug-in is loaded, based on the output of GetModuleDefinition method of our service provider.

Click here for larger image

Figure 3. IIS Manager, showing the Image Copyright extension (Click on the picture for a larger image)

Server-Side Service Module

The server-side service module is a class that exposes some of its methods to calls from the IIS Manager console. Methods that are accessible in this way are decorated by the ModuleServiceMethod attribute. They can be accessed either locally (the management console runs in the same operating system) or remotely, when the private data format is used to communicate over HTTP. A typical service method is a service-oriented facade for reading or manipulating server configuration files, but—theoretically—it can do whatever is needed. The module service inherits from the abstract ModuleService class, which offers a wealth of useful functionality. Writing a typical service method thus involves just a few lines of code:

[ModuleServiceMethod]
public void RemoveLabel(string name)
{
   LabelsCollection labels = 
GetLabelsCollection(ManagementUnit.Configuration);
   LabelElement label = labels[name];
   labels.Remove(label);
   ManagementUnit.Update();
}

Client-Side Module

The IIS Manager is an extensible Windows Forms application. When it connects to a Web server or site, it receives the list of client-side plug-in modules as returned by the GetModuleDefinition method of each server-side module provider. The corresponding assemblies must be available on a management client computer (preferably installed in GAC); they are not downloaded or installed.

The IIS Manager console then calls the Initialize method of each plug-in module that has a chance to register its extensions with the extensibility manager, most often by calling the RegisterPage method, which ensures that the feature will have its entry in the Properties window:

public class ImageCopyrightModule : Module
{
        
   protected override void Initialize(IServiceProvider serviceProvider,
   ModuleInfo moduleInfo)
   {
      base.Initialize(serviceProvider, moduleInfo);
      IControlPanel controlPanel =
      (IControlPanel)serviceProvider.GetService(typeof(IControlPanel));
      ModulePageInfo info = new ModulePageInfo(this,
      typeof(ImageCopyrightPage), Resources.Title, 
Resources.Description,
      null, Resources.FeatureImage, Resources.Description);
      controlPanel.RegisterPage(ControlPanelCategoryInfo.CommonHttp,
      info);
      controlPanel.RegisterPage(ControlPanelCategoryInfo.Iis, info);
   }
}

Developing the page is a relatively straightforward Windows Forms development exercise. To ensure consistent behavior and appearance, it is highly recommended not to use Windows Forms classes directly, but instead to inherit from abstract classes in the Microsoft.Web.Management.Client and Microsoft.Web.Management.Client.Win32 namespaces. There are many helpful classes here, some of which you can see in Figure 4.

Click here for larger image

Figure 4. Visual elements inherited from Microsoft.Web.Management.Client and Microsoft.Web.Management.Client.Win32 namespace classes (Click on the picture for a larger image)

Of course, your page has to call the server-side service module to get configuration data or submit changes. Fortunately, this is really easy because all the plumbing is again implemented in the abstract classes that you inherit from. Calling a service method is just a single line of code with a reflection-like syntax:

internal class ImageCopyrightModuleProxy : ModuleServiceProxy
{
   internal void EditSettings(bool enabled, int cacheDuration)
   {
      Invoke("EditSettings", new object[2] { enabled, cacheDuration });
   }
   
   internal string GetImageCopyrightConfiguration()
   {
      return (string)Invoke("GetImageCopyrightConfiguration", new 
object[0]);
   }
   ...
}

Not Production Code!

Let's be clear: This is a sample, not production code. It has not been extensively tested. Some things have been sacrificed for the sake of brevity. For example, there is very little error handling in the code; we assume here that everything will go well, which is not always the case. If, for instance, someone modifies the config file and deletes the label that you have been editing, you might be in trouble. Similarly, the handling of images does not validate the correct image-file format; and there are other, more or less likely situations that could arise.

You should also consider performance aspects. The performance penalty of using managed code instead of native code is surprisingly small. However, whatever kind of module/handler you use, it will always be significantly slower than the kernel-mode file system cache for static files. Even the kernel-mode URI cache has a lower performance and is not always available (see KB article Q817445, "Reasons content is not cached by HTTP.sys in kernel"). If performance is a top priority for you, note that batch generating modified static files won't incur any performance penalty, but it does cost much more to manage.

There are also many ways to enhance the solution. For example, you can add UI for ordering labels in IIS Manager. You could also make the GDI+ manipulation much smarter. Not much attention has been paid to testing this in different situations or in different file formats, as this was not the goal of this paper.

Conclusion

We have just finished a quick code walk-through of the IIS 7 extensibility sample. While it is far from perfect, it is useful and easy to understand. It demonstrates how modular architecture, transparent design, and clean configuration open new areas of extensibility that were impossible or difficult to achieve with previous versions of IIS. With IIS 7, every developer or IT professional can reduce or extend Web server functionality to fit their specific needs.