如果要让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),试着查询一下,如果如下: