An End-to-End Extensibility Example for IIS 7.0 Developers

by Saad Ladki

IIS 7 and above are built with a completely modular architecture, on top of rich extensibility APIs. This enables developers to easily add, remove and even replace built-in IIS components with hand-crafted ones, specifically suited for any given Web site. It has never been so easy to plug code deep into the IIS core pipeline and extend IIS in ways that were impossible before.

To give some examples: a few lines of code allows developers to write modules or handlers that provide new authentication and authorization schemes, do run-time or security analysis of incoming request and inspect responses. But to provide a real value-add, these modules must be manageable via programming interfaces, command-line tools and a user interface.

This white paper is an end-to-end example of how to extend the IIS web server with a custom request handler. It shows how to add API and command-line support for the configuration of this handler and how to write a user interface module plugs into the IIS Management interface.

The solution has been tested on Windows Vista and Windows Server® 2008 Beta 3. It will be updated once the final version of Windows Server 2008 is available.

Feature Set

  • Managed handler inserts a copyright message into image files
  • Copyright message feature is configuration driven and uses the new IIS configuration system
  • Configuration can be schematized and made accessible to configuration APIs, WMI scripting and IIS command-line tools
  • User Interface Extension Module allows configuration of copyright message feature through the IIS user interface

Prerequisites

To follow the steps in this document, the following software must be installed:

ASP.NET

Install ASP.NET via the Windows Vista Control Panel. Select "Programs" - "Turn on or off Windows features". Then open "Internet Information Services" - "World Wide Web Services" - Application Development Features" and check "ASP.NET.

If you have a Windows Server 2008 build. open "Server Manager" - "Manage Roles" and select "Web Server (IIS)". Click "Add role services". Under "Application Development" check "ASP.NET".

You must also install "IIS Management Scripts and Tools" to take advantage of the WMI extensibility in IIS. To do this, select "Programs" - "Turn on or off Windows features". Then open "Internet Information Services" - "Web Management Tools" and check " IIS Management Scripts and Tools".

If you have a Windows Server 2008 build, open "Server Manager" - "Roles" and select "Web Server (IIS)". Click "Add role services". Under "Web Management Tools" check " IIS Management Scripts and Tools ".

Visual C# Express Edition or Visual Studio 2005

For the User Interface Module, you need a C# Development Tool. If you do not have a copy of Visual Studio 2005, download Visual Studio for free.

Dealing With User Account Control Issues

Windows Vista User Account Protection removes the Administrator privileges from your access token. By default, you will not have access to IIS configuration and content locations. To fix this problem, we recommend going through this article by using an elevated command prompt.

To start an elevated command prompt, go to the "Start" menu, click "All Programs" - "Accessories". Right click "Command Prompt" and click "Run as administrator". Confirm the elevation prompt.

Scenario

The following example dynamically decorates images served by our web server with copyright information in the lower left corner as seen in Figure 1.

Screenshot of the web page displaying an image of snow covered rocky mountains on the backdrop of a cloudy sky.
Figure 1: Image Copyright Module in Action

We use managed code for developing the handler that decorates the images. As part of the sample, we also specify the configuration for this handler and store it in the IIS configuration store. Last, we will develop a User Interface plug-in for IIS Manager.

The IIS configuration store can be extended by simply copying a schema file into the IIS schema directory. The schema declares the name of the new configuration section and its attributes, types and default values. For our example, we declare a new configuration section called imageCopyright. It lives within the system.webServer configuration group. Its properties are:

  • A Boolean flag that enables or disables the imageCopyright functionality
  • A string attribute containing the copyright message
  • A color attribute specifying the color of the copyright message

Schema Declaration

Save the following schema definition as imagecopyright.xml in %windir%\system32\inetsrv\config\schema:

<configSchema>
    <sectionSchema name="system.webServer/imageCopyright">
        <attribute name="enabled" type="bool" defaultValue="false" />
        <attribute name="message" type="string" defaultValue="Your Copyright Message" />
        <attribute name="color" type="string" defaultValue="Red"/> 
   </sectionSchema>
