如果要让ASP.NET以某种非标准的方式处理请求,我们可以编写自定义的HTTP处理程序。通过编写处理程序,用户可以通过Web调用各种功能。如,我们可以实现单击计数器和各种图像处理(包括图像的动态生成、服务器端缓存、防止图像盗链)。

  HTTP处理程序能够以同步方式工作,也能以异步方式工作。

  常规的ISAPI扩展和筛选器应在IIS元库中注册。如果希望HTTP处理程序参与进处理Web请求的HTTP管道,则要在web.config文件中注册它。它的使用方式与ISAPI扩展类似,可以直接通过URL进行调用。

IHttpHandler接口

  HTTP处理程序是实现IHttpHandler接口的类。更准确的讲,同步HTTP处理程序要实现IHttpHandler接口,而异步处理程序要实现IHttpAsyncHandler接口。IHttpHandler接口定义了处理程序以同步方式处理HTTP请求时需要执行的操作。

IHttpHandler接口的成员

  IHttpHandler接口定义了两个成员:ProcessRequest和IsReusable。见下表:

  System.Web.UI.Page类的IsReusable属性返回false。那意味着,每个新的页面请求都需要创建页面的新实例。若请求需要做大量的处理工作,我们应让IsReusable返回false;若请求仅需简单的筛选特殊请求,可使IsReusable返回true,以节省CPU周期。

  ProcessRequest方法的签名为:

void ProcessRequest(HttpContext context);

  它接收请求上下文,确保相应的请求能够被处理。如果是同步处理程序,在ProcessRequest返回后,便会得到待发往客户端的输出。

简单的HTTP处理程序

  我们从一个简单的HTTP处理程序示例入手:

using System.Web;
namespace Core35.Componts
{
public class SimpleHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
Context.Response.Write(
"<h1>Hello, I'm an HTTP handler</h1>");
}

public bool IsReusable
{
get { return true; }
}
}
}

  我们需要一个入口点来调用该处理程序。在这个上下文中,该处理程序的入口点不过是HTTP端点,即一个公共的URL。其URL必须具有唯一的名称,使IIS和ASP.NET运行库能够将其映射到这段代码上。注册时,要通过web.config文件在HTTP处理程序和Web服务器资源之间建立一个映射。

<configuration>
<system.web>
<httpHandlers>
<add verb="*" path="hello.aspx" type="Core35.Components.SimpleHandler" />
</httpHandlers>
</system.web>
</configuration>

  <httpHandlers>节点列出了当前应用程序可用的处理程序。type属性用于引用包含该处理程序的类和程序集,其格式为type[,assembly]。如果该组件定义在App_Code或其他保留的文件夹,则忽略程序集信息。

  如果将上述设置整合到全局的web.config文件,那么当前服务器计算机中的所有Web应用程序都可调用SimpleHandler组件。

用HTTP处理程序快速建立数据报表

  本示例的思想是,针对自定义的sqlx资源,构建一个HTTP处理程序。SQLX文件是一种包含若干SQL查询语句的XML文档。本处理程序会获取有关查询的信息并执行,最终将结果集用DataGrid显示。

查询管理工具的创建

  为执行查询,我们需要连接字符串和命令文本,下面给出了sqlx文件的内容:

<queries>
<query connString="DataBase=northwind;Server=localhost;UID...;">
Select firstname, lastname, country from employees
</query>
<query connString="DataBase=northwind;Server=localhost;UID=...;">
Select companyname from customers Where country
='Italy'
</query>
</queries>

  该XML文件由<query>节点的集合构成,每个节点包含一个连接字符串属性和查询文本。

  ProcessRequest方法要在执行查询和生成输出前抽取其中的信息:

class SqlxData
{
public string ConnectionString;
public string QueryText;
}

public class QueryHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
//Parses the SQLX file
SqlxData[] data = ParseFile(context);
//Create the output as HTML
StringCollection htmlColl = CreateOutput(data);

//Output the data
context.Response.Write("<html><head><title>");
context.Response.Write(
"QueryHandler Output");
context.Response.Write(
"</title></head><body>");
foreach(string heml in htmlColl)
{
context.Response.Write(html);
context.Response.Write(html);
}
}

//override the IsReusable property
public bool IsReusable
{
get{ return true; }
}
...
}

  辅助函数ParseFile用于解析sqlx文件的源代码,并为每个查询创建SqlxData类的实例:

