使用 .NET Framework 开发 IIS 7.0 模块和处理程序

作者:Mike Volodarsky

概述

本文重点介绍如何开始开发基于 .NET Framework 的 IIS 7.0 及更高版本的 Web 服务器功能。 本文演示:

  1. 如何决定是要开发 IIS 模块还是 IIS 处理程序
  2. 如何使用 Visual Studio、Visual C# Express 或 .NET Framework 提供的命令行工具设置开发环境
  3. 如何创建第一个项目
  4. 如何开发简单的模块和处理程序
  5. 如何将简单的模块和处理程序部署到 IIS 服务器

若要查看一些实际的托管 IIS 模块和处理程序,并为应用程序下载它们,请访问使用 HttpRedirection 模块将请求重定向到应用程序使用 DirectoryListingModule 为 IIS 网站创建美观的目录列表,以及使用 IconHandler 在 ASP.NET 应用程序中显示漂亮的文件图标

简介:使用 ASP.NET 开发 IIS 功能

IIS 7.0 之前的 IIS 版本包含称为 ISAPI 的 C API,作为生成 Web 服务器功能的主要扩展性 API。 IIS 7.0 及更高版本经过彻底重新设计,提供全新的 C++ API,所有内置功能均基于该 API,以允许 Web 服务器的完整运行时扩展性。

除此之外,IIS 还首次通过利用与 ASP.NET 2.0 的紧密集成,提供用于扩展 Web 服务器的全保真 .NET API。 对于你而言,这意味着现在可以使用熟悉的 ASP.NET 2.0 API 生成的新 Web 服务器功能来扩展 IIS。 同样,可以在 IIS 上使用现有的 ASP.NET 2.0 模块和处理程序,利用 ASP.NET 集成来增强应用程序的功能,而无需编写任何新代码。 若要详细了解 IIS 中 ASP.NET 集成的详细信息,请参阅 ASP.NET 与 IIS 7 的集成

行业工具:确定开发环境

若要生成 IIS 模块和处理程序,请使用允许开发和编译 .NET 程序集的任何环境。 一些常见选项包括:

  1. Visual Studio 2005。 或者,也可以下载最新 beta 版 Visual Studio 2008
  2. Visual C# 2005 Express Edition,可免费下载(或其他 Express 工具,包括 Visual Basic 2005 Express)。
  3. .NET Framework 运行时中包含的 C# 命令行编译器 (csc.exe)(对于其他语言,需要下载 SDK),以及你偏好的源代码编辑器。

尽管可以使用任何其他受支持的 .NET 语言(托管 C++ 除外)开发 IIS 组件,但本文中的示例使用 C#。 本文将演示如何使用上述所有三种环境开发 IIS 扩展性组件。

注意

由于 IIS 利用现有 ASP.NET API 来实现其 .NET 扩展性,因此你可以在 Windows XP® 和 Windows Server® 2003 上使用 .NET Framework 2.0 开发 IIS .NET 模块和处理程序。 但是,如果你打算使用为支持新 IIS 功能而添加的几个新 ASP.NET API 之一,则必须在 Windows Vista® 上进行开发,或者从 Windows Vista 中获取 System.Web.dll 的版本,或者获取最新版本的 .NET Framework 3.5,以编译代码。

扩展 IIS 的两种方法:模块与处理程序

所有 IIS Web 服务器功能划分为两个类别:模块和处理程序。

模块与早期 IIS 版本中的 ISAPI 筛选器类似,参与每个请求的请求处理,以便以某种方式对其进行更改或添加。 IIS 中一些现成模块的示例包括身份验证模块(操作请求的身份验证状态)、压缩传出响应的压缩模块,以及将有关请求的信息记录到请求日志的日志记录模块。

模块是一个 .NET 类,用于实现 ASP.NET System.Web.IHttpModule 接口,并使用 System.Web 命名空间中的 API 参与一个或多个 ASP.NET 的请求处理阶段。

处理程序类似于早期 IIS 版本中的 ISAPI 扩展,负责处理请求并生成特定内容类型的响应。 模块和处理程序之间的主要差别在于,处理程序通常映射到特定的请求路径或扩展,并支持该路径或扩展对应的特定服务器资源的处理。 IIS 提供的处理程序示例包括 ASP(处理 ASP 脚本)、静态文件处理程序(提供静态文件),以及 ASP.NET 的 PageHandler(处理 ASPX 页面)。