</configSchema>

If you get an "access denied" message, you did not do this from the elevated command prompt. Once the schema file is added, the schema needs to be declared in the applicationhost.config file. Add the following XML to %windir%\system32\inetsrv\config\applicationhost.config

<configSections>
...
<sectionGroup name="system.webServer">
<section name="imageCopyright"  overrideModeDefault="Allow"/>
...    
</sectionGroup>
</configSections>

Configure It

The process is complete. You can set the new configuration settings via the command-line or directly within applicationhost.config or web.config. Try it. Open a command shell and enter the following:

<system.webServer>
    <imageCopyright />
</system.webServer>

The output shows the configuration section was recognized, with its default configuration:

%windir%\system32\inetsrv\appcmd set config -section:system.webServer/imageCopyright 

/color:yellow /message:"Copyright (C) Contoso.COM" /enabled:true

Now add your configuration settings via appcmd.exe, e.g.

%windir%\system32\inetsrv\appcmd set config -section:system.webServer/imageCopyright 

/color:yellow /message:"Copyright (C) Contoso.COM" /enabled:true

Check to see if the configuration was saved by running:

%windir%\system32\inetsrv\appcmd list config -section:system.webServer/imageCopyright

See the saved configuration:

<system.webServer> 
    <imageCopyright enabled="true" message="Copyright (C) Contoso.COM" color="yellow" />
</system.webServer>

Make imageCopyright Configuration Scriptable

Note

Making the imageCopyright handler configuration available for WMI scripting is optional. You can go directly to "Step 2 – Core Extensibility: The Image Copyright Handler" without affecting the remaining steps.

To make the imageCopyright handler configuration available for WMI scripts, complete the following steps:

  • Installation of IIS WMI support
  • Creating the imageCopyright.mof file
  • Including the imageCopyright.mof file into webadministration.mof and compilation of the WMI schema files
  • Writing and executing the script

Installation of IIS WMI support

The default install of IIS does not include the WMI scripting components. You must add them.

Installation of WMI Support on Vista Client SKUs

Install "IIS Management Scripts and Tools" via the Windows Vista Control Panel. Select "Programs" - "Turn on or off Windows features". Then open "Internet Information Services" - "Web Management Tools" and check "IIS Management Scripts and Tools".

Installation of WMI Support on Windows Server 2008 SKUs

If you have a Windows Server 2008 build, open "Server Manager" - "Roles" and select "Web Server (IIS)". Click "Add role services". Under "Management Tools" check "IIS Management Scripts and Tools".

Creating the imageCopyright.mof File

The schema declaration of WMI properties is very similar to the schema declaration of IIS properties in the previous step. WMI schemas are declared in .mof files and are compiled by a tool called mofcomp. Mofcomp adds the schema declaration to the WMI repository.

Tasks to Add the Schema Information

Open a notepad instance and copy the following lines into it:

#pragma AUTORECOVER
#pragma namespace("\\\\.\\Root\\WebAdministration")
[            
    dynamic : ToInstance ToSubClass,
    provider("WebAdministrationProvider") : ToInstance ToSubClass,
    Description("imageCopyright Section") : ToSubClass,
    Locale(1033) : ToInstance ToSubClass,
    factory_clsid("{901a70b2-0f7a-44ea-b97b-1e9299dec8ca}"),
    section_path("system.webServer/imageCopyright"),
    SupportsUpdate
]
 
class imageCopyright : ConfigurationSection
{      
    [
        read: ToSubClass ToInstance,
        write: ToSubClass ToInstance,
        DefaultValue("False"): ToSubClass ToInstance,
        Description("To be written"): ToSubClass ToInstance
    ]
    boolean Enabled;
  
    [
        read: ToSubClass ToInstance,
        write: ToSubClass ToInstance,
        DefaultValue("Your Copyright Message"): ToSubClass ToInstance,
        Description("Copyright Message"): ToSubClass ToInstance
    ]
    string Message;

