HttpApplication有19个标准事件,当到达第8个事件PostMapRequestHandler触发的时候,标志着已经获取到了处理请求的处理程序对象,在第11个事件PreRequestHandlerExecute之后,HttpApplication将执行这个处理程序。
问题:
- HttpApplication如何选择处理程序?
- 处理程序是什么对象?
- HttpApplication如何得到这个处理程序对象?
一、处理程序
针对不同的请求,Asp.net要有不同的处理,在Asp.net中通过各种处理程序来分别进行处理。通常情况下,根据请求的扩展名来确定处理程序。其实在Asp.net中已经预定义了很多处理程序。
1、处理程序与HttpApplication的关系
在Asp.net中,请求的真正处理是在处理程序这个环节。处理程序负责完成实际的请求处理工作,对于开发者来说,大多数的开发工作都是围绕着处理程序展开的。实际上,接触到HttpApplication事件处理的时候不多,HttpApplication除处理程序之外的作用,都可以理解为是为处理程序进行处理前的预处理和处理后的工作。
处理程序在不同的网站开发技术中,有着不同的名称,在Asp.net中,称为HttpHandler。
2、处理程序接口IHttpHandler和IHttpAsyncHandler
在Asp.net中,所有的处理程序类必须实现IHttpHandler接口或者实现IHttpAsyncHandler接口,一个同步,一个异步。
IHttpHandler的定义如下:
public interface IHttpHandler { bool IsReusable { get; } void ProcessRequest(HttpContext context); }
一般处理程序里实现的,就是这个东西。
- ProcessRequest是IHttpHandler接口的主要方法,接收并通过一个HttpContext类型的请求上下文对象,处理程序可以得到关于处理请求所需的信息。通过 HttpContext的Response属性可以得到响应的对象,用以向客户端返回服务器处理的结果。
- IsReusable属性表示:“当这个处理程序对象在使用之后,是否还可以被缓存起来,在以后的请求处理中再次使用”,这个属性主要用来配合处理程序工厂使用。
异步的处理程序派生自同步的处理程序接口,接口定义如下:
public interface IHttpAsyncHandler : IHttpHandler { IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData); void EndProcessRequest(IAsyncResult result); }
比IHttpHandler增加了两个方法,BeginProcessRequest和EndProcessRequest方法。跟其他异步方法使用一样。
3、在处理程序中使用会话
处理程序是Asp.net网站中处理请求的基本单位。在默认情况下,处理程序中甚至不能使用会话状态,这样可以提高网站处理的速度。对于需要读写会话状态的处理程序,必须实现一个特定的标记接口IRequireSessionState,这个接口定义在命名空间System.Web.SessionState中,其中没有定义任何成员,所以,实现这个接口并不需要在类中增加任何成员。类似地,同样定义在这个命名空间下的接口IReadOnlySessionState也没有定义任何程序,用来标志只需要读取会话状态的处理程序。
这种没有任何成员的接口,通常被称为标记接口,它的出现是由于.Net早期没有标签(Attribute)而昙花一现,在Asp.net中,这是仅有的一例。
HttpApplication的第9个事件AcquireRequestState事件和第13个事件ReleaseRequestState中,HttpApplication即通过判断已经获取得处理程序对象是否实现了这些接口来决定请求状态的处理工作。
4、处理程序工厂
实现了处理程序接口的类就可以被用来创建处理程序对象直接使用,如果再配合一个处理程序工厂,那么就可以实现处理程序对象的管理。
比如,创建一个处理程序对象池,就可以不用在每次使用处理程序的时候创建一个新的对象,而是可以从池中取出一个现有的对象直接使用,以提高效率。
在Asp.net中,作为处理程序工厂的类必须实现接口IHttpHandlerFactory,这个接口在命名空间System.Web下,定义为:
public interface IHttpHandlerFactory { IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated); void ReleaseHandler(IHttpHandler handler); }
其中GetHandler方法用来通过这个处理程序工厂获取一个处理程序对象,ReleaseHandler方法用来释放一个处理程序对象。
关系图如下:
5、注册处理程序
每一种处理程序用来处理一类请求,不同的请求类别通过请求的扩展名来进行区分,处理程序与请求之间的匹配关系在网站的配置文件web.config中通过配置参数来进行设置。system.web配置元素的子元素httpHandlers用来配置网站所使用的处理程序。httpHandlers元素可以包含三种子元素:add、remove和clear。
add子元素有三个必选的属性,作用如下:
- verb通过一个逗号(,)分割的HTTP请求类型列表来表示处理请求的类型,例如:GET,POST等;使用星号(*)表示处理所有类型的请求。
- path通过一个固定的URL路径或者一个使用星号(*)的通配符来匹配请求的URL,例如,使用*.aspx表示这个处理请求将处理所有扩展名为aspx的请求。
- type处理程序的类型名称,或者是处理程序工厂的类型名称,这个类型必须是类型的全名,包含命名空间、程序集(当类放在私有程序集时)。
- validate为可选的属性,如果设置为false,那么Asp.net在第一次匹配的请求调用之前,将不会试图加载这个类。
如果我们定义了一个处理程序类,这个类定义在命名空间MyHandler下,名为ValidateCodeHandler,这个类定义在私有程序集MyHandler.dll中,用来处理GET类型的请求,请求的扩展名为vc,那么配置参数如下:
<configuration> <system.web> <httpHandlers> <add verb="GET" path="*.vc" type="MyHandler.ValidateCodeHandler,MyHandler.dll"/> </httpHandlers> </system.web> </configuration>
在网站应用程序运行的时候,实际得到的配置文件来自于系统的machine.config,系统的web.config和网站自身的web.config合并。在web.config中Asp.net已经预先配置了57中处理程序的映射,程序员还可以通过处理程序接口扩展自定义的处理程序。
6、使用处理程序生成验证码
验证码是网站开发中常使用的安全技术,用于防止恶意的攻击。
我们通过一个处理程序来生成发送到客户端的验证码图片,完成这个处理程序需要以下步骤:
- 创建一个类库项目MyHandler,新的项目的默认命名空间也是MyHandler。
- 在类库项目MyHandler中添加一个新的类ValidateCodeHandler,实现IHttpHandler接口。为了在处理程序中使用Session状态管理,同时实现IRequiresSessionState接口。
- 在ProcessRequest方法中完成验证码生成的操作。
- 在网站项目中引用这个类库项目。
- 在网站项目的配置文件web.config中注册这个处理程序。
- 在需要验证码的页面上使用处理程序生成的验证码。
新建一个Asp.net Web程序:
public class ValidateCodeHandler : IHttpHandler,IRequiresSessionState { private static Random random = new Random(); public void ProcessRequest(HttpContext context) { context.Response.ContentType = "image/jpeg"; Image image = new Bitmap(60, 30); //生成随机数 int code = random.Next(1000, 10000); string codeString = code.ToString(); //使用会话状态 context.Session["Code"] = codeString; using (Graphics g = Graphics.FromImage(image)) { g.Clear(Color.WhiteSmoke); StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Center; g.DrawString(codeString, new Font("Arial", 14), Brushes.Blue, new RectangleF(0, 0, image.Width, image.Height), sf); } context.Response.ContentType = "image/jpeg"; image.Save(context.Response.OutputStream, ImageFormat.Jpeg); } public bool IsReusable { get { return false; } }
然后在web.config里面增加如下配置:
<httpHandlers> <add verb="GET" path="*.vc" type="WebApplication1.ValidateCodeHandler"/> </httpHandlers>
然后启动项目,随便打开一个本系统下.vc后缀的路径:
从项目中我们可以看到,所有.vc后缀的路径,Asp.net都会映射到这个类中处理。
二、一般处理程序
虽然通过标准的方式可以创建处理程序,但是实现的步骤比较复杂,为了方便网站开发中对于处理程序的应用,从Asp.net 2.0开始,Asp.net提供了称为一般处理程序的处理程序,允许我们私用比较简单的方式定义扩展名为ashx的专用处理程序。
通过Visual Studio 2010创建的一般处理程序将会生成两个文件Handler1.ashx和Handler1.ashx.cs。其中Handler1.ashx文件中的内容如下所示:
<%@ WebHandler Language="C#" Class="Handler" %>
对应的代码文件Handler1.ashx.cs中的内容如以下代码所示。可以非常清楚地看到,这就是一个实现IHttpHandler接口的类,如下:
public class Handler1 : IHttpHandler { public void ProcessRequest(HttpContext context) { context.Response.ContentType = "text/plain"; context.Response.Write("Hello World"); } public bool IsReusable { get { return false; } } }
一般处理程序实际上就是一个处理程序类,这个处理程序被Asp.net直接在系统的配置文件中映射到了ashx扩展名的请求上。这样,我们就不需要在配置文件中进行配置了。
1、一般处理程序工厂
对于一般处理程序来说,扩展名请求的程序并不需要程序员在web.config中进行配置,这是因为这个处理程序已经定义在系统配置文件的web.config。所以浏览器可以直接请求扩展名为ashx的地址:
<add path="*.ashx" verb="*" type="System.Web.UI.SimpleHandlerFactory" validate="True" />
通过配置文件,我们可以看到,对于扩展名为ashx的请求是通过定义在命名空间System.Web.UI下的SimpleHandlerFactory处理程序工厂来完成的。
当请求一个ashx扩展名的服务器上的资源的时候,SimpleHandlerFactory将找到对应的ashx文件,通过这个文件找到对应的处理程序,最后,SimpleHandlerFactory通过反射创建一个此类型的处理程序对象实例,并通过GetHandler方法返回给HttpApplication,完成最终的请求处理过程。
2、使用一般处理程序的场合
对于Asp.net网站来说,最常见的处理结果就是HTML网页,生成网页的工作通常使用扩展名为aspx的Web窗体来完成,对于处理结果不是HTML的请求,都可以通过一般处理程序来完成。例如生成RSS Fed,XML,图片等。
一般处理程序是Asp.net网站中最为简单、高效的处理程序,在处理返回类型不是HTML的请求中有着重要的作用。
3、使用一般处理程序生成验证码图片
例如,在前面实现的验证码处理程序用一般处理程序能够更快地完成,直接在ProcessRequest方法中实现功能就可以了,原来的第1、2、4、5步都可以省略。
public class Handler1 : IHttpHandler, IRequiresSessionState { private static Random random = new Random(); public void ProcessRequest(HttpContext context) { context.Response.ContentType = "image/jpeg"; Image image = new Bitmap(60, 30); //生成随机数 int code = random.Next(1000, 10000); string codeString = code.ToString(); //使用会话状态 context.Session["Code"] = codeString; using (Graphics g = Graphics.FromImage(image)) { g.Clear(Color.WhiteSmoke); StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Center; g.DrawString(codeString, new Font("Arial", 14), Brushes.Blue, new RectangleF(0, 0, image.Width, image.Height), sf); } context.Response.ContentType = "image/jpeg"; image.Save(context.Response.OutputStream, ImageFormat.Jpeg); } public bool IsReusable { get { return false; } } }
一般处理程序要使用Session也是要实现接口IRequiresSessionState的。
4、使用一般处理程序生成JSON
代码示例:
public class UploadPercentHandler : IHttpHandler { public void ProcessRequest(HttpContext context) { Person p = new Person(); p.Id = 1; p.Name = "撼地神牛"; JavaScriptSerializer jss = new JavaScriptSerializer(); string json = jss.Serialize(p); context.Response.ContentType = "application/json"; context.Response.Write(json); } public bool IsReusable { get { return false; } } } public class Person { public int Id { get; set; } public string Name { get; set; } }
输出:
三、页面处理程序
Asp.net使用模板的方式来生成一个处理程序。模板的扩展名为aspx,通过一个内置的处理程序工厂PageHandlerFactory将aspx形式的模板编译生成处理程序代码,然后,将这个处理程序返回给HttpApplication完成请求的处理。aspx形式的模板文件可以直接通过文本编辑器进行编辑。
1、页面处理工厂
在Asp.net的系统配置文件中,已经进行了如下的配置,可以看到,对于扩展名为aspx的请求,将由PageHandlerFactory这个处理程序工厂进行处理。
配置如下:
<add path="*.aspx" verb="*" type="System.Web.UI.PageHandlerFactory" validate="True" />
这个类定义在命名空间System.Web.UI下,具体的定义如下:
public class PageHandlerFactory : IHttpHandlerFactory
这是一个典型的处理程序工厂,用来返回一个实现IHttpHandler接口的处理程序对象实例。PageHandlerFactory将寻找匹配请求名称的aspx文件,然后将这个模板文件进行解析,通过代码生成机制生成相应的页面类。这个页面类派生自Page类,Page类定义在命名空间System.Web.UI下,类的定义为:
public class Page : TemplateControl,IHttpHandler
aspx模板文件与Page类和生成类之间的关系是:
PageHandlerFactory读取aspx文件,将aspx文件解析生成一个派生自Page的类;
aspx模板的解析和代码生成仅仅出现在第一次处理的时候,以后的请求将直接使用已经编译生成的程序集,所以,这个处理过程并不会降低网站的处理速度。
2、创建页面处理程序
在PageHandlerFactory的内部,通过PageParser这个类解析指定的aspx文件生成Page类的派生类,这个派生类用来创建页面处理程序的对象实例。这个类定义在命名空间System.Web.UI中,完整的定义如下:
public sealed class PageParser
PageParser有一个静态方法
GetCompiledPageInstance(string virtualPath,string inputFile,HttpContext context)
GetCompiledPageInstance方法内部又使用了BuildManager类来创建页面对象实例,这个类定义在命名空间System.Web.Compilation下,完整的类定义如下:
public sealed class BuildManager
BuildManager的CreateInstanceFromVirtualPath方法通过页面的虚拟路径通过代码生成得到派生的页面类,然后通过反射创建这个页面对象,代码如下:
public static Object CreateInstanceFromVirtualPath(string virtualPath,Type requiredBaseType)
3、生成的代码
对于一个aspx模板文件来说,一般情况下将对应存在一个同名的cs后台代码文件,其中定义了一个与页面同名的类,通过代码文件可以看到这个类派生自Page的类。
PageHandlerFactory通过aspx文件将生成两个类,一个为与后台代码中定义的类同名的部分类,这个部分类将与后台代码中定义的类在编译的时候合并为一个派生自Page的页面派生类。但是,在Asp.net中,创建实际页面对象的类并不是这个类,而是生成的第二个类,一般情况下,这个类的名字为页面的名字后面加上下划线和aspx。这才是实际创建页面对象的页面类。
假设有一个Person.aspx,那么应该存在一个对应的后台代码文件Person.aspx.cs,其中定义了Person这个Page的派生类。
通过PageHandlerFactory,Person.aspx将生成两个类,部分类Person和Person_aspx。其中,后台代码文件中的Person将和生成的部分类Person在编译的时候合并为一个完整的Person类定义,而Person_aspx则是从Person派生的一个页面类,用来创建实际的页面对象实例。
4、使用页面处理程序
由于页面处理程序是通过模板生成的,所以,大多数情况下,我们只要创建好这个aspx的模板就可以了,HttpApplication会通过PageHandlerFactory创建这个生成的处理程序,然后使用这个处理程序完成服务器的处理任务。现在,如果我们需要通过处理程序生成一个网页的话,只需要设计一个aspx页面即可。
在一些特殊的情况下,我们需要创建一些生成HTML输出的程序。但是这些程序并不希望能够被客户端直接请求,那么,可以借助PageParser或者BuildManager通过aspx格式的模板来创建一个自定义的处理程序完成处理任务。