使用 HTTP 处理程序提供动态内容

 

斯科特·米切尔

2004 年 4 月

适用于
   Microsoft ASP.NET
   Microsoft Internet Information Services

总结: 了解如何使用本文中介绍的三种实际方案,通过 HTTP 处理程序提供模板化 URL 驱动内容。 ) (29 个打印页

下载本文的源代码

目录

简介
基于文件扩展名路由请求
创建和配置 HTTP 处理程序
剖析一些Real-World HTTP 处理程序
结论
相关书籍

简介

每当请求到达Microsoft Internet Information Services (IIS) Web 服务器时,IIS 就会通过检查所请求文件的扩展名来确定如何处理该文件。 静态文件(如 HTML 页面、图像、级联样式表 (CSS) 文件等)由 IIS 直接处理。 Microsoft ASP.NET 网页或 Web 服务的请求(扩展名为 .aspx 或 .asmx 的文件)将移交给 ASP.NET 引擎。 对扩展名为 .asp 的文件的请求将移交给经典 ASP 引擎。 ASP.NET 和 ASP 引擎负责为请求的资源生成标记。 对于 ASP.NET 和经典 ASP 网页,此标记为 HTML;对于 Web 服务,标记是 SOAP 响应。 引擎成功呈现所请求资源的标记后,此标记将返回到 IIS,IIS 然后将标记发送回请求资源的客户端。

这种提供内容的模型(让 IIS 直接提供静态内容,同时将动态内容的呈现委托给单独的引擎)有两个明显的优点:

  1. 它提供了良好的分工。 IIS 可以专注于处理静态内容,并且可以将动态内容的详细信息留给外部程序。 也就是说,IIS 可以专注于有效地为 HTML 页面和图像提供服务,而 ASP.NET 引擎可以专注于有效地呈现 ASP.NET 网页和 Web 服务。
  2. 它允许以可插入的方式将新的动态服务器端技术添加到 IIS。 试想一下,如果 IIS 负责呈现网页本身 ASP.NET,而不是依赖于外部引擎。 在这种情况下,每次出现新版本的 ASP.NET 或任何动态服务器端技术时,都需要创建支持此新版本的 IIS。 想要使用最新版本的用户必须更新 IIS 版本。

若要使此内容服务模型正常工作,IIS 需要将文件扩展名映射到程序。 此信息存在于 IIS 元数据库中,可以通过 Internet 服务管理器进行配置,我们将在下一部分中看到。 当请求进入 IIS 时,将参考此映射来确定应将请求路由到何处。 默认情况下,.aspx、.asmx、.ashx、.cs、.vb、.config等扩展都配置为路由到 ASP.NET 引擎。

每当请求从 IIS 路由到 ASP.NET 引擎时,ASP.NET 引擎将执行一系列类似的步骤来确定如何正确呈现请求的文件。 具体而言,ASP.NET 引擎检查所请求文件的扩展名,然后调用与该扩展关联的 HTTP 处理程序 ,其作业是呈现所请求文件的标记。

注意 从技术上讲,ASP.NET 引擎将调用 HTTP 处理程序或 HTTP 处理程序工厂。 HTTP 处理程序工厂是返回 HTTP 处理程序实例的类。

HTTP 处理程序是一个类,它知道如何呈现特定类型的 Web 内容的内容。 例如,.NET Framework中用于呈现 ASP.NET 网页的 HTTP 处理程序类不同于用于呈现 Web 服务的 HTTP 处理程序类。 正如 IIS 依赖于外部程序来提供动态内容一样,ASP.NET 引擎依赖于不同的类来呈现特定类型的内容。

通过像 IIS 一样可插入 ASP.NET 引擎,ASP.NET 实现了前面讨论的相同优势。 特别感兴趣的是,此模型允许开发人员创建新的 HTTP 处理程序类,并将其插入 ASP.NET 引擎。 在本文中,我们将准确探讨如何创建自定义 HTTP 处理程序并在 ASP.NET Web 应用程序中使用这些处理程序。 首先,我们将深入探讨 ASP.NET 引擎如何确定应为请求提供服务的 HTTP 处理程序。 然后,我们将了解如何使用几行代码轻松创建自己的 HTTP 处理程序类。 最后,我们将介绍一些现可在 Web 应用程序中开始使用的实际 HTTP 处理程序示例。

基于文件扩展名路由请求

如简介中所述,IIS 和 ASP.NET 引擎根据请求的文件扩展名将传入请求路由到外部程序或类。 为实现此目的,IIS 和 ASP.NET 必须具有某种目录,将文件扩展名映射到外部程序。 IIS 将此信息存储在其元数据库中,该元数据库可通过 Internet 服务管理器进行编辑。 图 1 显示了 IIS 应用程序的“应用程序配置”对话框的屏幕截图。 每个提供的扩展都映射到特定的可执行路径。 图 1 显示了映射到 ASP.NET 引擎 (.asax、.ascx、.ashx、.asmx 等) 的一些文件扩展名。

ms972953.httphandlers_fig01 (en-us,MSDN.10) .gif

图 1. 配置的文件扩展名

具体而言,IIS 将 ASP.NET 相关的扩展映射到 \WINDOWS_DIR\Microsoft.NET\Framework\VERSION\aspnet_isapi.dll。 正如 ASP.NET 将文件扩展名映射到 HTTP 处理程序一样,IIS 将文件扩展名映射到 ISAPI 扩展。 (ISAPI 扩展是一个非托管的编译类,用于处理传入的 Web 请求,其任务是为请求的资源生成内容 ) 。但是,ASP.NET 引擎是.NET Framework中的一组托管类。 aspnet_isapi.dll充当非托管世界 (IIS) 与托管世界 (ASP.NET 引擎) 之间的桥梁。

若要更深入地了解 IIS 如何处理传入请求,包括如何自定义特定于 IIS 的映射,检查 Michele Leroux Bustamante 的文章 In IIS and ASP.NET

虽然 IIS 将其文件扩展名目录和 ISAPI 扩展存储在元数据库中,但此目录用于 ASP.NET 存储在 XML 格式的配置文件中。 位于 \WINDOWS_DIR\Microsoft.NET\Framework\VERSION\CONFIG\) 中的machine.config文件 (包含默认的 Web 服务器范围的映射,而Web.config文件可用于指定特定于 Web 应用程序的映射。

在machine.config和Web.config文件中,映射存储在 httpHandlers> 元素中<。 每个映射都由单个 <add> 元素表示,该元素具有以下语法:

<add verb="verb list" path="extension | path" 
type="HTTP handler type" />

谓词属性可用于将 HTTP 处理程序限制为仅为特定类型的 HTTP 请求提供服务,例如 GET 或 POST。 若要包含 所有 谓词,请使用 *。 path 属性指定要映射到 HTTP 处理程序的扩展,例如 *.scott,也可以指定特定的 URL 路径。 最后, 类型 属性指定负责呈现此内容的 HTTP 处理程序的类型。

下面是 machine.config 文件中的默认 HTTP 处理程序分配的代码片段:

<httpHandlers>
   <add verb="*" path="*.aspx" 
     type="System.Web.UI.PageHandlerFactory" />
   <add verb="*" path="*.asmx" 
type="System.Web.Services.Protocols.WebServiceHandlerFactory, 
System.Web.Services, Version=1.0.5000.0, Culture=neutral, 
PublicKeyToken=b03f5f7f11d50a3a" validate="false" />
   <add verb="*" path="*.ascx" 
     type="System.Web.HttpForbiddenHandler" />
   <add verb="*" path="*.config" 
      type="System.Web.HttpForbiddenHandler" />
   <add verb="*" path="*.cs" 
     type="System.Web.HttpForbiddenHandler" />
   <add verb="*" path="*.vb" 
      type="System.Web.HttpForbiddenHandler" />
   ...
</httpHandlers>

第一个 <添加> 元素将 ASP.NET 网页的所有请求 (*.aspx) 映射到 HTTP 处理程序工厂 PageHandlerFactory。 第二个将 web 服务 (.asmx) 的所有请求映射到 WebServiceHandlerFactory 类。 其余四个<添加>元素将某些扩展映射到 HttpForbiddenHandler HTTP 处理程序。 如果用户尝试浏览到.config文件(例如应用程序的Web.config文件),则会调用此 HTTP 处理程序。 HttpForbiddenHandler 只是发出一条消息,指示未提供该类型的文件,如图 2 所示。

注意 可以通过将这些文件的扩展名映射到machine.config或Web.config文件中的 HttpForbiddenHandler HTTP 处理程序,防止个人直接访问敏感文件。 例如,如果运行 Web 托管公司,则可以配置 IIS 以将请求路由到 .mdb 文件 (Microsoft Access 数据库文件) ASP.NET 引擎,然后让 ASP.NET 引擎将所有 .mdb 文件映射到 HttpForbiddenHandler。 这样,即使用户将其 Access 数据库文件放在 Web 可访问的位置,恶意用户也无法下载这些文件。 有关详细信息,请参阅我的文章 使用 ASP.NET 保护文件

ms972953.httphandlers_fig02 (en-us,MSDN.10) .gif

图 2. 限制查看web.config

machine.config 文件指定 Web 服务器上的所有 Web 应用程序的默认映射。 但是,可以使用 Web.config 文件通过 Web 应用程序在 Web 应用程序上自定义映射。 若要向 Web 应用程序添加 HTTP 处理程序,请将 add> 元素添加到<<httpHandlers> 元素。 <httpHandlers> 元素应添加为 system.web> 元素的<子元素,如下所示:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
  <system.web>
    <httpHandlers>
        <add verb="verb list" path="extension | path" type="type" />
    </httpHandlers>

    ...
  </system.web>
</configuration>

也可以使用 remove 元素从 Web 应用程序中删除特定的 HTTP 处理程序,<如下所示:>

    <httpHandlers>
        <remove verb="verb list" path="extension | path" />
    </httpHandlers>

自定义 ASP.NET 引擎的文件扩展名到 HTTP 处理程序的映射时,请务必了解在machine.config或Web.config文件中设置的文件扩展名必须映射到 IIS 元数据库中的aspnet_isapi.dll。 为了使 ASP.NET 引擎能够将请求路由到正确的 HTTP 处理程序,它必须首先从 IIS 接收请求。 仅当所请求文件的扩展名映射到 IIS 元数据库中的 aspnet_isapi.dll 文件时,IIS 才会将请求路由到 ASP.NET 引擎。 创建自定义 HTTP 处理程序时,始终需要牢记这一点。 本文稍后将介绍一个示例,其中需要对 IIS 元数据库进行添加,将.gif和.jpg扩展映射到aspnet_isapi.dll ISAPI 扩展。

现在,我们已经了解了 IIS 如何将传入请求映射到 ISAPI 扩展,以及 ASP.NET 引擎如何将传入的请求映射到 (或 HTTP 处理程序工厂) ,接下来我们来了解如何创建自己的 HTTP 处理程序类。

创建和配置 HTTP 处理程序

将 HTTP 处理程序添加到 ASP.NET Web 应用程序需要两个步骤。 首先,必须创建 HTTP 处理程序,这需要创建实现 System.Web.IHttpHandler 接口的类。 其次,需要将 ASP.NET Web 应用程序配置为使用 HTTP 处理程序。 在上一部分中,我们了解了如何将 Web 应用程序配置为使用 HTTP 处理程序,方法是将 httpHandlers> 节添加到<应用程序的Web.config文件或 Web 服务器的machine.config文件。 由于我们已经了解了如何配置应用程序以使用 HTTP 处理程序,因此让我们重点介绍如何生成 HTTP 处理程序。

IHttpHandler 接口定义了一个方法, 即 ProcessRequest (HttpContext) 和一个属性 IsReusableProcessRequest (HttpContext) 方法采用包含请求相关信息的 System.Web.HttpContext 实例。 ProcessRequest (HttpContext) 方法负责根据请求详细信息发出正确的标记。

传递到 ProcessRequest (HttpContext) 方法的 HttpContext 类公开 System.Web.UI.Page 类提供的许多相同重要属性:RequestResponse 属性允许您处理传入请求和传出响应;会话应用程序属性可用于处理会话和应用程序状态;Cache 属性提供对应用程序的数据缓存的访问权限;和 User 属性包含有关发出请求的用户的信息。

注意HttpContextPage 类之间看似相似之处并非巧合。 事实上, Page 类是 HTTP 处理程序本身,可实现 IHttpHandler。 在 Page 类的 ProcessRequest (HttpContext) 方法的开头, 为 Page 类的 RequestResponseServer 和其他内部对象分配传入的 HttpContext 的相应属性。

IHttpHandlerIsReusable 属性是一个布尔属性,指示 HTTP 处理程序是否可重用。 IsReusable 属性指示 HTTP 处理程序的一个实例是否可用于对相同文件类型的其他请求,或者每个请求是否需要 HTTP 处理程序类的单个实例。 可悲的是,官方文档中几乎没有关于使用 IsReusable 的最佳做法的信息。 Microsoft ASP.NET 团队开发人员 Dmitry Robsman 的这篇 ASP.NET 论坛文章阐明了一些主题:“当你不需要每个请求的新实例时,处理程序是可重用的。 分配内存的成本很低,因此,如果一次性初始化成本较高,只需将处理程序标记为可重用。”Dmitry 还指出, Page 类(即 HTTP 处理程序,召回)不可重复使用。 有了此信息,就可以放心地让 HTTP 处理程序为 IsReusable 返回 false。

创建简单的 HTTP 处理程序

为了说明如何创建 HTTP 处理程序类,让我们生成一个简单的 HTTP 处理程序,该处理程序仅显示当前时间和请求信息。 若要继续操作,请使用所选语言创建新的类库项目, (我将使用 C#;我将项目命名为 skmHttpHandlers) 。 这将使用默认的 Class.cs (或 Class.vb) 文件创建新项目。 在此文件中,添加以下代码, (命名空间可能与) 不同:

using System;
using System.Web;

namespace skmHttpHandlers
{
   public class SimpleHandler : IHttpHandler
   {
      public void ProcessRequest(HttpContext context)
      {
         // TODO:  Add SimpleHandler.ProcessRequest implementation
      }

      public bool IsReusable
      {
         get
         {
            // TODO:  Add SimpleHandler.IsReusable 
            // getter implementation
            return false;
         }
      }
   }
}

上面的代码定义了一个名为 SimpleHandler 的类,该类实现了 IHttpHandlerSimpleHandler 类提供单个方法(ProcessRequest (HttpContext) )和单个属性 IsReusable。 我们可以将 IsReusable 属性保留原样 (,因为它默认返回 false) ,这意味着我们只需为 ProcessRequest (HttpContext) 方法编写代码。

通过将以下代码添加到 ProcessRequest (HttpContext) 方法,我们可以让此 HTTP 处理程序呈现当前时间和请求详细信息:

public void ProcessRequest(HttpContext context)
{
   context.Response.Write("<html><body><h1>The current time is ");
   context.Response.Write(DateTime.Now.ToLongTimeString());
   context.Response.Write("</h1><p><b>Request Details:</b><br /><ul>");
   context.Response.Write("<li>Requested URL: ");
   context.Response.Write(context.Request.Url.ToString());
   context.Response.Write("</li><li>HTTP Verb: ");
   context.Response.Write(context.Request.HttpMethod);
   context.Response.Write("</li><li>Browser Information: ");
   context.Response.Write(context.Request.Browser.ToString());
   context.Response.Write("</li></ul></body></html>");
}

请注意,此代码使用一系列 Response.Write () 语句发出其内容,并吐出应发送回请求的 Web 浏览器的精确 HTML 标记。 请注意 ,ProcessRequest (HttpContext) 方法的目标是将页面的标记发送到 Response 对象的输出流。 实现此目的的一种简单方法是通过 Response.Write () 。 (在“保护映像”部分中,我们将了解如何将图像文件的二进制内容直接写入 Respose 对象的 OutputStream 属性。)

Web 控件呢?

对于呈现 HTML 标记的 HTTP 处理程序,如果使用 Response.Write () 语句,则可能有点不妥。 你需要改用 Web 控件发出 HTML 标记。 可以在 HTTP 处理程序中使用 Web 控件,尽管它不像在 ASP.NET 网页中使用 Web 控件那么简单或直接。 可以使用两种技术:

  1. 创建单独的 ASP.NET 网页,用作 HTTP 处理程序输出的 模板 。 然后,HTTP 处理程序将模板和要显示的动态内容结婚。
  2. 在 HTTP 处理程序中以编程方式创建 Web 控件,并使用 RenderControl () 方法将 Web 控件的 HTML 标记呈现 () 。

第一种方法是理想的方法,因为它提供代码和内容的干净分离。 也就是说,只需修改网页 ASP.NET 模板即可调整 HTTP 处理程序生成的 HTML 标记,而不是在 HTTP 处理程序中使用 Response.Write () 语句。 不过,要使此方法正常工作,需要一些工作。 在“使用 HTTP 处理程序工厂显示URL-Driven内容”部分中,我们将介绍使用此技术的 HTTP 处理程序工厂。

第二种方法涉及以编程方式创建要在 HTTP 处理程序的 ProcessRequest () 方法中呈现的 Web 控件类的实例。 事实证明,此方法有点具有挑战性,因为如果要添加嵌套在其他控件内部的 Web 控件,则必须自行手动生成 Web 控件层次结构。 构造控件层次结构后,需要使用 RenderControl () 方法生成控件层次结构的 HTML。

以下代码片段演示了如何在 HTTP 处理程序中以编程方式呈现 Web 控件:

public void ProcessRequest(HttpContext context)
{
   // build up the control hiearchy - a Panel as the root, with
   // two Labels and a LiteralControl as children...
   Panel p = new Panel();      

   Label lbl1 = new Label();
   lbl1.Text = "Hello, World!";
   lbl1.Font.Bold = true;

   Label lbl2 = new Label();
   lbl2.Text = "How are you?";
   lbl2.Font.Italic = true;

   p.Controls.Add(lbl1);
   p.Controls.Add(new LiteralControl(" - "));
   p.Controls.Add(lbl2);

   // Render the Panel control
   StringWriter sw = new StringWriter();
   HtmlTextWriter writer = new HtmlTextWriter(sw);
   p.RenderControl(writer);

   // Emit the rendered HTML
   context.Response.Write(sw.ToString());
}

(若要使用上述代码,需要通过 Imports 或使用 statements.System.IO、System.Web、System.Web.UI 和 System.Web.UI.WebControls 命名空间)

显然,手动构建和呈现控件层次结构并不像通过拖放或使用声明性 Web 控件语法添加 Web 控件那么容易。 请注意,每次 ASP.NET 网页在其 HTML 部分更改后访问它时,HTML 部分都会转换为一个类,该类以编程方式构建控件层次结构,类似于上面的示例。

将 ASP.NET Web 应用程序配置为使用简单 HTTP 处理程序

创建 HTTP 处理程序类后,剩下的就是将 ASP.NET Web 应用程序配置为使用特定文件扩展名的处理程序。 假设你为 HTTP 处理程序类库项目创建了一个新的 Microsoft® Visual Studio® .NET 解决方案,最简单的方法是将新的 ASP.NET Web 应用程序项目添加到解决方案。 还需要将 HTTP 处理程序项目添加到 ASP.NET Web 应用程序的 References 文件夹。 (右键单击“ 引用” 文件夹,然后选择 “添加引用”。在“ 添加引用 ”对话框中,选择“ 项目 ”选项卡,然后选择在上一节中创建的“类库”项目。) 如果不使用 Visual Studio .NET,则需要手动将 HTTP 处理程序的程序集复制到 ASP.NET Web 应用程序的 /bin 目录。

回想一下,若要将 Web 应用程序配置为使用 HTTP 处理程序,需要将某些文件扩展名 (或特定路径) 映射到 HTTP 处理程序。 我们可以构建自己的扩展(如 .simple),但这样做时,我们必须配置 IIS,将 .simple 扩展映射到 ASP.NET 引擎的 ISAPI 扩展 (aspnet_isapi.dll) 。 如果在共享 Web 服务器上托管站点,则 Web 托管公司可能不允许向 IIS 元数据库添加自定义映射。 幸运的是,在 Web 服务器上安装.NET Framework时,会自动添加扩展 .ashx 并将其映射到 ASP.NET 引擎的 ISAPI 扩展。 如果无权修改 IIS 元数据库,则此扩展可用于自定义 HTTP 处理程序。

对于将 Web 应用程序配置为使用自定义 HTTP 处理程序的第一个示例,让我们使用 .ashx 扩展,这是通过将以下 <httpHandlers> 部分添加到 Web 应用程序的 Web.config 文件来完成的。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <httpHandlers>
       <!-- Simple Handler -->
       <add verb="*" path="*.ashx" 
         type="skmHttpHandlers.SimpleHandler, skmHttpHandlers" />
    </httpHandlers>
  </system.web>
</configuration>

请注意, <add> 元素指示任何具有 .ashx 扩展名的文件的任何 HTTP 谓词上传入的请求应由 skmHttpHandlers.SimpleHandler HTTP 处理程序处理。 type 属性的值指定 (namespace.className) 要使用的 HTTP 处理程序类的类型,后跟逗号,后跟 HTTP 处理程序类所在的程序集名称。

将上述 <httpHandlers> 部分添加到 Web 应用程序的 Web.config 文件后,访问具有 .ashx 扩展名 的任何 路径将显示图 3 中所示的页面。

ms972953.httphandlers_fig03 (en-us,MSDN.10) .gif

图 3. 简单 HTTP 处理程序

图 3 显示了浏览器访问 Web 应用程序的 HelloWorld.ashx 文件的屏幕截图。 请注意,此文件 HelloWorld.ashx 实际上不存在。 发生的情况如下:

  1. 请求传入了 HelloWorld.ashx 的 IIS。
  2. IIS 会记录 .ashx 扩展,并将请求路由到 ASP.NET 引擎。
  3. ASP.NET 引擎会查阅 Web.config 文件,并注意到 .ashx 文件应由 SimpleHandler HTTP 处理程序处理。 然后,ASP.NET 引擎创建此类的实例,并调用其 ProcessRequest () 方法,并传入当前 HttpContext
  4. SimpleHandler HTTP 处理程序向 Response 对象的输出流发出当前时间和请求详细信息。
  5. ASP.NET 引擎将呈现的 HTML 标记从 HTTP 处理程序返回到 IIS。
  6. IIS 将呈现的 HTML 标记返回到请求 HelloWorld.ashx 的浏览器。
  7. 浏览器将显示呈现的 HTML,如图 3 所示。

如果访问者请求 ASPisNeat.ashx、 myfile.ashx 或任何扩展名为 .ashx 的文件,这七个步骤将采用相同的方式进行。

剖析一些Real-World HTTP 处理程序

现在,我们已经了解了创建 HTTP 处理程序并将 Web 应用程序配置为使用该处理程序所需的步骤,接下来让我们检查一些现可在 ASP.NET Web 应用程序中开始使用的实际 HTTP 处理程序。 (其中许多 HTTP 处理程序想法都来自我针对实际 HTTP 处理程序示例请求建议的 博客文章 中的评论。)

本文的其余部分将演练三个此类 HTTP 处理程序:

  • CodeFormatHandler - 一种 HTTP 处理程序,用于设置代码文件 (.cs 和 .vb 文件的格式) 类似于 Visual Studio .NET。 (仅当请求通过 localhost.)
  • ImageHandler - 提供 GIF 和 JPEG 图像的 HTTP 处理程序。 处理程序向图像添加水印,并检查以确保图像不会从您的网站“提升” (也就是说,另一个网站中的某人未从其网站) 链接到你的图像。
  • EmployeeHandlerFactory - 一个 HTTP 处理程序 工厂 ,它使用单独的 ASP.NET 网页显示员工数据库中有关特定员工的信息。

可从本文下载所有三个处理程序示例的完整源代码以及演示其用法的 ASP.NET Web 应用程序的源代码。

使用 HTTP 处理程序设置代码格式

你是否曾想快速查看 ASP.NET 网页代码隐藏类之一的源代码,但不想花时间加载 Visual Studio .NET 并打开关联的项目? 如果你和我一样,你可能已经打开了多个 Visual Studio .NET 实例,以及 Microsoft® Outlook、Microsoft® Word、.NET Framework文档、SQL Enterprise Manager 和 Web 浏览器,因此打开另一个 Visual Studio .NET 实例通常是我最不想做的。

理想情况下,最好能够访问驻留在服务器上的代码隐藏类以查看其源。 也就是说,若要查看 WebForm1.aspx 页面的代码,我只需将浏览器指向 https://localhost/WebHost1.aspx.cs. 但是,如果尝试此操作,你将获得“无法提供此类型的页面”方法, (请参阅图 2) ,因为默认情况下,.cs 和 .vb 扩展映射到 HttpForbiddenHandler HTTP 处理程序。 请注意,这是一件好事,因为代码隐藏类可能包含你不希望任何随机访问者查看的连接字符串或其他敏感信息。 但是,理想情况下,将 ASP.NET Web 应用程序从开发服务器移动到生产服务器时, 不会 复制代码隐藏类文件,而只会复制 /bin 目录中的 .aspx 文件和所需的程序集。

不过,在开发服务器上,你可能希望能够通过浏览器查看代码隐藏源代码。 一种选择是删除从 .cs 和 .vb 文件到 HttpForbiddenHandler HTTP 处理程序的映射。 (可通过修改machine.config文件来针对整个开发服务器执行此操作,也可以通过使用 Web.config file 的 httpHandlers> 部分中的 remove 元素<逐个<应用程序完成此操作。) 如果正常工作,源代码以未格式化的方式显示为纯文本, (见图 4) 。>

ms972953.httphandlers_fig04 (en-us,MSDN.10) .gif

图 4。 使用 HTTP 处理程序显示代码

虽然这确实有效,但源代码显示绝不是理想的。 幸运的是,有免费的 .NET 库可用于执行代码的 HTML 格式设置,例如 squishySyntaxHighlighter by squishyWARE。 只需几行代码,squishySyntaxHighlighter 即可获取包含 Visual Basic.NET 代码、C# 代码或 XML 内容的字符串,并返回一个 HTML 字符串,该字符串显示传入的内容类似于 Visual Studio .NET 呈现代码和 XML 内容的方式。 为此,我们将使用 HTTP 处理程序。 毕竟,这是 HTTP 处理程序旨在用来呈现特定类型的内容。 在本例中,我们将提供代码隐藏类的格式化呈现。

以下代码显示了 CodeFormatHandler HTTP 处理程序的 ProcessRequest () 方法。 代码中使用的 SyntaxHighlighter 类是由 squishySyntaxHighlighter 提供的类。 要突出显示代码,只需使用 GetHighlighter () 静态方法创建 SyntaxHighlighter 类的实例,指定我们希望如何将代码格式化 (为 Visual Basic.NET、C# 或 XML) 。 然后,创建实例的 Highlight (内容) 方法采用字符串输入 (内容) 并返回该内容的 HTML 格式表示形式。 在此方法结束时,使用 Response.Write () 发出格式化内容。

public void ProcessRequest(HttpContext context)
{
   string output = string.Empty;

   // grab the file's contents
   StreamReader sr = File.OpenText(context.Request.PhysicalPath);
   string contents = sr.ReadToEnd();
   sr.Close();

   // determine how to format the file based on its extension
   string extension = Path.GetExtension(
     context.Request.PhysicalPath).ToLower();
   SyntaxHighlighter highlighter;

   if (extension == ".vb")
   {
      highlighter = SyntaxHighlighter.GetHighlighter( 
        SyntaxType.VisualBasic );
      output = highlighter.Highlight( contents );
   }
   else if (extension == ".cs")
   {
      highlighter = SyntaxHighlighter.GetHighlighter( 
        SyntaxType.CSharp );
      output = highlighter.Highlight( contents );
   }
   else // unknown extension
   {
      output = contents;
   }

   // output the formatted contents
   context.Response.Write("<html><body>");
   context.Response.Write(output);
   context.Response.Write("</body></html>");
}

图 5 显示了图 4 中相同代码的屏幕截图,但当使用 CodeFormatHandler 突出显示时。

ms972953.httphandlers_fig05 (en-us,MSDN.10) .gif

图 5:由 HTTP 处理程序格式化的代码

注意 可供下载的 CodeFormatHandler HTTP 处理程序具有稍微可靠的 ProcessRequest () 方法,仅允许通过 localhost 访问页面的用户查看代码隐藏类的源代码。

如果要对整个 Web 服务器) 使用处理程序,只需将元素添加到<>应用程序的Web.config文件 (或machine.config文件的 httpHandlers> 部分即可。< 如下所述,.cs 和 .vb 扩展都路由到 CodeFormatHandler (,而不是 HttpForbiddenHandler) :

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <httpHandlers>
       <!-- Code Format handler -->
       <add verb="*" path="*.cs" 
         type="skmHttpHandlers.CodeFormatHandler, 
         skmHttpHandlers" />
       <add verb="*" path="*.vb" 
         type="skmHttpHandlers.CodeFormatHandler, 
         skmHttpHandlers" />
    </httpHandlers>

    ...
  </system.web>
</configuration>

保护映像

关于网络上的内容有一句话:一半的内容是原创的,另一半是被盗的。 借助计算机,可以轻松完成他人的工作,并用最少的工作量进行复制。 例如,专业摄影师可能想要在他的网站上显示一些他最好的图像,但想要阻止其他人简单地保存图片并将其放在他们的网站上,就像这是他们自己的作品一样。 即使你不介意其他网站是否使用你的图像,你也希望确保它们保存你的图像在其网站上,而不是简单地添加一个 <指向 Web 服务器的 img> 标记。 (一个示例是,如果网站 www.bandwidthThief.com 有一个带有 <img> 标记(如:<img src=“;)http://yourSite.com/BigImage.jpg">的网页。最好避免这种情况,因为通过从 Web 服务器提供图像,即使查看图像的用户正在访问 www.bandwidthThief.com 网站,也不得不承担带宽费用。)

为了帮助保护图像,让我们创建一个执行两项操作的 HTTP 处理程序:

  1. 检查以确保其他站点未链接到请求的图像。
  2. 向图像添加水印。

请求图像时,大多数浏览器会发送包含引用网站 HTTP 标头中图像的网页的 URL。 因此,我们可以在 HTTP 处理程序中执行的操作是检查,以确保引用网站 HTTP 标头的主机和图像 URL 的主机相同。 如果他们不是,那么我们手上有一个带宽窃贼。 在这种情况下,我们将返回一个备用图像,例如“可以通过转到 www.YourSite.com 查看此图像”,而不是 (甚至水印图像) 返回原始图像。

注意 某些浏览器在请求图像时不发送引用网站 HTTP 标头;其他提供禁用此功能的选项。 因此,这种技术并非万无一失,但可能适用于绝大多数 Web 冲浪者,从而阻止恶意网站设计人员直接链接到网站上的图像。

若要添加水印,我们将使用 System.Drawing 命名空间类在图像的中心添加文本消息。 .NET Framework在 System.Drawing 命名空间中包含许多可用于在运行时创建和修改图形图像的类。 遗憾的是,对这些类的彻底讨论远远超出了本文的范围,但克里斯·加勒特(Chris Garrett) 涵盖 GDI+ 和 System.Drawing 的文章是了解详细信息的一个很好的起点。

创建 ImageHandler HTTP 处理程序

以下代码片段显示了 ImageHandler HTTP 处理程序的 ProcessRequest () 方法。

public void ProcessRequest(HttpContext context)
{
   if (context.Request.UrlReferrer == null || 
      context.Request.UrlReferrer.Host.Length == 0 ||
      context.Request.UrlReferrer.Host.CompareTo(
        context.Request.Url.Host.ToString()) == 0)
   {
      // get the binary data for the image
      Bitmap bmap = new Bitmap(context.Request.PhysicalPath);

      // determine if we need to add a watermark
      if (ImageConfiguration.GetConfig().AddWatermark)
      {
         // Create a Graphics object from the bitmap instance
         Graphics gphx = Graphics.FromImage(bmap);

         // Create a font
         Font fontWatermark = new Font("Verdana", 8, FontStyle.Italic);

         // Indicate that the text should be 
         // center aligned both vertically
         // and horizontally...
         StringFormat stringFormat = new StringFormat();
         stringFormat.Alignment = StringAlignment.Center;
         stringFormat.LineAlignment = StringAlignment.Center;

         // Add the watermark...
         gphx.DrawString(ImageConfiguration.GetConfig().WatermarkText, 
                        fontWatermark, Brushes.Beige, 
                        new Rectangle(10, 10, bmap.Width - 10, 
                          bmap.Height - 10), 
                        stringFormat);

         gphx.Dispose();
      }


      // determine what type of file to send back   
      switch (
        Path.GetExtension(context.Request.PhysicalPath).ToLower())
      {
         case ".gif":
            context.Response.ContentType = "image/gif";
            bmap.Save(context.Response.OutputStream, ImageFormat.Gif);
            break;

         case ".jpg":
            context.Response.ContentType = "image/jpeg";
            bmap.Save(context.Response.OutputStream, ImageFormat.Jpeg);
            break;
      }

      bmap.Dispose();
   }
   else
   {
      string imgPath = 
        context.Server.MapPath( 
        ImageConfiguration.GetConfig().ForbiddenFilePath);
      Bitmap bmap = new Bitmap(imgPath);

      // determine what type of file to send back   
      switch (Path.GetExtension(imgPath))
      {
         case ".gif":
            context.Response.ContentType = "image/gif";
            bmap.Save(context.Response.OutputStream, ImageFormat.Gif);
            break;

         case ".jpg":
            context.Response.ContentType = "image/jpeg";
            bmap.Save(context.Response.OutputStream, ImageFormat.Jpeg);
            break;
      }

      bmap.Dispose();   
   }
}

ImageHandlerImageConfiguration 类协同工作,该类包含 HTTP 处理程序使用的配置信息。 ImageConfiguration 类由其静态 GetConfig () 方法填充,该方法反序列化相应的 Web.config 节。 ImageConfiguration 有三个属性:

  • FobiddenFilePath - 一个文件的路径,如果从其他网站请求图像,则该文件将代替所请求的图像显示。
  • AddWatermark - 一个布尔值,指示是否应为所有图像加水印。
  • WatermarkText - 要显示为水印的文本,例如“版权斯科特·米切尔”。

这些设置在 ImageHandler> 部分的 Web 应用程序的 Web.config 文件中<指定,如下所示:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
  <configSections>
     <section name="ImageHandler"
       type= 
         "skmHttpHandlers.Config.ImageConfigSerializerSectionHandler, 
         skmHttpHandlers" />
  </configSections>
  
  <ImageHandler>    
    <forbiddenFilePath>
       ~/images/STOP-STEALING-MY-BANDWIDTH.gif
    </forbiddenFilePath>
    <addWatermark>true</addWatermark>
    <watermarkText>Copyright Scott Mitchell</watermarkText>
  </ImageHandler>
    
  <system.web>
    ...
  </system.web>
</configuration>

上述设置规定,如果检测到图像是从外部站点请求的,最终用户将看到图像 ~/images/STOP-STEALING-MY-BANDWIDTH.gif。 此外,这些设置指示所有图像都应使用“版权斯科特·米切尔”文本水印。

ProcessRequest () 方法首先检查是否正在从远程主机请求此图像,方法是确定引用者 HTTP 标头的主机和图像 URL 的主机是否匹配。 如果没有,则从其他 Web 服务器请求映像,并且用户将重定向到 ImageConfiguration 类的 ForbiddenFilePath 属性中指定的映像。 否则,代码会检查 AddWatermark 属性是否为 true,如果为,则使用指定的 WatermarkText 对图像进行水印。

将 Web 应用程序配置为使用 ImageHandler HTTP 处理程序

若要在 ASP.NET Web 应用程序中使用 ImageHandler HTTP 处理程序,需要先通过 ImageHandler> 节中的<Web.config添加 ImageConfiguration 属性,然后在 c 节中包含 add<> 元素,将.gif和.jpg扩展与 HTTP 处理程序相关联。 我们检查了<上面的 ImageHandler> 部分,因此我们来看看 <httpHandlers> 部分。 如下所示,需要两个 <添加> 元素:一个将.gif文件映射到处理程序,一个映射.jpg文件。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  ... <ImageHandler> section ...
    
  <system.web>
    <httpHandlers>
        <!-- ImageHandler handlers -->
        <add verb="*" path="*.jpg" 
         type="skmHttpHandlers.ImageHandler, skmHttpHandlers" />
        <add verb="*" path="*.gif" 
         type="skmHttpHandlers.ImageHandler, skmHttpHandlers" />
    </httpHandlers>
    
    ...
  </system.web>
</configuration>

除了将 httpHandlers> 部分添加到<Web.config文件外,还必须配置 IIS 以将所有请求发送到.gif,并将.jpg文件发送到 aspnet_isapi.dll ISAPI 扩展。 如果忘记执行此操作,则每当收到 GIF 或 JPEG 文件的请求时,IIS 都会处理请求本身。 我们需要将此映射添加到 IIS 元数据库,以便在 GIF 或 JPEG 请求传入时将其路由到 ASP.NET 引擎,然后引擎会将请求路由到 ImageHandler HTTP 处理程序。

现在,我们已经了解了如何创建 HTTP 处理程序并将 Web 应用程序 (和 IIS) 配置为使用该处理程序,让我们看看处理程序在操作中! 图 6 显示了一个网页,其中显示了我的狗 Sam 的两张图像。 请注意,图像使用指定的水印文本进行水印。

ms972953.httphandlers_fig06 (en-us,MSDN.10) .gif

图 6。 水印图像

图 7 显示了尝试从另一台服务器访问图像的网页。 对于此示例,网页在 <HTML 中包含 img> 标记,如 <img src=“http://mitchellsvr/HttpHandlerTest/images/SamSitting.jpg">。 (mitchellsvr 是我的计算机的名称。) 当通过 https://localhost/HttpHandlerTest/WebForm1.aspx请求页面时,浏览器会将引用网站 HTTP 标头 https://localhost/HttpHandlerTest/WebForm1.aspx作为 发送 ,而图像请求的 URL 为 http://mitchellsvr/HttpHandlerTest/images/SamSitting.jpg. ImageHandler HTTP 处理程序检测引用网站主机和图像 URL 主机之间的差异,因此显示STOP-STEALING-MY-BANDWIDTH.gif图像。

ms972953.httphandlers_fig07 (en-us,MSDN.10) .gif

图 7。 限制图像请求

使用 HTTP 处理程序工厂显示URL-Driven内容

所有 Web 开发人员都创建了一个页面,该页面基于一组参数显示不同的数据,例如,根据通过查询字符串传递的员工 ID 显示有关员工的信息的网页。 在提供 /DisplayEmployee.aspx 时?EmpID=EmployeeID 网页是显示员工信息的一种方式,你可能希望能够使用更难忘的 URL(如 /employees/name.info)提供员工信息。 (使用此备用 URL 查看有关员工 Jisun Lee 的信息,请访问 /employees/JisunLee.info.)

有几种技术可用于实现更易读和更难忘的 URL。 第一种是使用 URL 重写。 在我的上一篇文章 (ASP.NET 中的 URL 重写)中,我演示了如何使用 HTTP 模块和 HTTP 处理程序执行 URL 重写。 URL 重写是截获某些不存在 URL 的 Web 请求并将请求重新路由到实际 URL 的过程。 URL 重写通常用于提供简短且令人难忘的 URL。 例如,电子商务网站可能有一个标题为 /ListProductsByCategory.aspx 的页面,其中列出了特定类别中所有要销售的产品,其中要显示的产品类别由 querystring 参数指示。 即 /ListProductsByCategory.aspx?CatID=PN-1221 可能会列出所有要销售的小组件。 通过 URL 重写,可以定义一个“伪”URL,如 /Products/Widgets.aspx,而该 URL 实际上并不存在。 当请求进入 /Products/Widget.aspx 的 ASP.NET 引擎时,URL 重写会透明地将用户重定向到 /ListProductsByCategory.aspx?CatID=PN-1221。 但是,用户仍会在浏览器的地址栏中看到 /Products/Widgets.aspx。 ASP.NET 引擎中的 URL 重写通常通过使用 HttpContext的 RewritePath (newURL) 方法来完成,该方法可以在 HTTP 模块或 HTTP 处理程序中使用。

第二种方法是创建一个 HTTP 处理程序,该处理程序知道如何呈现 .info 文件。 此 HTTP 处理程序将通过检查请求的 URL 并选取员工的姓名来显示员工信息。 拥有员工姓名后,快速查找数据库表会检索有关员工的信息。 最后一步是以某种方式将此信息呈现为 HTML 标记。

ImageHandler 示例中,我们了解了 HTTP 处理程序如何检查所请求资源的 URL,因此选取员工的姓名并从数据库中访问其信息应该相对简单。 真正的挑战在于呈现员工信息。 最简单的方法是使用 Response.Write () 语句对 HTTP 处理程序中的 HTML 输出进行硬编码,以发出精确的 HTML 标记,根据需要插入员工的信息。 (此行为类似于我们查看的第一个 HTTP 处理程序示例 SimpleHandler.)

更好的方法是使用 ASP.NET 页作为模板来分隔代码和内容。 为此,需要首先在 Web 应用程序中创建 ASP.NET 网页。 此页面应混合使用 HTML 标记和 Web 控件,就像任何其他 ASP.NET 网页一样。 在我们的示例中,我们将显示员工的姓名、社会安全号码和简短的传记。 因此,此页面将为这三个字段提供三个标签。 图 8 显示了在 Visual Studio .NET 的“设计”选项卡中查看的 ASP.NET 网页 DisplayEmployee.aspx 的屏幕截图。

ms972953.httphandlers_fig08 (en-us,MSDN.10) .gif

图 8。 “员工信息”页

接下来,我们需要创建一个 HTTP 处理程序 工厂。 HTTP 处理程序工厂是实现 System.Web.IhttpHandlerFactory 接口的类,负责在调用时返回实现 IHttpHandler 的类的实例。 Web 应用程序配置为使用 HTTP 处理程序工厂,其方式与配置为使用 HTTP 处理程序的方式完全相同。 Web 应用程序将扩展映射到 HTTP 处理程序工厂后,当传入该扩展的请求时,ASP.NET 引擎会向 HTTP 处理程序工厂请求 IHttpHandler 实例。 HTTP 处理程序工厂向 ASP.NET 引擎提供此类实例,该引擎随后可以调用此实例的 ProcessRequest () 方法。 ASP.NET 引擎通过调用 HTTP 处理程序工厂的 GetHandler () 方法请求 IHttpHandler 实例。

对于此示例, GetHandler () 方法需要执行两项操作:首先,它需要确定被请求的员工的姓名并检索其员工信息。 然后,它必须返回一个 HTTP 处理程序,该处理程序可以为我们之前 (DisplayEmployee.aspx) 创建的 ASP.NET 网页提供服务。 幸运的是,我们不需要创建 HTTP 处理程序来为 ASP.NET 网页提供服务 ,.NET Framework中已经存在一个可以调用 System.Web.UI.PageParser 类的 GetCompiledPageInstance () 方法进行检索的处理程序。 此方法采用三个输入:请求网页的虚拟路径、所请求网页的物理路径以及请求此网页时使用的 HttpContext。 了解 GetCompiledPageInstance () 工作原理的完整详细信息并不重要;但请注意,如果需要,该方法会将 ASP.NET HTML 部分编译为类,并返回此类的实例。 (此自动生成的类派生自代码隐藏类,该类派生自 System.Web.UI.Page 类。 Page 类实现 IHttpHandler,因此从 GetCompiledPageInstance () 返回的类是 HTTP handler.)

我们面临的最后一个挑战是如何将员工信息从 HTTP 处理程序工厂传递到 ASP.NET 网页模板。 HttpContext 对象包含一个 Items 属性,该属性可用于在共享同一 HttpContext 的资源之间传递信息。 因此,我们希望在此处存储员工的数据。

最后一步是返回到 displayEmployee.aspx) (ASP.NET 网页模板。 在模板的代码隐藏类中,我们需要从 HttpContextItems 属性中检索员工信息,并将员工数据分配给页面 HTML 部分中相应的 Label Web 控件。

对于此代码下载附带的演示,我在 EmployeeBOL 项目中提供了一组类,这些类提供员工 (Employee) 的类表示形式,以及一个类,用于根据员工的姓名 (EmployeeFactory) 生成员工。 HTTP 处理程序工厂的 GetHandler () 方法如下所示。 请注意,它首先确定员工的姓名,并将 EmployeeFactory.GetEmployeeByName () 返回的 Employee 对象添加到HttpContextItems 集合中。 最后,当将 DisplayEmployee.aspx 作为物理文件路径传入 DisplayEmployee.aspx 模板时,它将返回由 PageParser.GetCompiledInstance () 返回 ASP.NET 的 IHttpHandler 实例。

public class EmployeeHandlerFactory : IHttpHandlerFactory
{
   ...

   public IHttpHandler GetHandler(HttpContext context, 
     string requestType, string url, string pathTranslated)
   {
      // determine the employee's name
      string empName = 
        Path.GetFileNameWithoutExtension( 
        context.Request.PhysicalPath);

      // Add the Employee object to the Items property
      context.Items.Add("Employee Info", 
        EmployeeFactory.GetEmployeeByName(empName));

      // Get the DisplayEmployee.aspx HTTP handler
      return PageParser.GetCompiledPageInstance(url, 
        context.Server.MapPath("DisplayEmployee.aspx"), context);
   }
}

DisplayEmployee.aspx 的代码隐藏类 ASP.NET 网页模板从 HttpContextItems 属性访问 Employee 类实例,并将标签 Web 控件的 Text 属性分配给相应的 Employee 属性。

public class DisplayEmployee : System.Web.UI.Page
{
   // three Label Web controls in HTML portion of page
   protected System.Web.UI.WebControls.Label lblName;
   protected System.Web.UI.WebControls.Label lblSSN;
   protected System.Web.UI.WebControls.Label lblBio;

   private void Page_Load(object sender, System.EventArgs e)
   {
      // load Employee information from context
      Employee emp = (Employee) Context.Items["Employee Info"];

      if (emp != null)
      {
         // Assign the Employee properties to the Label controls
         lblName.Text = emp.Name;
         lblSSN.Text = emp.SSN;
         lblBio.Text = emp.Biography;
      }
   }
}

若要将 Web 应用程序配置为使用 HTTP 处理程序工厂,请将 add> 元素放在< httpHandlers >节中,就像对 HTTP 处理程序一样:<

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.web>
    <httpHandlers>
      <!-- EmployeeHandlerFactory -->
      <add verb="*" path="*.info" 
         type="skmHttpHandlers.EmployeeHandlerFactory, 
         skmHttpHandlers" />
    </httpHandlers>
  </system.web>
</configuration>

当然,由于此 HTTP 处理程序工厂使用 .info 扩展,因此还需要将 IIS 中的 .info 扩展映射到aspnet_isapi.dll ISAPI 扩展。 图 9 显示了正在运行的员工 HTTP 处理程序工厂的屏幕截图。

ms972953.httphandlers_fig09 (en-us,MSDN.10) .gif

图 9. 使用员工信息处理程序

此模板方法的好处是,如果我们想要更改“员工信息”页面,只需编辑 DisplayEmployee.aspx 页面。 无需更改 HTTP 处理程序工厂,也无需重新编译 HTTP 处理程序工厂程序集。

注意。TextScott Watermasysk 用 C# 编写的开源博客引擎,它使用 HTTP 处理程序工厂提供 URL 驱动的内容。 .与本文中介绍的模板系统相比,文本提供了更深入的模板系统。 它有一个主模板页 DTP.aspx,用作所有请求的模板。 不过,可以根据发出的请求类型自定义此模板。 例如,如果向 /blog/posts/123.aspx 等 URL 发出请求,则为 。文本可以根据 /posts/) 用户要求查看特定帖子 (URL 路径确定。 因此,通过加载一组特定于显示单个博客文章的用户控件,在运行时 (自定义主模板页面) 。 另一方面,如果收到 /blog/archive/2004/04.aspx 的请求,则为 。文本可以根据 (/archive/2004/XX.aspx) 的路径确定您希望查看给定月份 (2004 年 4 月) 的所有帖子。 因此,母版模板页面将加载与显示一个月的条目相关的用户控件。

结论

与 IIS 中的 ISAPI 扩展一样,HTTP 处理程序在 ASP.NET 引擎和 Web 内容呈现之间提供抽象级别。 正如我们所看到的,HTTP 处理程序负责根据指定的扩展为特定类型的请求生成标记。 HTTP 处理程序实现 IHttpHandler 接口,从而提供 ProcessRequest () 方法中的所有繁重工作。

本文介绍了三个真实的 HTTP 处理程序方案:代码格式化程序、图像保护程序以及用于提供模板化 URL 驱动的内容的 HTTP 处理程序工厂。 HTTP 处理程序的其他一些用途包括:

  • 将变量添加到外部 CSS 文件 (请参阅 Rory Blyth 的这篇博客文章,了解) 的详细信息。
  • 显示 Andrew Connell 在此 博客评论) 中分享 (想法的缩略图列表。
  • 通过去除空格和注释、变量重命名等来优化外部 JavaScript 文件。

如果你想出了其他很酷的想法或对 HTTP 处理程序的用法,我邀请你在此博客文章中发表意见: REQUEST: Ideas for a Useful HTTP Handler Demo?

编程愉快!

 

关于作者

斯科特·米切尔是五本书的作者和 4GuysFromRolla.com 的创始人,过去六年来一直在使用 Microsoft Web 技术。 Scott 担任独立顾问、培训师和作家。 可在 或通过其博客(可在 中找到)联系mitchell@4guysfromrolla.comhttp://ScottOnWriting.NET他。

© Microsoft Corporation. 保留所有权利。