Creating a UI Module For IIS7 to watch Current Requests

8 minute read

A while back I posted about Failed Request Tracing in IIS 7 and I mentioned the command appcmd list requests which lists the requests that are currently executing in the process.

I was playing around a bit with UI Modules in IIS7, you know the ones that show up in the Features view when you open up a site in IIS 7.

UIModules1

and I decided to create one that displays the requests that are currently executing.

CurrentRequests

This is a step-by-step of how I created it, along with a few notes about things to think about when creating them. The sample should be seen as a proof-of-concept and although you are welcome to use this sample I will advice that it is not tested and doesn’t have much in terms of error handling etc. I just wanted to show how I went through it so you can make your own more interesting UI modules.

Creating and setting up the Project in Visual Studio

Note: make sure that you run Visual Studio as an administrator, otherwise you will not be able to perform certain actions like GACing the dll or edit the inetsrv Administration.config file

  1. Create a new class library (C#) in Visual Studio .Net 2005 or 2008, call it CurrentRequestsUI
  2. Remove Class1.cs as we will not be using this
  3. Add references to Microsoft.Web.Administration.dll and Microsoft.Web.Management.dll in the c:\windows\system32\inetsrv directory. These dlls contain the classes needed to implement the Module, ModuleProvider and ModulePage as well as the classes needed to retrieve the requests.
  4. Sign the assembly. UI Modules need to be added to the GAC so we need to set up signing by:
    • In the project properties on the “Signing” tab, check the box “Sign the assembly”
    • In the dropdown box for the “Choose a strong name key file” select <new>, call the file MyKey.snk, and uncheck the checkbox “Protect my key file with a password”
    • Under the “Build Events” tab in the project properties add the following to the “Post-build event command line”

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

      this will add the assembly to the GAC when you build. Note that on 2005 you would have to change the path to be %VS80COMNTOOLS% rather than %VS90COMNTOOLS%

  5. Under the “Debug” tab in project properties set the “Start Action” to start the external program c:\windows\system32\inetsrv\InetMgr.exe so that you automatically open the IIS manager when you start debugging.

Implementing the essential parts of a UI module

The most basic UI Modules consist of three parts:

  • a class deriving from Microsoft.Web.Management.Client.Module where we initialize the module and register the page we want to show when users access it. Here we have to override and implement the Initialize method which runs when the Module is initialized.
  • a class deriving from Microsoft.Web.Management.Server.ModuleProvider where we set up some basic information about the Module. Here we have to override and implement the ServiceType property (which returns the type of Module Service that is associated with the module provider), the SupportsScope method (where you decide in which scope eg. Application, Site, Server etc. your module will be valid), and the GetModuleDefinition method (which returns a ModuleDefinition with some information about the module).
  • a class deriving from Microsoft.Web.Management.Client.Win32.ModulePage which contains the UI and most of the functionality of our module. The ModulePage is essentially a windows forms, and you can use the same controls on a module page as you can on a windows form.

RequestModule

  1. Add a new class to the project called RequestModule and make it derive from Module
  2. Include the namespaces Microsoft.Web.Management.Client, Microsoft.Web.Management.Server and System.Windows.Forms
  3. Override and implement the Initialize method like this…

     using System;
     using Microsoft.Web.Management.Client;
     using System.Windows.Forms;
     using Microsoft.Web.Management.Server;
    
     namespace CurrentRequestsUI
     {
         internal class RequestModule : Module
         {
             protected override void Initialize(IServiceProvider serviceProvider, Microsoft.Web.Management.Server.ModuleInfo moduleInfo)
             {
                 base.Initialize(serviceProvider, moduleInfo);
                 //register the Module Page - RequestPage
                 IControlPanel controlPanel = (IControlPanel)GetService(typeof(IControlPanel));
                 ModulePageInfo modulePageInfo = new ModulePageInfo(this, typeof(RequestPage), "Current Requests", "Displays the current requests in all worker processes");
                 controlPanel.RegisterPage(modulePageInfo);
             }
         }
     }
    

    In this method we create a new ModulePageInfo and use it to register the RequestPage that we will create later with the controlPanel in IIS 7. “Current Requests” will be the title of the UI Module in the features view, and when you hover over it you will see the tooltip “Displays the current requests in all worker processes”.

RequestModuleProvider

  1. Add a new class to the project called RequestModuleProvider and make it derive from ModuleProvider
  2. Include the namespace Microsoft.Web.Management.Server
  3. Override the ServiceType property so that it returns null since we don’t have an associated Module service
  4. Override the SupportsScope method to return true indicating that we support all scopes, i.e. that it should be visible both at the Application, Site and Server level. If you want it to only be visible at the Application level you can use

     return (scope == ManagementScope.Application);
    
  5. Override the GetModuleDefinition method to

     return new ModuleDefinition(Name, typeof(RequestModule).AssemblyQualifiedName)
    

The whole class should look like this:

using System;
using Microsoft.Web.Management.Server;

namespace CurrentRequestsUI
{
    class RequestModuleProvider : ModuleProvider
    {
        public override Type ServiceType
        {
            get { return null; }
        }
        public override ModuleDefinition GetModuleDefinition(IManagementContext context)
        {
            return new ModuleDefinition(Name, typeof(RequestModule).AssemblyQualifiedName);
        }
        public override bool SupportsScope(ManagementScope scope)
        {
            return true;
        }
    }
}

RequestPage

  1. Add a new class called RequestPage that derives from ModulePage
  2. Include the namespaces System.Windows.Forms, Microsoft.Web.Administration and Microsoft.Web.Management.Client.Win32
  3. To be able to work with the form in the designer you can temporarily change it so that it derives from Form, just make sure to change it later.
  4. While it is temporarily deriving from Form, double click on the class file to open it in the designer and add the following controls from the toolbox

    • a DataGridView called dgRequests
    • a Button called btnRefresh
  5. Change the properties of the controls so that they look approximately like the Current Requests picture at the top of this post. Here are the values I changed

    Control Property Value
    RequestPage Size 734;427
    dgRequests Coulmns Process ID, URL, Client IP, Time Elapsed (ms)
      Location 12;12
      Size 693;341
    btnRefresh Location 630;368
      Size 75;23
      Text Refresh
  6. At the top of the class definition for RequestPage add the following code to create a new ServiceManager that we can use to retrieve the requests.

     Microsoft.Web.Administration.ServerManager manager = new ServerManager();
    
  7. Add a method to the class called UpdateUI() that displays the requests that are currently executing

     private void UpdateUI()
     {
         dgRequests.Rows.Clear();
    
         foreach (WorkerProcess w3wp in manager.WorkerProcesses)
         {
             string ProcID = w3wp.ProcessId.ToString();
    
             foreach (Request request in w3wp.GetRequests(0))
             {
                 string[] row0 = { ProcID, request.Url, request.ClientIPAddr, request.TimeElapsed.ToString() };
                 dgRequests.Rows.Add(row0);
             }
         }
     }
    

    This method cycles through the worker processes provided by the ServiceManager, and for each one of the processes we grab the current requests and display them in the dgRequests grid view.

  8. In the forms designer, double click on the Refresh button to create a btnRefresh_Click event handler and call UpdateUI() from this event handler

     private void btnRefresh_Click(object sender, EventArgs e)
     {
         UpdateUI();
     }
    

    Each time the refresh button is clicked we will update the grid with the current status of the requests.

  9. Create a constructor for RequestPage() and make it call InitializeComponent() and UpdateUI() so that the requests data grid is populated when we open the UI Module

     public RequestPage()
     {
         InitializeComponent();
         UpdateUI();
     }
    
  10. Change the class again so that it derives from ModulePage

And that is pretty much all there is to writing a simple UI Module, now we just have to build the project and configure IIS 7 to use it.

Some notes about the Microsoft.Web.Administration and Microsoft.Web.Management namespaces

The Microsoft.Web.Management namespaces is where all the classes relating to setting up the modules etc. are stored. The Microsoft.Web.Administration namespaces on the other hand are much more interesting in my opinion since this is where we can get to all the process and administration information that we can get to with the appcmd command.

To get to most of the information, you first create a new ServerManager and from this you can get to the Sites, ApplicationPools and WorkerProcesses. Once you have a WorkerProcess, Site or ApplicationPool you can get and set their properties as you would with appcmd. You can also get a little more crazy and for example get all the ApplicationDomains in a worker process and unload a given ApplicationDomain, or get the list of application pools and start, stop or set some of the recycling options on them. Or for that matter, why not read from the failed request tracing logs.

Installing/Configuring the UI Module

Before we can use the UI Module we have to register it with IIS 7 and this is a two step process…

  1. Open c:\windows\system32\inetsrv\config\Administration.config in your visual studio instance
  2. Under <ModuleProviders> add the following line…

     <add name="CurrentRequestsUI" type="CurrentRequestsUI.RequestModuleProvider, CurrentRequestsUI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=269f1cc1a4bf892b" />
    
  3. Browse to c:\windows\assembly (the GAC), locate CurrentRequestsUI, right click to view the properties of it to get the PublicKeyToken and replace the PublicKeyToken above with your PublicKeyToken.
  4. Under <modules> in Administration.config add
<add name="CurrentRequestsUI" />

Now you are all set, and if you open InetMgr.exe and look at one of your applications you should now be able to see the “Current Requests” icon in the features view…

CurrentRequestsIcon

And if you open the feature you should be able to see your current requests (provided that you have any)…

CurrentRequests

Note: in the Client IP column means localhost, for any other requests you should see proper IP addresses.

If you come up with or know about some cool/useful UI Modules, feel free to link to them in the comments. There are some really good ones in the download section of http://www.iis.net if you are interested as well.

Laters, Tess

PS! The full code is available here