    [
        read: ToSubClass ToInstance,
        write: ToSubClass ToInstance,
        DefaultValue("Yellow"): ToSubClass ToInstance,
        Description("Color of Copyright Message"): ToSubClass ToInstance
    ]
    string Color;
};

The schema declaration contains the same entries as imageCopyright.xml in the previous step, namely the name and type of the configuration setting and its default value. Save the file as %windir%\system32\inetsrv\imageCopyright.mof.

Compilation of WMI Schema Files

Compile imageCopyright.mof by executing the following command

mofcomp webadministration.mof

The WMI Script

Mofcomp added the imageCopyright schema to the WMI repository. Set IIS configuration settings by scripting the IIS WMI provider. Here is an example:

Tasks

Open an instance of NOTEPAD and copy the following lines into it. Save the file as SetCopyrightConfig.vbs:

Set oIIS = GetObject("winmgmts:root\WebAdministration")        
Set oSection = oIIS.Get("ImageCopyright.Path='MACHINE/WEBROOT/APPHOST/Default Web Site',Location=''")
oSection.Enabled = true
oSection.Message = "Copyright (C) IIS7 Team - Date: " & date
oSection.Color = "White"
oSection.Put_

This is a standard WMI script that connects to the IIS WMI provider. It gets the configuration section at the specified location ("Default Web Site") and changes its values. The Put_ call will save the changes to disk.

If you execute the script, it adds the copyright message with the current date into %systemdrive%\inetpub\wwwroot\web.config. Have a look.

Next, add the image copyright handler itself.

A handler is a piece of code that gets executed when the request matches a certain pattern, usually a file extension. Requests ending with .ASP are mapped to ASP.DLL, for example. In IIS 6.0, you had to write an ISAPI extension to handle requests with certain file extension. ASP.NET also allowed handling file extensions, but only if you mapped the request to ASP.NET first. In IIS, you can handle arbitrary file extensions without involving ASP.NET. In our example, we handle requests with the extension .JPG. Here is how to do it:

Creating the Content Directory

Create a content directory, for example c:\inetpub\mypictures, and copy some digital pictures of your choice into it. Make sure these files are image files with the extension .JPG.

Note

For simplicity purposes, the code examples shown here do not include error handling code for files that are not image files.

Create a sub directory called App_Code underneath your new directory: for example, c:\inetpub\mypictures\App\_Code.

Create the mypictures Application

You can create an application that points to c:\inetpub\mypictures via the IIS Management console, but there are more interesting ways to do it. Create a new application via appcmd. The following command creates an app called "mypictures" on the "Default Web Site" with the physical path c:\inetpub\mypictures:

%windir%\system32\inetsrv\appcmd add app -site.name:"Default Web Site"

-path:/mypictures -physicalPath:%systemdrive%\inetpub\mypictures

Because we want to see the JPG files copied into this directory, enable directory browsing. Do this via the IIS Management Console, or resort to a more interesting method and use appcmd. Here is how to set directory browsing to true via appcmd:

%windir%\system32\inetsrv\appcmd set config "Default Web Site/mypictures"

 -section:directoryBrowse -enabled:true

If you request http://localhost/mypictures, you see a directory listing with your pictures.

Time to Write Code

Now write the actual image handling code. Write a few lines of C# code and you have the result: use the code below as a reference and save it as imagecopyrighthandler.cs in your App_Code directory, for example c:\inetpub\mypictures\App\_Code\imagecopyrighthandler.cs.

#region Using directives
using System;
using System.Web;
using System.Drawing;
using System.Drawing.Imaging;
using Microsoft.Web.Administration;
#endregion
  
namespace IIS7Demos
{
    public class imageCopyrightHandler : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            ConfigurationSection imageCopyrightHandlerSection = 
                WebConfigurationManager.GetSection("system.webServer/imageCopyright");
  