private SqlxData[] ParseFile(HttpContext context)
{
XmlDocument doc
= new XmlDocument();
string filePath = context.Request.Path;
using(Stream fileStream = VirtualPathProvider.OpenFile(filePath))
{
doc.Load(fileStream);
}

//Visit the <mapping> nodes
XmlNodeList mappings = doc.SelectNodes("queries/query");
SqlxData[] descriptors
= new SqlxData[mappings.Count];
for(int i = 0; i < descriptors.Length; i++)
{
XmlNode mapping
= mappings[i];
SqlxData query
= new SqlxData();
descriptors[i]
= query;

try
{
query.ConnectionString
= mapping.Attributes["connString"].Value;
query.QueryText
= mapping.InnerText;
}
catch
{
context.Response.Write(
"Error parsing the input file.");
descriptors
= new SqlxData[0];
break;
}
}
return descriptors;
}

  SqlxData这个内部类将连接字符串和命令文本集中在一起。该信息被传给CreateOutput函数,由该函数执行实际的查询工作,并生成DataGrid:

private StringCollection CreateOutput(SqlxData[] descriptors)
{
StringCollection coll
= new StringCollection();

foreach(SqlxData data in descriptors)
{
//Run the query
DataTable dt = new DataTable();
SqlDataAdapter adapter
= new SqlDataAdapter(data.QueryText, data.ConnectionString);
adapter.Fill(dt);

// Error handling
...

//Prepare the grid
DataGrid grid = new DataGrid();
grid.DataSource
= dt;
grid.DataBind();

// Get the HTML
string html = Utils.RenderControlAsString(grid);
coll.Add(html);
}
return coll;
}

  执行查询后,该方法会动态创建DataGrid控件。在ASP.NET页面中,DataGrid控件与其他控件一样,会被呈现为HTML。然而,这是通过管理aspx资源的HTTP处理程序完成的。对于sqlx资源,我们需要自己编写这种功能。将Web控件转换为HTML,只需要调用控件的RenderControl方法,并传入一个文本编写器对象:

static class Utils
{
public static string RenderControlAsString(Control ctl)
{
StringWriter sw
= new StringWriter();
HtmlTextWriter writer
= new HtmlTextWriter(sw);
ctl.RenderControl(writer);
return sw.ToString();
}
}

  提示:如果HTTP处理程序需要访问会话状态值,必须实现IRequiresSessionState接口。与INamingContainer一样,它是一种标记性接口,没有任何要实现的方法。实现该接口可对会话状态进行读取和写入操作。如果只需要读取操作,应使用IReadOnlySessionState接口。

处理程序的注册

  HTTP处理程序集必须部署到应用程序的Bin目录下。如果希望该处理程序对所有应用程序可用,可以将其程序集复制到全书程序集缓存(GAC)中。接下来要为某个应用程序或运行在Web服务器中的所有应用程序注册该处理程序。注册工作由web.config完成:

<system.web>
<httpHandlers>
<add verb="*" path="*.sqlx" type="Core35.Components.QueryHandler,Core35Lib" />
</httpHandlers>
</system.web>

  httpHandlers节点支持的操作有:<add>(添加HTTP处理程序)、<remove>(移除特定的处理程序)、<clear>(清除所有已注册的处理程序)。为添加新处理程序,我们需要设置3个属性:verb、path、type。这些属性的说明见下表:

  这些属性必选的,还有一个可选属性validate。如果validate设置为false,ASP.NET将尽量延迟HTTP处理程序集的加载。也就是说,仅当请求到达时才会加载该程序集。ASP.NET不会试图预先加载它,也不会捕获和处理其中错误。

  至此,虽然已正确部署并注册了这个HTTP处理程序,但还是无法调用sqlx资源。原因在于,虽然ASP.NET能够处理sqlx资源,但IIS无法识别它们。

  在sqlx资源的请求传给ASP.NET ISAPI扩展前,会先由IIS处理。如果不注册处理sqlx资源请求的ISAPI扩展,IIS会将其按静态资源处理,直接将sqlx文件的源代码发给客户。所以,我们还需向IIS 6.0元库中注册sqlx扩展名,以便将sqlx资源的请求交给ASP.NET处理。

  在IIS 6.0中打开应用程序的属性对话框-->主目录-->配置-->添加映射-->选择aspnet_isapi.dll作为sqlx资源的ISAPI扩展。

向IIS 7.0注册处理程序

  如果使用IIS 7.0,我们不必通过IIS管理器进行设置,只需在web.config中向system.webserver节点添加一个子节点,并指定HTTP处理程序即可:

<system.webserver>
<add verb="*" path="*.sqlx" type="Core35.Components.QueryHandler, Core35Lib" />
</system.webserver>

  这个新引入的节点是<configuration>的直接子节点。

最终结果

  现在让我们在浏览器中输入一个sqlx文件的URL地址(如:http://localhost:57023/core35/Samples/Ch18/Handlers/sample.sqlx),试着查询一下,如果如下:

posted on 2011-04-28 22:42  辛勤的代码工  阅读(468)  评论(0编辑  收藏  举报