处理程序是一个 .NET 类,用于实现 ASP.NET System.Web.IHttpHandlerSystem.Web.IHttpAsyncHandler 接口,并使用 System.Web 命名空间中的 API,为其支持的特定内容生成 http 响应。

在计划开发 IIS 功能时,要问的第一个问题是该功能是负责向特定 url/扩展提供请求,还是基于任意规则应用于所有/部分请求。 对于前一种情况,应使用处理程序,对于后一种情况,应使用模块。

本文演示了生成简单模块和简单处理程序、创建项目和编译项目的常用步骤,以及将其部署到服务器的具体步骤。

注意

如果你正在开发模块,则不需要开发处理程序,反之亦然。

入门:创建 Visual Studio 项目

若要生成模块或处理程序,必须生成包含模块/处理程序类的 .NET 程序集 (DLL)。 如果使用 Visual Studio 或 Visual Studio Express 工具,则第一步是创建类库项目:

  1. 从“文件”菜单中,选择“新建”>“项目...”。 在“新建项目”对话框(如下所示)中,选择“Visual C#”项目类型,然后在 Visual Studio 安装的模板的右侧列表中选择“类库”

    Create an IIS7 module and handler project in Visual Studio

  2. 我们必须添加对“System.Web.dll”程序集的引用,该程序集包含用于开发 ASP.NET、IIS 模块和处理程序的 API。 右键单击右侧解决方案资源管理器树视图中“项目”节点下的“引用”节点,选择“添加引用...”,然后在“.NET”选项卡中选择“System.Web 程序集版本 2.0”(如下所示)

    Add reference to System.Web.dll

注意

如果你不打算利用 IIS 特定的 ASP.NET API,则可以在 Windows XP 和 Windows Server 2003 上使用 System.Web 程序集版本 2.0。 引用此程序集编译的模块和处理程序可以正常地在 Windows Vista 和 Windows Server 2008 上的 IIS 上部署和运行。 如果你确实想要在模块中使用少数 IIS 特定的 ASP.NET API,则必须在 Windows Vista、Windows Server 2008 上进行开发,或者从 .NET Framework 3.5 获取 System.Web.dll 程序集。 IIS 特定的 API 包括 HttpServerUtility.TransferRequest、HttpResponse.Headers 集合、HttpApplication.LogRequest 事件,以及其他几个 API。

编写代码:生成简单模块

第一个任务是生成一个简单的模块。 在本文稍后,我们还会生成一个示例处理程序。

若要创建模块,请定义一个实现 System.Web.IHttpModule 接口的类。

  1. 删除项目系统生成的“class1.cs”文件,然后添加一个名为 MyModule 的新 C# 类,方法是右键单击右侧树视图中的“MyIIS7Project”项目,选择“添加”>“新建项”,选择“类”,并在“名称”字段中键入“MyModule.cs”

  2. 导入 System.Web 命名空间,以便可以轻松访问其中的类型。

  3. 让我们的 MyModule 类实现 IHttpModule 接口,并定义接口成员 Dispose() 和 Init()。 右键单击“IHttpModule”接口并选择“实现接口”选项可以快速完成此操作:

    A simple IHttpModule class in Visual Studio

    Dispose() 方法旨在在卸载模块时确定性地清除所有非托管资源,以便在垃圾回收器最终确定模块实例之前释放资源。 在大多数情况下,可以将此方法留空。

    Init(HttpApplication context) 方法是与此相关的主方法。 其作用是执行模块的初始化,并将模块连接到 HttpApplication 类上可用的一个或多个请求处理事件。 在请求处理期间,将针对模块订阅的每个事件调用模块,从而允许其执行其服务。 若要执行该操作:

  4. 通过将模块类上的方法连接到所提供的 HttpApplication 实例上的事件之一来订阅一个或多个请求处理事件。 该方法必须遵循 System.EventHandler 委托签名。 我们定义了一个名为 OnPreExecuteRequestHandler 的新方法,并将其连接到 HttpApplication.PreRequestRequestHandlerExecute 事件,该事件在服务器即将调用请求的请求处理程序之前发生:

    public void Init(HttpApplication context) 
    { 
        context.PreRequestHandlerExecute += 
            newEventHandler(OnPreRequestHandlerExecute) 
    }
    

    Implement IHttpModule.Init() in Visual Studio

    此时,我们的模块已设置为接收每个请求的 PreRequestHandlerExecute 事件。 可以针对你想要接收的所有其他事件重复此操作。

  5. 现在,让我们的模块执行一些有用的操作,以便演示如何使用模块可用的某些 ASP.NET API。 检查请求是否指定了 Referrer 标头,如果已指定,请拒绝它,这是防止人们从其他网站链接到你的网站的不合理方法。 我们将在 OnPreRequestHandlerExecute 方法中执行此操作,该方法在处理程序针对每个请求运行之前调用:

    public void OnPreRequestHandlerExecute (
       Object source, EventArgs e) 
    { 
       HttpApplication app = (HttpApplication)source; 
       HttpRequest    request = app.Context.Request; 
    
       if (!String.IsNullOrEmpty( request.Headers["Referer"] )) 
       { 
           throw new HttpException(403, 
                                                   "Uh-uh!"); 
       } 
    }
    

    Implement IHttpModule

    注意

    HttpApplication 实例通过 source 参数提供给模块,需要进行强制转换。 可以从 HttpApplication 实例访问请求对象模型的其余部分,例如 HttpContext 对象以及表示请求的包含的 HttpRequest 对象。