            HandleImage(    context,
                            (bool)imageCopyrightHandlerSection.Attributes["enabled"].Value,
                            (string)imageCopyrightHandlerSection.Attributes["message"].Value,
                            (string)imageCopyrightHandlerSection.Attributes["color"].Value                            
                        );
        }
  
        void HandleImage(   HttpContext context,
                            bool enabled,
                            string copyrightText,
                            string color
                        )           
        {
            try
            {
                string strPath = context.Request.PhysicalPath;
                if (enabled)
                {
                    Bitmap bitmap = new Bitmap(strPath);
                    // add copyright message
                    Graphics g = Graphics.FromImage(bitmap);
                    Font f = new Font("Arial", 50, GraphicsUnit.Pixel);
                    SolidBrush sb = new SolidBrush(Color.FromName(color));
                    g.DrawString(   copyrightText,
                                    f,
                                    sb,
                                    5,
                                    bitmap.Height - f.Height - 5
                                );
                    f.Dispose();
                    g.Dispose();
                    // slow, but good looking resize for large images
                    context.Response.ContentType = "image/jpeg";
                    bitmap.Save(
                                        context.Response.OutputStream,
                                        System.Drawing.Imaging.ImageFormat.Jpeg
                                     );
                    bitmap.Dispose();
                }
                else
                {
                    context.Response.WriteFile(strPath);
                }
            }
            catch (Exception e)
            {
                context.Response.Write(e.Message);
            }
        }
  
        public bool IsReusable
        {
            get { return true; }
        }
    }
}

The above code does the following:

  • Reads the configuration
  • Calls HandleImage

HandleImage does the following:

  • Creates a Graphics object from the bitmap
  • Creates a font object using the configured values
  • Draws the message into the bitmap

To use the Microsoft.Web.Administration class, you must add the reference to the IIS Administration API assembly. To do this, open %systemdrive%\inetpub\mypictures\web.config and add the following entries:

<system.web>
    <compilation>
      <assemblies>
        <add assembly="Microsoft.Web.Administration, Version=7.0.0.0, 
Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"/>
      </assemblies>
    </compilation>
</system.web>

You can also compile the handler into an assembly and put it into the mypictures/bin. If you do this, you do not have to add the Microsoft.Web.Administration assembly to your web.config file.

Handler Configuration

You only have to tell IIS to invoke your new handler if a .JPG file is requested. Do this via the IIS Management Console or you use appcmd:

appcmd set config "Default Web Site/mypictures/" -section:handlers 

/+[name='JPG-imageCopyrightHandler',path='*.jpg',verb='GET',type='IIS7Demos.imageCopyrightHandler']

The appcmd command above configures the new handler in the /mypictures directory only. Because handler entries are in a collection, you have to use the +[] syntax. This syntax is always used when add elements must be added to collections. The elements of the handler configuration are:

name

Can be any unique name. The name is only used to uniquely identify the handler.

path

Tells IIS when to execute this handler. *.JPG tells IIS to execute this handler for all files ending in .JPG. If you use foo*.JPG as a path, only JPG files starting with foo are executed by this handler.

verb

Comma-separates list of http verbs that have to match to execute this handler. In our case, we only want to execute the request when a GET request comes in.

type

The managed type of the class that should be executed when the request matches. It is comprised of the namespace and the IHttpHandler derived class in your App_Code directory.

One Last Note

Before you start testing the copyrighted images, make sure that the IIS worker process which executes the request picks up the schema changes you made. It is possible that the worker process was already running when you added the imageCopyright.xml file to the schema directory. If this happens, you get a configuration exception in imagecopyrightconfig.cs. The author encountered into this issue when writing this article and it cost quite a bit of time.

Simply recycling the Application Pool solves this problem:

appcmd recycle AppPool DefaultAppPool

The process is complete. If you now request http://localhost/mypictures/<imageOfYourChoice>.jpg), you see the copyright message.