上面的代码检查是否已指定 Referrer 标头,如果已指定,它会拒绝请求并返回“403 Unauthorized”错误代码。

编写代码:生成简单处理程序

下一个任务是生成一个简单的处理程序。 在本文前面,我们生成了一个示例模块 - 如果你要阅读有关如何生成模块的信息,请返回前文。

若要创建处理程序,我们必须定义一个实现 System.Web.IHttpHandler 接口的类(如果我们希望页面异步执行,则还可以实现 System.Web.IHttpAsyncHandler)。 若要执行该操作:

  1. (如果尚未这样做)删除项目系统生成的“class1.cs”文件,然后添加一个名为 MyHandler 的新 C# 类,方法是右键单击右侧树视图中的“MyIIS7Project”项目,选择“添加”>“新建项”,选择“类”,并在“名称”字段中键入“MyHandler.cs”

  2. 导入 System.Web 命名空间,以便可以轻松访问其中的类型。

  3. 让我们的 MyHandler 类实现 IHttpHandler 接口,并定义接口成员 IsReusableProcessRequest()。 右键单击“IHttpHandler”接口并选择“实现接口”选项可以快速完成此操作:

    Implement the IHttpHandler interface in Visual Studio

    IsReusable() 指示处理程序实例是否可以重用于后续请求。 在某些情况下,处理完请求后,处理程序可能会处于错误状态(处理另一个请求),特别是在成员变量中存储了有关前一个请求的数据时。 请注意,运行时永远不会使用处理程序的同一实例同时处理两个请求,即使它被标记为可重用。 如果处理程序不在成员变量中存储任何每个请求的状态,并且可以根据需要多次调用其 ProcessRequest 函数,请使该属性返回 true 以允许重用。

    ProcessRequest() 方法是处理程序的主要入口点。 其作用是处理由提供的 HttpContext 实例提供的 HttpRequest 实例指定的请求,并使用同样可从 HttpContext 提供的 HttpResponse 实例生成适当的响应。 ProcessRequest() 方法将由运行时在 ExecuteRequestHandler 请求处理阶段调用,并且仅当请求根据配置的处理程序映射项映射到处理程序时才会调用。 这与接收对应用程序的所有请求的通知的模块不同。

  4. 首先实现 IsReusable 属性。 由于我们的处理程序不会存储请求的任何成员状态,并且能够支持对不同请求的 ProcessRequest() 的多次调用,因此我们通过返回 true 将其标记为可重用。

    public bool IsReusable
    {
        get { return true; }
    
  5. 最后,让我们实现 ProcessRequest() 方法,使处理程序可以真正执行一些有用的操作。 为了让一切变得顺利而简单,我们的处理程序将返回服务器上的当前时间,有时还允许在查询字符串中指定时区。 我们的目的是能够请求 URL(例如 http://myserver/time.tm)并获取服务器上的当前时间。 此外,我们能够通过请求 http://myserver/time.tm?utc=true 来获取协调世界时。 下面是我们的实现:

    public void ProcessRequest(HttpContext context) 
    { 
        DateTime dt; 
        String useUtc = context.Request.QueryString["utc"]; 
        if (!String.IsNullOrEmpty(useUtc) && 
                useUtc.Equals("true")) 
        { 
            dt = DateTime.UtcNow; 
        } 
        else 
        { 
            dt = DateTime.Now; 
        } 
        context.Response.Write( 
            String.Format( "<h1>{0}</h1>", 
                           dt.ToLongTimeString() 
                           ) ); 
    }
    

    我们使用 HttpRequest.QueryString 集合来检索 QueryString 变量,并使用 HttpResponse.Write 方法将当前时间写入响应。 这只是可以选择在处理程序中执行的操作的一个示例 - HttpRequest 类提供有关请求的详细信息,而 HttpResponse 类提供许多不同的方法来塑造返回到客户端的响应。

    Implement IHttpHandler.ProcessRequest in Visual Studio

处理程序已完成。

代码完成:编译模块/处理程序

实现模块和处理程序后,我们可以将其编译成 ASP.NET 可在运行时加载的程序集。 如果你使用的是 Visual Studio 或 Visual Studio Express,请按“Ctrl-Shift-B”或右键单击项目并选择“生成”,直接从工具编译项目

.DLL 程序集将在 <ProjectDirectory>\bin\debug 文件夹中生成,同时还会生成 .PDB 符号文件,你可以使用该文件在服务器上调试程序集/在项目调试阶段将源代码行包含在异常中。

如果你要将程序集上传到生产服务器,请务必通过右键单击解决方案节点,选择“配置管理器”并将类型更改为“调试”,将解决方案配置更改为“发布”。 上传程序集的发布版本(保留 PDB 文件)- 这会从程序集中删除调试信息,并对其进行优化,从而生成更快的代码。

如果未使用 Visual Studio,请使用 Framework 运行时中包含的 C# 命令行编译器来编译项目。 若要编译项目,请打开命令行提示符(如果使用的是 Windows Vista 或 Windows Server 2008,请务必使用“以管理员身份运行”选项运行命令行提示符):

> %windir%\Microsoft.NET\Framework\v2.0.50727\csc.exe /t:library /out:MyIIS7Project.dll /debug \*.cs /r:System.Web.dll

这会生成 MyIIS7Project.DLL 和 MyIIS7Project.PDB 文件。 如果你要生成程序集的发布版本,请省略 /debug 开关,并包含 /o 开关以优化程序集。

将程序集部署到服务器

实现自定义模块和处理程序后,接下来我们将其部署到 Web 应用程序。 可以通过多种方式将模块或处理程序部署到应用程序,并且可以使用许多配置选项来根据需求定制其部署。 下面将演示最基本的部署步骤。 有关部署和配置选项的进阶讨论(包括如何为整个服务器部署模块/处理程序),请参阅本教程系列的下一篇文章:部署 IIS 模块和处理程序(即将发布)

以下步骤假设你要将模块和处理程序部署到 IIS 服务器上的现有应用程序。 如果你尚未创建应用程序,请使用通常位于 %systemdrive%\inetpub\wwwroot 的“默认网站”的根应用程序。 在以下示例中,我们将模块和处理程序部署到位于默认网站中名为“myiis7project”的应用程序。

若要部署模块和处理程序,首先应使包含其实现的程序集可供 ASP.NET 应用程序使用:

  1. 将之前编译的 MyIIS7Project.dll 程序集复制到应用程序根目录中的 /BIN 目录。 如果该目录不存在,请创建它。

  2. 配置要在应用程序中加载的模块和处理程序。 通过“开始”菜单打开“IIS7 管理工具”,在“开始”/搜索框中键入“inetmgr.exe”,然后按 Enter。 在该工具中,双击左侧树视图中的服务器节点,展开“站点”节点,然后双击想要将模块和处理程序添加到的站点或应用程序

  3. 选择“模块”功能图标,然后单击“添加托管模块...”操作,并在出现的对话框中,键入模块名称(任意名称)和完全限定的模块类型“MyIIS7Modules.MyModule”。 请注意,也可以在下拉框中选择类型,因为该工具会自动将程序集加载到 bin 中,并发现实现 IHttpModule 接口的类型。 按“确定”添加模块。

    Adding an IIS7 module

  4. 再次双击站点/应用程序节点,然后选择“处理程序映射”功能图标,以添加处理程序。 然后,单击“添加托管处理程序”操作,并在出现的对话框中指定“time.tm”作为路径,指定“MyIIS7Modules.MyHandler”作为类型,指定“MyHandler”作为名称(可指定任意名称)。 同样请注意,类型会出现在下拉框中,因为管理工具会自动在程序集中检测到该类型。 按“确定”添加处理程序。

    Adding an IIS7 handler

上述操作生成的应用程序配置会配置要在应用程序中加载的 MyModule 模块(使其能够针对所有请求运行),并将处理请求的 MyHandler 处理程序映射到应用程序中的 time.tm URL。

请注意,此配置使模块和应用程序只能在 IIS 集成模式应用程序中运行。 如果你希望模块和处理程序也能在 IIS 及早期版本的 IIS 上的经典模式应用程序中运行,则还必须为模块和处理程序添加经典 ASP.NET 配置。 此外,在 IIS 或早期版本的 IIS 上以经典模式运行时,处理程序要求创建一个脚本映射,以便将 .tm 扩展名映射到 IIS 脚本映射中的 ASP.NET,并且模块仅针对映射到 ASP.NET 的扩展名的请求运行。 有关更多详细信息,请参阅部署 IIS 模块和处理程序(即将发布)

还可以使用 IIS 命令行工具 AppCmd.exe,或者通过脚本或托管代码操作 IIS 配置,或者将配置直接放入 web.config 文件,来添加模块和处理程序。 部署 IIS 模块和处理程序(即将发布)中深入介绍了这些附加选项。

测试模块和处理程序

我们已部署并配置了模块/处理程序。 现在对其进行测试:

  1. 通过在应用程序中向“time.tm”发出请求来测试处理程序。 如果成功,我们会在服务器上看到当前时间。 向应用程序发出请求(例如 http://localhost/myiis7project/time.tm),因为我们已将处理程序部署到默认网站中的 myiis7project 应用程序:

    如果处理程序已正确部署到此应用程序,则你会在服务器上看到当前时间:

    Testing the IIS7 handler

    另请尝试请求 http://localhost/myiis7project/time.tm?utc=true 以显示 UTC 时间。

  2. 测试模块。 在应用程序中创建名为 page.html 的简单 html 页面,该页面链接到 /time.tm URL:

    page.html

    <html>
      <body>
          <a href="time.tm">View current server time</a>
      </body>
    </html>
    

    然后,向 http://localhost/myiis7project/page.html 发出请求以显示链接。 单击该链接时,你会看到错误:

    Testing the IIS7 module

    你可能会问,我们未请求上述相同的 URL,但为何成功看到了时间。 这是因为,我们的模块配置为拒绝对应用程序发出的、指定了 Referrer 标头的请求,每当用户单击链接访问你的网站,而不是直接在浏览器中键入 URL 时,浏览器就会添加该标头。 因此,当你直接请求 URL 时,便可以访问该网站 - 但是,当你单击另一个页面中的链接时,请求会被模块拒绝。

总结

在本文中,我们演示了使用熟悉的 ASP.NET API 开发 IIS 模块和处理程序并将其部署到应用程序的基本步骤。 我们还讨论了开发环境的选择,以及如何确定在哪种情况下要生成模块,在哪种情况下要生成处理程序。 本文中的信息应该可以让你能够生成第一个模块和处理程序来增强 IIS 应用程序的功能。

还可以在使用 .NET 开发模块中查看一个示例模块,其中介绍了如何针对 ASP.NET 成员资格提供程序启用基本身份验证。

请务必查看更多示例,了解托管的 IIS 模块和处理程序如何为应用程序增加价值,并通过访问使用 HttpRedirection 模块将请求重定向到应用程序使用 DirectoryListingModule为 IIS 网站提供美观的目录列表使用 IconHandler在 ASP.NET 应用程序中显示漂亮的文件图标,为应用程序下载这些模块和处理程序。