Options:

  • You can change the copyright message options via appcmd or by directly editing the web.config file
  • You can map the imageCopyright handler to other image types, e.g. BMP or GIF by adding the same handler for a different extension. Example:
appcmd set config "Default Web Site/mypictures/" -section:handlers /+[name='BMP-imageCopyrightHandler',path='*.bmp',verb='GET',type='IIS7Demos.imageCopyrightHandler']

Time for the finishing touches. We already extended the IIS core server with a few lines of code; we extended the IIS configuration system with no code at all and got command-line support for free. Now to configure our imageCopyright handler via the IIS Management Console.

We do this via the following tasks:

  • Creating the project in Microsoft Visual Studio or Microsoft Visual C# Express so that the assembly can be used inside the IIS Management Console
  • Creating a module provider
  • Creating a module that reads and sets imageCopyright properties.

Creating the Project

To create an extensibility module for InetMgr you need to create a DLL project also known as a Class Library project. This DLL needs to be strongly named so that it can be registered in the GAC (Global Assembly Cache) which is a requirement for Modules that are used by the IIS Management Console.

Steps

  1. Click Start, click Programs and run Microsoft Visual Studio 2005 or Microsoft Visual C# 2005 Express Edition.

  2. In the File Menu select the option New Project.

  3. In the New Project dialog select Class Library as the project type and type imageCopyrightUI as the name of the project and click OK.

    Screenshot of New Project dialog box with Class Library selected and image Copyright U I entered in the Name filed as the name of the project.
    Figure 2: New Project Dialog

  4. Remove the file Class1.cs that was added by default since we will not be using that.

  5. Using the Add New Reference option from the Project Menu, add a reference to Microsoft.Web.Management.dll located in the \Windows\system32\inetsrv directory. This is the DLL that contains all the extensibility classes needed for creating modules for the IIS Management Console.

  6. Using the Add New Reference option from the Project Menu, add a reference to Microsoft.Web.Administration.dll located in the \Windows\system32\inetsrv directory. This is the DLL that contains all the configuration classes needed for reading configuration writing IIS configuration.

  7. Since we will be using code to create UI based on WinForms, we also want to add a reference to System.Windows.Forms.dll; for that again using the Add New Reference option from the Project Menu, select System.Windows.Forms.dll and System.Web.dll in the.NET list of assemblies.

  8. One of the requirements for libraries to be used inside InetMgr is that they need to be registered inside the GAC. For that, we need to make sure our DLL is strongly named (sometimes referred as Signed). Visual Studio offers an easy way to create new names and to select one for the project, so for that, using the Project menu select the option imageCopyrightUI Properties.

  9. In the Signing Tab check the Sign the assembly.

  10. In the Create Strong Name Key type imageCopyrightUI as the name for the key and uncheck the Protect my key file with a password check box. Click OK.

    Screenshot of Create Strong Name Key dialog box displaying image Copyright U I entered as Key file name and password created and confirmed.
    Figure 3: Create Strong Name Dialog

    The signing tab displays:

    Screenshot of Signing tab with image Copyright U I dot s n k selected in the Choose a strong name key file field.
    Figure 4: VS Project Signing Tab

  11. Since we want the assembly to be in the GAC, we will add some Post-build events so that it automatically gets added to the GAC every time we compile. This will make it really straight forward to debug and make changes as we add new functionality. For this, select the Build Events tab and add the following Post-build event command line:

    call "%VS80COMNTOOLS%\vsvars32.bat" > NULL
    
    gacutil.exe /if "$(TargetPath)"
    

    Screenshot of Post Build Event command line populated with code.
    Figure 5: VS Post-Build Events Tab

    (Optional) If you are using Microsoft Visual Studio 2005 (this will not work with the Visual C# Express Edition), setup debugging correctly to use F5 to run your code. To do this, go to the project properties, select Debug tab and set it to start an external program choosing \windows\system32\inetsrv\inetmgr.exe

    Screenshot of Debug tab set to Start external program action.
    Figure 6: Debugging Tab

  12. Finally, close the project properties, select the option Save All in the File Menu and click OK.

    Now compile the project using Build Solution under Build menu. This automatically builds the DLL and adds it to the GAC.

Creating the Module Provider

The IIS user interface is as customizable and modular as the IIS core server and the IIS configuration system. The IIS user interface is a set of feature modules that can be removed or replaced. The entry point for each UI module is a module provider. A list of all module providers can be found in %windir%\system32\inetsrv\Administration.config in the <modules> section.

As a first step, create the imageCopyrightUI module provider.

Steps

  1. Select the option Add New Item from the Project Menu. In the Add New Item dialog select the Class template and type imageCopyrightUIModuleProvider.cs as the name for the file.

    Screenshot of Add New Item dialog box with Class template selected and the Name field populated with image Copyright U I Module Provider dot c s.
    Figure 7: Add New Item Dialog

  2. Change the code so that it looks as follows:

    using System;
    using System.Security;
    using Microsoft.Web.Management.Server;
        
    namespace IIS7Demos           
    {
        class imageCopyrightUIProvider : ModuleProvider
        {
            public override Type ServiceType              
            {
                get { return null; }
            }
    
            public override ModuleDefinition GetModuleDefinition(IManagementContext context)
            {
                return new ModuleDefinition(Name, typeof(imageCopyrightUI).AssemblyQualifiedName);
            }
    
            public override bool SupportsScope(ManagementScope scope)
            {
                return true;
            }
        }            
    }
    

    This code creates a ModuleProvider that supports all types of scopes (Server, Site and Application) and registers a client-side Module called imageCopyrightUI. To only show your module on the application level, the SupportsScope function looks like this:

    public override bool SupportsScope(ManagementScope scope)
    {
        return (scope == ManagementScope.Application) ;
    }
    

Creating the UI Module

A Module is the main entry point in the client for all extensibility objects. It has one main method called Initialize. This is the method where all the action takes place.

Steps

  1. Select the option Add New Item in the Project Menu.

  2. Select the Class template and type imageCopyrightUI.cs as the file name. Change the code so that it looks as follows:

    using System;
    using System.Windows.Forms;
    using Microsoft.Web.Management.Client;
    using Microsoft.Web.Management.Server;
    
    namespace IIS7Demos
    {
        internal class imageCopyrightUI : Module
        {
            protected override void Initialize(IServiceProvider serviceProvider, ModuleInfo moduleInfo)
            {
                base.Initialize(serviceProvider, moduleInfo);
                IControlPanel controlPanel = (IControlPanel)GetService(typeof(IControlPanel));
                ModulePageInfo modulePageInfo = new ModulePageInfo(this, typeof(imageCopyrightUIPage), "Image Copyright", "Image Copyright");
                controlPanel.RegisterPage(modulePageInfo);
            }
        }              
    }
    

    In the above code, we specify the Text of the entry in the list of UI Modules and the type of a single page that should be displayed when a user clicks this text.

All that is left is to write the page itself.

Creating the Module Page

In this task, you create the most basic Module Page. ModulePage is the base class provided by the framework to create a new user interface. There are four different classes that are provided by the framework that are helpful, depending on the scenario you are trying to build.

  • ModulePage. This base class offers only the most basic services, and offers no special user interface at all. None of the features that are included in InetMgr derives from this class directly.
  • ModuleDialogPage. This base class offers similar semantics as a dialog, including an Apply and Cancel links in the task list and offer specific methods you can override to handle this common tasks. It also handles things like Refresh and other functions automatically. Samples of features that derive from this page include Machine Key, Management Service, etc.
  • ModulePropertiesPage. This base class offers a UI similar to the Visual Studio Property Grid where all the properties are displayed in a hierarchical grid-like control. Samples of this include CGI, ASP, .NET Compilation, etc.
  • ModuleListPage. This base class is useful whenever you need to display a list of items. It includes a ListView control you can use to display the settings and offers searching, grouping and views automatically. Samples include Application Settings, Modules, Worker Processes, etc.

Steps

  1. Select the option Add New Item from the Project Menu.

  2. In the Add New Item dialog, select the Class template and type imageCopyrightUIPage.cs as the name for the file. Change the code so that it looks as follows:

    using System;
    using System.Collections.Generic;
    using System.Windows.Forms;
    using Microsoft.Web.Management.Client.Win32;
    using Microsoft.Web.Administration;
    using Microsoft.Web.Management.Client;
    using Microsoft.Web.Management.Server;
    namespace IIS7Demos
    {
        public sealed class imageCopyrightUIPage : ModulePage
        {
            public string message;
            public bool featureenabled;
            public string color;
    
            ComboBox _colCombo = new ComboBox();
            TextBox _msgTB = new TextBox();
            CheckBox _enabledCB = new CheckBox();
            public imageCopyrightUIPage()
            {
                this.Initialize();
            }
            protected override void OnActivated(bool initialActivation)
            {
               base.OnActivated(initialActivation);
               if (initialActivation)
               {
                    ReadConfig();
                    UpdateUI();
                }
            }
    
            void UpdateUI()
            {
                _enabledCB.Checked = featureenabled;
                int n = _colCombo.FindString(color, 0);
                _colCombo.SelectedIndex = n;
                _msgTB.Text = message;
            }
    
            void Initialize()
            {
                Label crlabel = new Label();
                crlabel.Left = 50;
                crlabel.Top = 100;
                crlabel.AutoSize = true;
                crlabel.Text = "Enable Image Copyright:";
                _enabledCB.Text = "";
                _enabledCB.Left = 200;
                _enabledCB.Top = 100;
                _enabledCB.AutoSize = true;
    
                Label msglabel = new Label();
                msglabel.Left = 150;
                msglabel.Top = 130;
                msglabel.AutoSize = true;
                msglabel.Text = "Message:";
                _msgTB.Left = 200;
                _msgTB.Top = 130;
                _msgTB.Width = 200;
                _msgTB.Height = 50;
    
                Label collabel = new Label();
                collabel.Left = 160;
                collabel.Top = 160;
                collabel.AutoSize = true;
                collabel.Text = "Color:";
                _colCombo.Left = 200;
                _colCombo.Top = 160;
                _colCombo.Width = 50;
                _colCombo.Height = 90;
                _colCombo.Items.Add((object)"Yellow");
                _colCombo.Items.Add((object)"Blue");
                _colCombo.Items.Add((object)"Red");
                _colCombo.Items.Add((object)"White");
    
                Button apply = new Button();
                apply.Text = "Apply";
                apply.Click += new EventHandler(this.applyClick);
                apply.Left = 200;
                apply.AutoSize = true;
                apply.Top = 250;
    
                Controls.Add(crlabel);
                Controls.Add(_enabledCB);
                Controls.Add(collabel);
                Controls.Add(_colCombo);
                Controls.Add(msglabel);
                Controls.Add(_msgTB);
                Controls.Add(apply);
            }
    
            private void applyClick(Object sender, EventArgs e)
            {
                try
                {
                    UpdateVariables();
                    ServerManager mgr;
                    ConfigurationSection section;
                    mgr = new ServerManager();
                    Configuration config =
                    mgr.GetWebConfiguration
                    (
                           Connection.ConfigurationPath.SiteName, 
                           Connection.ConfigurationPath.ApplicationPath +
                           Connection.ConfigurationPath.FolderPath
                    );
    
                section = config.GetSection("system.webServer/imageCopyright");
                section.GetAttribute("color").Value = (object)color;
                section.GetAttribute("message").Value = (object)message;
                section.GetAttribute("enabled").Value = (object)featureenabled;
    
                mgr.CommitChanges();
    
                }
    
                catch
                {}
    
            }
    
            public void UpdateVariables()
            {
                featureenabled = _enabledCB.Checked;
                color = _colCombo.Text;
                message = _msgTB.Text;
            }
    
            public void ReadConfig()
            {
                try
                {
                    ServerManager mgr;
                    ConfigurationSection section;
                    mgr = new ServerManager();
                    Configuration config =
                    mgr.GetWebConfiguration(
                           Connection.ConfigurationPath.SiteName,
                           Connection.ConfigurationPath.ApplicationPath +
                           Connection.ConfigurationPath.FolderPath);
    
                    section = config.GetSection("system.webServer/imageCopyright");
                    color = (string)section.GetAttribute("color").Value;
                    message = (string)section.GetAttribute("message").Value;
                    featureenabled = (bool)section.GetAttribute("enabled").Value;
    
                }
    
                catch
                {}
    
            }
        }
    }
    

    Although there is a lot, this code does nothing more than put a couple of controls on the ModulePage and reads and writes to the IIS configuration store.

Reading Configuration

The ReadConfig function uses the same Microsoft.Web.Administration interfaces to open the IIS configuration store. The UI itself provides the scope at which the configuration settings will be applied.

Example:

Connection.ConfigurationPath.SiteName,

Connection.ConfigurationPath.ApplicationPath+

Connection.ConfigurationPath.FolderPath

Saving Configuration

The configuration is saved when the Apply button is clicked (applyClick function). The changes made in the UI transfer into the section attributes and the section saves to disk.

section.GetAttribute("enabled").Value = (object)featureenabled;

mgr.CommitChanges();

At this point, you are ready to compile everything again using "Build Solution" from the Build Menu. This builds the assembly imageCopyrightUI and puts it into the Global Assembly Cache.

Module Registration

The UI Module is built, but we still must tell the IIS Management Console to load it. Do this by:

  • Getting the UI module's strong name from the Global Assembly Cache
  • Adding the strong name and type to the IIS Management Console's configuration file. This will cause the IIS Management Console to load the type on startup
  • Enabling the module in the UI modules list

Steps

  1. Open or use an existing elevated command shell and register the Visual Studio 8.0 environment variables by executing the following command:

    "%vs80comntools%\vsvars32.bat
    
  2. Run GacUtil

    GACUTIL /l imageCopyrightUI
    
  3. Open %windir%\system32\inetsrv\config\administration.config and add the following right after the <moduleProviders> entry:

    <add name="imageCopyrightUI" type="IIS7Demos.imageCopyrightUIProvider, IIS7Demos, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3fd9bd5e992ee757"/>
    

The Result

The task is complete. Look at the results.

Open the IIS Management Console and navigate to the /mypictures application.

Double-click the "Image Copyright" entry.

Screenshot of I I S Management Console with my pictures application selected and image Copyright message displayed.
Figure 8: Image Copyright User Interface

Change the copyright message, click apply and refresh your browser. The copyright message changed. Look into the web.config file in the %systemdrive%\inetpub\mypictures directory to see the changed configuration.

Summary

IIS is extensible in ways that were not possible before. You can extend the IIS core processing pipeline with your own component, store the configuration for this component together with the IIS configuration and even write a user interface plug-in that lives side-by-side with the standard IIS settings. To review what we did in the previous example:

IIS Core Extensibility

We added an image handler to the IIS core that inserts a copyright message into each .JPG file that gets served. This was accomplished with only a few lines of C# code. The functionality of the handler was configuration driven. We stored the configuration in the regular IIS configuration files applicationhost.config and web.config. We also added caching support for the images.

IIS Configuration System Extensibility

We added the image copyright handler configuration to the IIS configuration system. Benefits like a highly readable and xml store, instant API and command-line support, delegation and distributed deployments came for free. We did not have to write a single line of code.

IIS User Interface Extensibility

To give our feature the visibility it deserves, we added an IIS User Interface Module. Although not shown, the IIS User Interface is completely remotable via HTTPS.