ASP.NET底层的初步认识与理解
最近在国外的网站乱走一通,发现一些比较好的文章,收集整理加于自己的理解,作为笔记形式记录下来,让以后自己有个回忆。
ASP.NET是一个非常强大的构建Web应用的平台,它提供了极大的灵活性和能力以致于可以用它来构建所有类型的Web应用.绝大多数的人只熟悉高层的框架如WebForms和WebServices-这些都在ASP.NET层次结构在最高层.在这篇文章中我将会讨论ASP.NET的底层机制并解释请求(request)是怎么从Web服务器传送到ASP.NET运行时然后如何通过ASP.NET管道来处理请求.
ASP.NET是一个请求处理引擎.它接收一个发送过来的请求,把它传给内部的管道直到终点,作为一个开发人员的你可以在这里附加一些代码来处理请求.这个引擎是和HTTP/Web服务器完全分隔的.事实上,HTTP运行时是一个组件,使你可以摆脱IIS或者任何其他的服务器程序,将你自己的程序寄宿在内.
运行时提供了一个复杂但同时非常优雅的在管道中路由请求的机制.其中有很多相关的对象,大多数都是可扩展的(通过继承或者事件接口),在几乎所有的处理流程上都是如此.所以这个框架具有高度可扩展性.通过这个机制,挂接到非常底层的接口(比如缓存,认证和授权)都变得可能了.你甚至可以在预处理或者处理后过滤内容,也可以简单的将符合特殊标记的请求直接路由你的代码或者另一个URL上.存在着许多不同的方法来完成同一件事,但是所有这些方法都是可以简单直接地实现的,同时还提供了灵活性,可以得到最好的性能和开发的简单性.
整个ASP.NET引擎是完全建立在托管代码上的,所有的扩展功能也是通过托管代码扩展来提供的.这是对.NET框架具有构建复杂而且高效的框架的能力的最好的证明.ASP.NET最令人印象深刻的地方是深思熟虑的设计,使得框架非常的容易使用,又能提供挂接到请求处理的几乎所有部分的能力.
ASP.NET在微软的平台上就是通过ISAPI扩展来和IIS进行交互的,这个扩展寄宿着.NET运行时和ASP.NET运行时.ISAPI提供了核心的接口,ASP.NET使用非托管的ISAPI代码通过这个接口来从Web服务器获取请求,并发送响应回客户端.ISAPI提供的内容可以通过通用对象(例如HttpRequest和HttpResponse)来获取,这些对象通过一个定义良好并有很好访问性的接口来暴露非托管数据.
当用户发送一个URL请求时,在Web服务器端,IIS5或6,获得这个请求.在最底层,ASP.NET和IIS通过ISAPI扩展进行交互.在ASP.NET环境中这个请求通常被路由到一个扩展名为.aspx的页面上,但是这个流程是怎么工作的完全依赖于处理特定扩展名的HTTP Handler是怎么实现的.在IIS中.aspx通过’应用程序扩展’(又称为脚本映射)被映射到ASP.NET的ISAPI扩展DLL-aspnet_isapi.dll.每一个请求都需要通过一个被注册到aspnet_isapi.dll的扩展名来触发ASP.NET(来处理这个请求).
ISAPI是底层的非托管Win32 API.ISAPI定义的接口非常简单并且是为性能做了优化的.它们是非常底层的-处理指针和函数指针表来进行回调-但是它们提供了最底层和面向效率的接口,使开发者和工具提供商可以用它来挂接到IIS上.因为ISAPI非常底层所以它并不适合来开发应用级的代码,而且ISAPI倾向于主要被用于桥接接口,向上层工具提供应用服务器类型的功能.
下面来介绍HttpRuntime,HttpContext,HttpApplication
当一个请求到来时,它被路由到ISAPIRuntime.ProcessRequest()方法.这个方法调用HttpRuntime.ProcessRequest方法,它作一些重要的事情
为请求创建一个新的HttpContext实例
获取一个HttpApplication实例
调用HttpApplication.Init()方法来设置管道的事件
Init()方法触发开始ASP.NET管道处理的HttpApplication.ResumeProcessing()方法
首先一个新的HttpContext对象被创建并用来传递ISAPIWorkerRequest,这个上下文在整个请求的生命周期总都是可用的并总可以通过静态属性.
HttpContext.Currect来访问.正像名字所暗示的那样,HttpContext对象代表了当前活动请求的上下文因为他包含了在请求生命周期中所有典型的你需要访问的重要对象:Request,Response,Application,Server,Cache.在请求处理的任何时候HttpContext.Current给你访问所有这些的能力.
HttpContext对象也包含一个非常有用的Items集合,你可以用它来保存针对特定请求的数据.上下文对象在请求周期的开始时被创建,在请求结束时被释放,所有在Items集合中保存的数据只在这个特定的请求中可用.一个很好的使用的例子是请求日志机制,当你通过想通过在Global.asax中挂接Application_BeginRequest和Application_EndRequest方法记录请求的开始和结束时间(象在列表3中显示的那样).HttpContext对你就非常有用了-如果你在请求或页面处理的不同部分需要数据,你自由的使用它.
protected void Application_BeginRequest(Object sender, EventArgs e)
{
if (App.Configuration.LogWebRequests)
{
Context.Items.Add("WebLog_StartTime",DateTime.Now);
}
}
protected void Application_EndRequest(Object sender, EventArgs e)
{
if (App.Configuration.LogWebRequests)
{
try
{
TimeSpan Span = DateTime.Now.Subtract((DateTime) Context.Items["WebLog_StartTime"] );
int MiliSecs = Span.TotalMilliseconds;
WebRequestLog.Log(App.Configuration.ConnectionString, true, MilliSecs);
}
}
}
HttpApplication
每个请求都被路由到一个HttpApplication对象上.HttpApplicationFactory类根据应用程序的负载为你的ASP.NET应用创建一个HttpApplication对象池并为每个请求分发HttpApplication对象的引用.对象池的大小受machine.config文件中ProcessModel键中的MaxWorkerThreads设置限制.
HttpApplication是你的Web程序的外部包装器,而且它被映射到在Global.asax里面定义的类上.它是进入HttpRuntime的第一个入口点.如果你查看Global.asax(或者对应的代码类)你会发现这个类直接继承自HttpApplication:
HttpApplication的主要职责是作为Http管道的事件控制器,所以它的接口主要包含的是事件.事件挂接是非常广泛的,大概包括以下这些:
BeginRequest
AuthenticateRequest
AuthorizeRequest
ResolveRequestCache
AquireRequestState
PreRequestHandlerExecute
PostRequestHandlerExecute
ReleaseRequestState
UpdateRequestCache
EndRequest
HttpModule和HttpHandler两者都是在HttpApplication.Init()函数调用的一部分中被载入并附加到调用链上
httpApplication它本身对发送给应用程序的数据一无所知-它只是一个通过事件来通讯的消息对象.它触发事件并通过HttpContext对象来向被调用函数传递消息.实际的当前请求的状态数据由前面提到的HttpContext对象维护.它提供了所有请求专有的数据并从进入管道开始到结束一直跟随请求
一旦管道被启动,HttpApplication开始象图六那样一个个的触发事件.每个事件处理器被触发,如果事件被挂接,这些处理器将执行它们自己的任务.这个处理的主要任务是最终调用挂接到此特定请求的HttpHandler.处理器(handler)是ASP.NET请求的核心处理机制,通常也是所有应用程序级别的代码被执行的地方.记住ASP.NET页面和Web服务框架都是作为HttpHandler实现,这里也是处理请求的的核心之处.模块(module)趋向于成为一个传递给处理器(handler)的上下文的预处理或后处理器.ASP.NET中典型的默认处理器包括预处理的认证,缓存以及后处理中各种不同的编码机制.
虽然HttpModule看上去很像ISAPI过滤器,它们都检查每个通过ASP.NET应用的请求,但是它们只检查映射到单个特定的ASP.NET应用或虚拟目录的请求,也就是只能检查映射到ASP.NET的请求.这样你可以检查所有ASPX页面或者其他任何映射到ASP.NET的扩展名.
实现一个HTTP模块是非常简单的:你必须实现之包含两个函数(Init()和Dispose())的IHttpModule接口.传进来的事件参数中包含指向HTTPApplication对象的引用,这给了你访问HttpContext对象的能力.在这些方法上你可以挂接到HttpApplication事件上.例如,如果你想挂接AuthenticateRequest事件到一个模块上
总的来说w3wp.exe调用.NET类库进行具体处理,顺序如下:ISAPIRuntim, HttpRuntime, HttpApplicationFactory, HttpApplication, HttpModule, HttpHandlerFactory, HttpHandler
有时间再对每个对象正进深入理解.
下面最先介绍HttpRuntime的Web.config里的配置
<httpRuntime
executionTimeout = "number"
maxRequestLength = "number"
requestLengthDiskThreshold = "number"
useFullyQualifiedRedirectUrl = "[True|False]"
minFreeThreads = "number"
minLocalRequestFreeThreads = "number"
appRequestQueueLimit = "number"
enableKernelOutputCache = "[True|False]"
enableVersionHeader = "[True|False]"
apartmentThreading = "[True|False]"
requireRootedSaveAsPath = "[True|False]"
enable = "[True|False]"
sendCacheControlHeader = "[True|False]"
shutdownTimeout = "number"
delayNotificationTimeout = "number"
waitChangeNotification = "number"
maxWaitChangeNotification = "number"
enableHeaderChecking = "[True|False]"
/>
通过上面的配置说明, 下面是在Web.Config里节点的设置
<configuration>
<system.web>
<httpRuntime maxRequestLength="4000"
enable = "True"
requestLengthDiskThreshold="512
useFullyQualifiedRedirectUrl="True"
executionTimeout="45"
versionHeader="1.1.4128"/>
</system.web>
</configuration>
IIS 所收到的对某 Microsoft ASP.NET 页面的每个请求都被移交给 ASP.NET HTTP 管线。HTTP 管线由一系列托管对象组成,这些对象按顺序处理该请求,并完成从 URL 到普通 HTML 文本的转换。HTTP 管线的入口点是 HttpRuntime 类。ASP.NET 基础结构为辅助进程中所承载的每个 AppDomain 创建此类的一个实例请注意,该辅助进程为当前正在运行的每个 ASP.NET 应用程序维护一个不同的 AppDomain。
要激活 HTTP 管道,可以创建一个 HttpRuntime 类的新实例,然后调用其 ProcessRequest 方法。一个完整的页面请求会包括下面的流程:
首先被WWW服务器截获(inetinfo.exe进程), 该进程首先判断页面后缀, 然后根据IIS中配置决定调用具体的扩展程序。aspx就会调用aspnet_isapi.dll,
然后由aspnet_isapi.dll发送给w3wp.exe(iis 工作者进程,IIS6.0中叫做 w3wq.exe,IIS5.0中叫做 aspnet_wp.exe)。
接下来在w3wp.exe调用.NET类库进行具体处理,顺序如下:ISAPIRuntim, HttpRuntime, HttpApplicationFactory, HttpApplication, HttpModule, HttpHandlerFactory, HttpHandler
ISAPIRuntime:主要作用是调用一些非托管代码生成HttpWorkerRequest对象,HttpWorkerRequest对象包含当前请求的所有信息,然后传递给HttpRuntime
HttpRuntime:根据HttpWorkerRequest对象生成HttpContext,HttpContext包含request、response等属性, 再调用HttpApplicationFactory来生成IHttpHandler, 调用HttpApplication对象执行请求
HttpApplicationFactory: 生成一个HttpApplication对象
HttpApplication:进行HttpModule的初始化,HttpApplication创建针对此Http请求的 HttpContext对象
HttpModule: 当一个HTTP请求到达HttpModule时,整个ASP.NET Framework系统还并没有对这个HTTP请求做任何处理,也就是说此时对于HTTP请求来讲,HttpModule是一个HTTP请求的“必经之路”,所以可以在这个HTTP请求传递到真正的请求处理中心(HttpHandler)之前附加一些需要的信息在这个HTTP请求信息之上,或者针对截获的这个HTTP请求信息作一些额外的工作,或者在某些情况下干脆终止满足一些条件的HTTP请求,从而可以起到一个Filter过滤器的作用。
HttpHandlerFactory:把用户request 转发到HttpHandlerFactory,再由HttpHandlerFactory实例化HttpHandler对象来相应request
HttpHandle:Http处理程序,处理页面请求
从上面看出HttpRuntime其中有一个ProcessRequest 方法
public static void ProcessRequest(HttpWorkerRequest wr); //驱动所有 ASP.NET Web 处理执行。伪代码如下:
public static void HttpRuntime.ProcessRequest(HttpWorkerRequest wr)
{
// 检查当前调用者有没有作为ASP.NET宿主(Host)的权限
InternalSecurityPermissions.AspNetHostingPermissionLevelMedium.Demand();
if(wr == null)
{
throw new ArgumentNullException("custom");
}
RequestQueue queue = HttpRuntime._theRuntime._requestQueue;
if(queue != null)
{
// 将参数中的Web页面请求放入请求队列中, 并从队列中使用FIFO策略获取一个页面请求
wr = queue.GetRequestToExecute(wr);
}
if(wr != null)
{
HttpRuntime.CalculateWaitTimeAndUpdatePerfCounter(wr); // 更新性能计数器
HttpRuntime.ProcessRequestNow(wr); // 实际完成页面请求工作
}
}
ProcessRequestNow函数则直接调用缺省HttpRuntime实例的ProcessRequestInternal函数完成实际页面请求工作,伪代码如下:
internal static void HttpRuntime.ProcessRequestNow(HttpWorkerRequest wr)
{
HttpRuntime._theRuntime.ProcessRequestInternal(wr);
}
ProcessRequestInternal函数逻辑稍微复杂一些,大致可分为四个部分:
检查当前HttpRuntime实例是否第一次被调用,如果是第一次调用则通过FirstRequestInit函数初始化
调用HttpResponse.InitResponseWriter函数初始化页面请求的返回对象HttpWorkerRequest.Response
调用HttpApplicationFactory.GetApplicationInstance函数获取当前 Web 应用程序实例
使用Web应用程序实例完成实际的页面请求工作
伪代码如下:
private void HttpRuntime.ProcessRequestInternal(HttpWorkerRequest wr)
{
// 构造 HTTP 调用上下文对象
HttpContext ctxt = new HttpContext(wr, 0);
// 设置发送结束异步回调函数
wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, ctxt);
// 更新请求计数器
Interlocked.Increment(&(this._activeRequestCount));
try
{
// 检查当前HttpRuntime实例是否第一次被调用
if(this._beforeFirstRequest)
{
lock(this)
{
// 使用 Double-Checked 模式 避免冗余锁定
if(this._beforeFirstRequest)
{
this._firstRequestStartTime = DateTime.UtcNow;
this.FirstRequestInit(ctxt); // 初始化当前 HttpRuntime 运行时环境
this._beforeFirstRequest = false;
}
}
}
// 根据配置文件设置,扮演具有较高特权的角色
ctxt.Impersonation.Start(true, false);
try
{
// 初始化页面请求的返回对象
ctxt.Response.InitResponseWriter();
}
finally
{
ctxt.Impersonation.Stop();
}
// 获取当前 Web 应用程序实例
IHttpHandler handler = HttpApplicationFactory.GetApplicationInstance(ctxt);
if (handler == null)
{
throw new HttpException(HttpRuntime.FormatResourceString("Unable_create_app_object"));
}
// 使用Web应用程序实例完成实际的页面请求工作
if((handler as IHttpAsyncHandler) != null)
{
IHttpAsyncHandler asyncHandler = ((IHttpAsyncHandler) handler);
ctxt.AsyncAppHandler = asyncHandler;
// 使用异步处理机制
asyncHandler.BeginProcessRequest(ctxt, this._handlerCompletionCallback, ctxt);
}
else
{
handler.ProcessRequest(ctxt);
this.FinishRequest(ctxt.WorkerRequest, ctxt, null);
}
}
catch(Exception E)
{
ctxt.Response.InitResponseWriter();
this.FinishRequest(wr, ctxt, E);
}
}
HttpRuntime.ProcessRequestInternal函数中调用了HttpApplicationFactory.GetApplicationInstance函数获取当前 Web 应用程序实例。至少HttpRuntime已经完完成,将转进HttpApplicationFactory阶段流程。大家可以看到,围绕HttpRuntime的函数都有一个HttpWorkerRequest参数,下面简单介绍一下这个参数的作用。在ISAPIRuntime阶段,调用一些非托管代码生成HttpWorkerRequest对象,该对象包含当前请求的所有信息,然后传递给HttpRuntime,这里生成的HttpWorkerRequest对象可以直接在我们的页面里调用.
IServiceProvider provider=(IServiceProvider)HttpContext.Current;
HttpWorkerRequest wr=(HttpWorkerRequest)provider.GetService(typeof(HttpWorkerRequest));
然后可以通过wr来调用里面的方法。关于HttpWorkerRequest类里面包含的方法,从其元数据里面可以看到,如下:
public abstract class HttpWorkerRequest
{
// 摘要:
// 指定 AcceptHTTP 标头的索引号。
public const int HeaderAccept = 20;
//
// 摘要:
// 指定 Accept-CharsetHTTP 标头的索引号。
public const int HeaderAcceptCharset = 21;
//
// 摘要:
// 指定 Accept-EncodingHTTP 标头的索引号。
public const int HeaderAcceptEncoding = 22;
//
// 摘要:
// 指定 Accept-LanguageHTTP 标头的索引号。
public const int HeaderAcceptLanguage = 23;
//
// 摘要:
// 指定 Accept-RangesHTTP 标头的索引号。
public const int HeaderAcceptRanges = 20;
//
// 摘要:
// 指定 AgeHTTP 标头的索引号。
public const int HeaderAge = 21;
//
// 摘要:
// 指定 AllowHTTP 标头的索引号。
public const int HeaderAllow = 10;
//
// 摘要:
// 指定 AuthorizationHTTP 标头的索引号。
public const int HeaderAuthorization = 24;
//
// 摘要:
// 表示 HTTPCache-ControlHTTP 标头的索引。
public const int HeaderCacheControl = 0;
//
// 摘要:
// 指定 ConnectionHTTP 标头的索引号。
public const int HeaderConnection = 1;
//
// 摘要:
// 指定 Content-EncodingHTTP 标头的索引号。
public const int HeaderContentEncoding = 13;
//
// 摘要:
// 指定 Content-LanguageHTTP 标头的索引号。
public const int HeaderContentLanguage = 14;
//
// 摘要:
// 指定 Content-LengthHTTP 标头的索引号。
public const int HeaderContentLength = 11;
//
// 摘要:
// 指定 Content-LocationHTTP 标头的索引号。
public const int HeaderContentLocation = 15;
//
// 摘要:
// 指定 Content-MD5HTTP 标头的索引号。
public const int HeaderContentMd5 = 16;
//
// 摘要:
// 指定 Content-RangeHTTP 标头的索引号。
public const int HeaderContentRange = 17;
//
// 摘要:
// 指定 Content-TypeHTTP 标头的索引号。
public const int HeaderContentType = 12;
//
// 摘要:
// 指定 CookieHTTP 标头的索引号。
public const int HeaderCookie = 25;
//
// 摘要:
// 指定 DateHTTP 标头的索引号。
public const int HeaderDate = 2;
//
// 摘要:
// 指定 ETagHTTP 标头的索引号。
public const int HeaderEtag = 22;
//
// 摘要:
// 指定 ExceptHTTP 标头的索引号。
public const int HeaderExpect = 26;
//
// 摘要:
// 指定 ExpiresHTTP 标头的索引号。
public const int HeaderExpires = 18;
//
// 摘要:
// 指定 FromHTTP 标头的索引号。
public const int HeaderFrom = 27;
//
// 摘要:
// 指定 HostHTTP 标头的索引号。
public const int HeaderHost = 28;
//
// 摘要:
// 指定 If-MatchHTTP 标头的索引号。
public const int HeaderIfMatch = 29;
//
// 摘要:
// 指定 If-Modified-SinceHTTP 标头的索引号。
public const int HeaderIfModifiedSince = 30;
//
// 摘要:
// 指定 If-None-MatchHTTP 标头的索引号。
public const int HeaderIfNoneMatch = 31;
//
// 摘要:
// 指定 If-RangeHTTP 标头的索引号。
public const int HeaderIfRange = 32;
//
// 摘要:
// 指定 If-Unmodified-SinceHTTP 标头的索引号。
public const int HeaderIfUnmodifiedSince = 33;
//
// 摘要:
// 指定 Keep-AliveHTTP 标头的索引号。
public const int HeaderKeepAlive = 3;
//
// 摘要:
// 指定 Last-ModifiedHTTP 标头的索引号。
public const int HeaderLastModified = 19;
//
// 摘要:
// 指定 LocationHTTP 标头的索引号。
public const int HeaderLocation = 23;
//
// 摘要:
// 指定 Max-ForwardsHTTP 标头的索引号。
public const int HeaderMaxForwards = 34;
//
// 摘要:
// 指定 PragmaHTTP 标头的索引号。
public const int HeaderPragma = 4;
//
// 摘要:
// 指定 Proxy-AuthenticateHTTP 标头的索引号。
public const int HeaderProxyAuthenticate = 24;
//
// 摘要:
// 指定 Proxy-AuthorizationHTTP 标头的索引号。
public const int HeaderProxyAuthorization = 35;
//
// 摘要:
// 指定 RangeHTTP 标头的索引号。
public const int HeaderRange = 37;
//
// 摘要:
// 指定 RefererHTTP 标头的索引号。
public const int HeaderReferer = 36;
//
// 摘要:
// 指定 Retry-AfterHTTP 标头的索引号。
public const int HeaderRetryAfter = 25;
//
// 摘要:
// 指定 ServerHTTP 标头的索引号。
public const int HeaderServer = 26;
//
// 摘要:
// 指定 Set-CookieHTTP 标头的索引号。
public const int HeaderSetCookie = 27;
//
// 摘要:
// 指定 TEHTTP 标头的索引号。
public const int HeaderTe = 38;
//
// 摘要:
// 指定 TrailerHTTP 标头的索引号。
public const int HeaderTrailer = 5;
//
// 摘要:
// 指定 Transfer-EncodingHTTP 标头的索引号。
public const int HeaderTransferEncoding = 6;
//
// 摘要:
// 指定 UpgradeHTTP 标头的索引号。
public const int HeaderUpgrade = 7;
//
// 摘要:
// 指定 User-AgentHTTP 标头的索引号。
public const int HeaderUserAgent = 39;
//
// 摘要:
// 指定 VaryHTTP 标头的索引号。
public const int HeaderVary = 28;
//
// 摘要:
// 指定 ViaHTTP 标头的索引号。
public const int HeaderVia = 8;
//
// 摘要:
// 指定 WarningHTTP 标头的索引号。
public const int HeaderWarning = 9;
//
// 摘要:
// 指定 WWW-AuthenticateHTTP 标头的索引号。
public const int HeaderWwwAuthenticate = 29;
//
// 摘要:
// 指定请求的原因。
public const int ReasonCachePolicy = 2;
//
// 摘要:
// 指定请求的原因。
public const int ReasonCacheSecurity = 3;
//
// 摘要:
// 指定请求的原因。
public const int ReasonClientDisconnect = 4;
//
// 摘要:
// 指定请求的原因。默认值为 System.Web.HttpWorkerRequest.ReasonResponseCacheMiss。
public const int ReasonDefault = 0;
//
// 摘要:
// 指定请求的原因。
public const int ReasonFileHandleCacheMiss = 1;
//
// 摘要:
// 指定请求的原因。
public const int ReasonResponseCacheMiss = 0;
//
// 摘要:
// 指定 MaximumHTTP 请求标头的索引号。
public const int RequestHeaderMaximum = 40;
//
// 摘要:
// 指定 MaximumHTTP 响应标头的索引号。
public const int ResponseHeaderMaximum = 30;
// 摘要:
// 初始化 System.Web.HttpWorkerRequest 类的新实例。
protected HttpWorkerRequest();
// 摘要:
// 获取 Machine.config 文件的完整物理路径。
//
// 返回结果:
// Machine.config 文件的物理路径。
public virtual string MachineConfigPath { get; }
//
// 摘要:
// 获取 ASP.NET 二进制文件的安装目录的物理路径。
//
// 返回结果:
// ASP.NET 二进制文件的物理目录。
public virtual string MachineInstallDirectory { get; }
//
// 摘要:
// 获取当前请求的 Windows 跟踪 ID 的对应事件跟踪。
//
// 返回结果:
// 当前 ASP.NET 请求的跟踪 ID。
public virtual Guid RequestTraceIdentifier { get; }
//
// 摘要:
// 获取根 Web.config 文件的完整物理路径。
//
// 返回结果:
// 根 Web.config 文件的物理路径。
public virtual string RootWebConfigPath { get; }
// 摘要:
// 终止与客户端的连接。
public virtual void CloseConnection();
//
// 摘要:
// 由运行库使用以通知 System.Web.HttpWorkerRequest 当前请求的请求处理已完成。
public abstract void EndOfRequest();
//
// 摘要:
// 将所有挂起的响应数据发送到客户端。
//
// 参数:
// finalFlush:
// 如果这将是最后一次刷新响应数据,则为 true;否则为 false。
public abstract void FlushResponse(bool finalFlush);
//
// 摘要:
// 返回当前正在执行的服务器应用程序的虚拟路径。
//
// 返回结果:
// 当前应用程序的虚拟路径。
public virtual string GetAppPath();
//
// 摘要:
// 返回当前正在执行的服务器应用程序的物理路径。
//
// 返回结果:
// 当前应用程序的物理路径。
public virtual string GetAppPathTranslated();
//
// 摘要:
// 在派生类中被重写时,返回当前 URL 的应用程序池 ID。
//
// 返回结果:
// 始终返回 null。
public virtual string GetAppPoolID();
//
// 摘要:
// 获取从客户端读入的字节数。
//
// 返回结果:
// 包含读取的字节数的 Long。
public virtual long GetBytesRead();
//
// 摘要:
// 在派生类中被重写时,从客户端发出的请求获取证书字段(以 X.509 标准指定)。
//
// 返回结果:
// 包含整个证书内容流的字节数组。
public virtual byte[] GetClientCertificate();
//
// 摘要:
// 获取证书颁发者(以二进制格式表示)。
//
// 返回结果:
// 包含以二进制格式表示的证书颁发者的字节数组。
public virtual byte[] GetClientCertificateBinaryIssuer();
//
// 摘要:
// 在派生类中被重写时,返回用于编码客户端证书的 System.Text.Encoding 对象。
//
// 返回结果:
// 表示为整数的证书编码。
public virtual int GetClientCertificateEncoding();
//
// 摘要:
// 在派生类中被重写时,获取与客户端证书关联的 PublicKey 对象。
//
// 返回结果:
// 一个 PublicKey 对象。
public virtual byte[] GetClientCertificatePublicKey();
//
// 摘要:
// 在派生类中被重写时,则获取证书开始生效的日期。此日期随区域设置的不同而不同。
//
// 返回结果:
// 表示证书生效时间的 System.DateTime 对象。
public virtual DateTime GetClientCertificateValidFrom();
//
// 摘要:
// 获取证书到期日期。
//
// 返回结果:
// 表示证书失效日期的 System.DateTime 对象。
public virtual DateTime GetClientCertificateValidUntil();
//
// 摘要:
// 在派生类中被重写时,返回当前连接的 ID。
//
// 返回结果:
// 始终返回 0。
public virtual long GetConnectionID();
//
// 摘要:
// 在派生类中被重写时,返回所请求的 URI 的虚拟路径。
//
// 返回结果:
// 请求的 URI 的路径。
public virtual string GetFilePath();
//
// 摘要:
// 返回请求的 URI 的物理文件路径(并将其从虚拟路径转换成物理路径:例如,从“/proj1/page.aspx”转换成“c:\dir\page.aspx”)
//
// 返回结果:
// 请求的 URI 的已转换的物理文件路径。
public virtual string GetFilePathTranslated();
//
// 摘要:
// 返回请求标头的指定成员。
//
// 返回结果:
// 请求标头中返回的 HTTP 谓词。
public abstract string GetHttpVerbName();
//
// 摘要:
// 提供对请求的 HTTP 版本(如“HTTP/1.1”)的访问。
//
// 返回结果:
// 请求标头中返回的 HTTP 版本。
public abstract string GetHttpVersion();
//
// 摘要:
// 返回与指定的索引相对应的标准 HTTP 请求标头。
//
// 参数:
// index:
// 标头的索引。例如,System.Web.HttpWorkerRequest.HeaderAllow 字段。
//
// 返回结果:
// HTTP 请求标头。
public virtual string GetKnownRequestHeader(int index);
//
// 摘要:
// 返回指定的 HTTP 请求标头的索引号。
//
// 参数:
// header:
// 标头的名称。
//
// 返回结果:
// 在 header 参数中指定的 HTTP 请求标头的索引号。
public static int GetKnownRequestHeaderIndex(string header);
//
// 摘要:
// 返回指定的 HTTP 请求标头的名称。
//
// 参数:
// index:
// 标头的索引号。
//
// 返回结果:
// 在 index 参数中指定的 HTTP 请求标头的名称。
public static string GetKnownRequestHeaderName(int index);
//
// 摘要:
// 返回指定的 HTTP 响应标头的索引号。
//
// 参数:
// header:
// HTTP 标头的名称。
//
// 返回结果:
// 在 header 参数中指定的 HTTP 响应标头的索引号。
public static int GetKnownResponseHeaderIndex(string header);
//
// 摘要:
// 返回指定的 HTTP 响应标头的名称。
//
// 参数:
// index:
// 标头的索引号。
//
// 返回结果:
// 在 index 参数中指定的 HTTP 响应标头的名称。
public static string GetKnownResponseHeaderName(int index);
//
// 摘要:
// 提供对请求标头的指定成员的访问。
//
// 返回结果:
// 请求标头中返回的服务器 IP 地址。
public abstract string GetLocalAddress();
//
// 摘要:
// 提供对请求标头的指定成员的访问。
//
// 返回结果:
// 请求标头中返回的服务器端口号。
public abstract int GetLocalPort();
//
// 摘要:
// 返回具有 URL 扩展的资源的其他路径信息。即对于路径 /virdir/page.html/tail,GetPathInfo 值为 /tail。
//
// 返回结果:
// 资源的附加路径信息。
public virtual string GetPathInfo();
//
// 摘要:
// 返回 HTTP 请求正文已被读取的部分。
//
// 返回结果:
// HTTP 请求正文已被读取的部分。
public virtual byte[] GetPreloadedEntityBody();
//
// 摘要:
// 使用指定的缓冲区数据和字节偏移量获取 HTTP 请求正文当前已被读取的部分。
//
// 参数:
// buffer:
// 要读取的数据。
//
// offset:
// 开始读取的位置的字节偏移量。
//
// 返回结果:
// HTTP 请求正文已被读取的部分。
public virtual int GetPreloadedEntityBody(byte[] buffer, int offset);
//
// 摘要:
// 获取 HTTP 请求正文当前已被读取部分的长度。
//
// 返回结果:
// 一个整数,包含当前已读取的 HTTP 请求正文的长度。
public virtual int GetPreloadedEntityBodyLength();
//
// 摘要:
// 在派生类中被重写时,返回 HTTP 协议(HTTP 或 HTTPS)。
//
// 返回结果:
// 如果 System.Web.HttpWorkerRequest.IsSecure() 方法为 true,则为 HTTPS;否则,为 HTTP。
public virtual string GetProtocol();
//
// 摘要:
// 返回请求 URL 中指定的查询字符串。
//
// 返回结果:
// 请求查询字符串。
public abstract string GetQueryString();
//
// 摘要:
// 在派生类中被重写时,以字节数组的形式返回响应查询字符串。
//
// 返回结果:
// 包含响应的字节数组。
public virtual byte[] GetQueryStringRawBytes();
//
// 摘要:
// 返回附加了查询字符串的请求标头中包含的 URL 路径。
//
// 返回结果:
// 请求标头的原始 URL 路径。
public abstract string GetRawUrl();
//
// 摘要:
// 提供对请求标头的指定成员的访问。
//
// 返回结果:
// 客户端的 IP 地址。
public abstract string GetRemoteAddress();
//
// 摘要:
// 在派生类中被重写时,返回客户端计算机的名称。
//
// 返回结果:
// 客户端计算机的名称。
public virtual string GetRemoteName();
//
// 摘要:
// 提供对请求标头的指定成员的访问。
//
// 返回结果:
// 客户端的 HTTP 端口号。
public abstract int GetRemotePort();
//
// 摘要:
// 在派生类中被重写时,返回请求的原因。
//
// 返回结果:
// 原因代码。默认值为 ReasonResponseCacheMiss。
public virtual int GetRequestReason();
//
// 摘要:
// 在派生类中被重写时,返回本地服务器的名称。
//
// 返回结果:
// 本地服务器的名称。
public virtual string GetServerName();
//
// 摘要:
// 从与请求关联的服务器变量词典返回单个服务器变量。
//
// 参数:
// name:
// 请求的服务器变量的名称。
//
// 返回结果:
// 请求的服务器变量。
public virtual string GetServerVariable(string name);
//
// 摘要:
// 返回一个字符串,该字符串描述指定的 HTTP 状态代码的名称。
//
// 参数:
// code:
// HTTP 状态代码。
//
// 返回结果:
// 状态说明。例如,System.Web.HttpWorkerRequest.GetStatusDescription(System.Int32) (404)
// 返回“未找到”。
public static string GetStatusDescription(int code);
//
// 摘要:
// 获取整个 HTTP 请求正文的长度。
//
// 返回结果:
// 包含整个 HTTP 请求正文的长度的整数。
public virtual int GetTotalEntityBodyLength();
//
// 摘要:
// 返回非标准的 HTTP 请求标头值。
//
// 参数:
// name:
// 标头名称。
//
// 返回结果:
// 标头值。
public virtual string GetUnknownRequestHeader(string name);
//
// 摘要:
// 获取所有非标准的 HTTP 标头的名称/值对。
//
// 返回结果:
// 标头的名称/值对的数组。
[CLSCompliant(false)]
public virtual string[][] GetUnknownRequestHeaders();
//
// 摘要:
// 返回请求的 URI 的虚拟路径。
//
// 返回结果:
// 请求的 URI 的路径。
public abstract string GetUriPath();
//
// 摘要:
// 当在派生类中被重写时,返回当前连接的上下文 ID。
//
// 返回结果:
// 始终返回 0。
public virtual long GetUrlContextID();
//
// 摘要:
// 在派生类中被重写时,返回客户端的模拟标记。
//
// 返回结果:
// 表示客户端的模拟标记的值。默认值为 0。
public virtual IntPtr GetUserToken();
//
// 摘要:
// 获取请求虚拟路径的模拟标记。
//
// 返回结果:
// 请求虚拟路径的标记的非托管内存指针。
public virtual IntPtr GetVirtualPathToken();
//
// 摘要:
// 返回一个值,该值指示请求是否包含正文数据。
//
// 返回结果:
// 如果请求包含正文数据,则为 true;否则,为 false。
public bool HasEntityBody();
//
// 摘要:
// 返回一个值,该值指示是否已为当前的请求将 HTTP 响应标头发送到客户端。
//
// 返回结果:
// 如果 HTTP 响应标头已发送到客户端,则为 true;否则,为 false。
public virtual bool HeadersSent();
//
// 摘要:
// 返回一个值,该值指示客户端连接是否仍处于活动状态。
//
// 返回结果:
// 如果客户端连接仍处于活动状态,则为 true;否则,为 false。
public virtual bool IsClientConnected();
//
// 摘要:
// 返回一个值,该值指示是否所有请求数据都可用,以及是否不需要对客户端进行进一步读取。
//
// 返回结果:
// 如果所有请求数据都可用,则为 true;否则,为 false。
public virtual bool IsEntireEntityBodyIsPreloaded();
//
// 摘要:
// 返回一个指示连接是否使用 SSL 的值。
//
// 返回结果:
// 如果连接是 SSL 连接,则为 true;否则为 false。默认值为 false。
public virtual bool IsSecure();
//
// 摘要:
// 返回与指定虚拟路径相对应的物理路径。
//
// 参数:
// virtualPath:
// 虚拟路径。
//
// 返回结果:
// 与 virtualPath 参数中指定的虚拟路径相对应的物理路径。
public virtual string MapPath(string virtualPath);
//
// 摘要:
// 读取客户端的请求数据(在尚未预加载时)。
//
// 参数:
// buffer:
// 将数据读入的字节数组。
//
// size:
// 最多读取的字节数。
//
// 返回结果:
// 读取的字节数。
public virtual int ReadEntityBody(byte[] buffer, int size);
//
// 摘要:
// 使用指定的要从中读取数据的缓冲区、字节偏移量和最大字节数从客户端读取请求数据(当未预先加载时)。
//
// 参数:
// buffer:
// 将数据读入的字节数组。
//
// offset:
// 开始读取的位置的字节偏移量。
//
// size:
// 最多读取的字节数。
//
// 返回结果:
// 读取的字节数。
public virtual int ReadEntityBody(byte[] buffer, int offset, int size);
//
// 摘要:
// 将 Content-Length HTTP 标头添加到小于或等于 2 GB 的消息正文的响应。
//
// 参数:
// contentLength:
// 响应的长度(以字节为单位)。
public virtual void SendCalculatedContentLength(int contentLength);
//
// 摘要:
// 将 Content-Length HTTP 标头添加到大于 2 GB 的消息正文的响应。
//
// 参数:
// contentLength:
// 响应的长度(以字节为单位)。
public virtual void SendCalculatedContentLength(long contentLength);
//
// 摘要:
// 将标准 HTTP 标头添加到响应。
//
// 参数:
// index:
// 标头索引。例如 System.Web.HttpWorkerRequest.HeaderContentLength。
//
// value:
// 标头的值。
public abstract void SendKnownResponseHeader(int index, string value);
//
// 摘要:
// 将指定文件的内容添加到响应并指定文件中的起始位置和要发送的字节数。
//
// 参数:
// handle:
// 要发送的文件的句柄。
//
// offset:
// 文件中的起始位置。
//
// length:
// 要发送的字节数。
public abstract void SendResponseFromFile(IntPtr handle, long offset, long length);
//
// 摘要:
// 将指定文件的内容添加到响应并指定文件中的起始位置和要发送的字节数。
//
// 参数:
// filename:
// 要发送的文件的名称。
//
// offset:
// 文件中的起始位置。
//
// length:
// 要发送的字节数。
public abstract void SendResponseFromFile(string filename, long offset, long length);
//
// 摘要:
// 将字节数组中指定数目的字节添加到响应。
//
// 参数:
// data:
// 要发送的字节数组。
//
// length:
// 要发送的字节数(从第一个字节开始)。
public abstract void SendResponseFromMemory(byte[] data, int length);
//
// 摘要:
// 将内存块中指定数目的字节添加到响应。
//
// 参数:
// data:
// 指向内存块的非托管指针。
//
// length:
// 要发送的字节数。
public virtual void SendResponseFromMemory(IntPtr data, int length);
//
// 摘要:
// 指定响应的 HTTP 状态代码和状态说明,例如 SendStatus(200, "Ok")。
//
// 参数:
// statusCode:
// 要发送的状态代码
//
// statusDescription:
// 要发送的状态说明。
public abstract void SendStatus(int statusCode, string statusDescription);
//
// 摘要:
// 将非标准 HTTP 标头添加到响应。
//
// 参数:
// name:
// 要发送的标头的名称。
//
// value:
// 标头的值。
public abstract void SendUnknownResponseHeader(string name, string value);
//
// 摘要:
// 在发送所有响应数据后注册可选通知。
//
// 参数:
// callback:
// 在发送所有数据(带外)后调用的通知回调。
//
// extraData:
// 回调的附加参数。
public virtual void SetEndOfSendNotification(HttpWorkerRequest.EndOfSendNotification callback, object extraData);
// 摘要:
// 表示用于在完成发送响应后通知调用方的方法。
//
// 参数:
// wr:
// 当前的 System.Web.HttpWorkerRequest。
//
// extraData:
// 处理请求所需的任何其他数据。
public delegate void EndOfSendNotification(HttpWorkerRequest wr, object extraData);
}
HttpApplication对象是经由HttpApplicationFactory.GetApplicationInstance(并最终调用HttpRuntime.CreateNonPublicInstance)创建的HttpApplicationFactory它的主要任务是使用 URL 信息来查找 URL 虚拟目录和汇集的 HttpApplication 对象之间的匹配关系。
这个工厂类的行为概括为有以下几点
1、工厂类维护, HttpApplication 对象池并使用它们来处理应用程序的请求。池的寿命与应用程序的寿命相同。
2、应用程序的第一个请求到达时,工厂类提取有关应用程序类型的信息(global.asax 类)、设置用于监视更改的文件、创建应用程序状态并触发 Application_OnStart 事件。工厂类从池中获取一个 HttpApplication 实例,并将要处理的请求放入实例中。如果没有可用的对象,则创建一个新的 HttpApplication 对象。要创建 HttpApplication 对象,需要先完成 global.asax 应用程序文件的编译。
3、HttpApplication 开始处理请求,并且只能在完成这个请求后才能处理新的请求。如果收到来自同一资源的新请求,则由池中的其他对象来处理。
4、应用程序对象允许所有注册的 HTTP 模块对请求进行预处理,并找出最适合处理请求的处理程序类型。这通过查找请求的 URL 的扩展和配置文件中的信息来完成。
HttpApplicationFactory.GetApplicationInstance创建HttpApplication实例中有三个关键方法:
HttpApplicationFactory._theApplicationFactory.EnsureInited() 该方法检查HttpApplicationFactory是否被初始化,如果没有,就通过HttpApplicationFactory.Init()进行初始化。在Init()中,先获取global.asax文件的完整路径,然后调用CompileApplication()对global.asax进行编译。
HttpApplicationFactory._theApplicationFactory.EnsureAppStartCalled(context) 创建特定的HttpApplication实例,触发ApplicationOnStart事件,执行ASP.global_asax中的Application_Start(object sender, EventArgs e)方法。这里创建的HttpApplication实例在处理完事件后,就被回收。
HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(context) 该方法创建HttpApplication实例并进行初始化,调用System.Web.HttpApplication.InitInternal()方法。创建HttpApplication实例是根据实际的_theApplicationType进行创建。如果Web目录中没有global.asa文件,也就是说没有动态编译生成ASP.global_asax类型,那就直接实例化 HttpApplication。如果创建了ASP.global_asax类型,那就对ASP.global_asa进行实例化。
创建HttpApplication实例之后就是调用实例的InitInternal方法。
InitInternal方法的主要功能如下:
1. InitModules():根据Web.Config的设置,创建相应的HttpModules。
2. HookupEventHandlersForAppplicationAndModules:根据发生的事件,调用HttpApplication实例中相应的事件处理函数。
3. 创建很多实现IExecutionStep接口的类的实例并添加到当前HttpApplication实例的_execSteps中,等待回调时执行。从这里我们可以看到HttpApplication是以异步的方式处理请求, 对请求的很多处理工作都放入了_execStep等待回调时执行。
_execStep中主要的处理工作如下:
1) 对请求的路径进行安全检查,禁止非法路径访问(ValidatePathExecutionStep)。
2) 如果设置了UrlMappings, 进行RewritePath(UrlMappingsExecutionStep)。
3) 执行事件处理函数,比如:BeginRequest、AuthenticateRequest等等。
下面就是获取处理当前请求的HttpHandler,ASP.NET页面的动态编译也是在这里进行的。至此HttpApplication流程将会转到HttpHandler流程.也就是说HttpApplication 对象负责查找应该使用哪种处理程序来处理请求。HttpApplication 对象还负责检测对动态创建的、表示资源的程序集(如 .aspx 页面或 .asmx Web 服务)所进行的更改。如果检测到更改,应用程序对象将确保编译并加载所请求的资源的最新来源。HttpApplication调用ProcessRequest方法来处理用户请求,此方法会调用对应的HttpHandler来处理用户请求,HttpHandler根据用户请求的文件的扩展名处理请求,并把请求的结果,也就是HTML发送到客户浏览器.
HttpApplication是HttpRuntime所创建的吗? 并不是,HttpRuntime只是向HttpApplicationFactory提出请求,要求返回一个HttpApplication对象。HttpApplicationFactory在接收到请求后,会先检查是否有已经存在并空闲的对象,如果有就取出一个HttpApplication对象返回给HttpRuntime,如果没有的话,则要创建一个HttpApplication对象给HttpRunTime。
关于HttpApplication这个类的方法的实现,就不再一一解释,需要了解的,在类里面写上一个HttpApplication单词,然后右键选择“转到定义“,就可以看到里面的元数据了。
从上面看出global类与HttpApplication十分紧密,其事上,global类是继承与System.Web.HttpApplication类。
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
}
//省略
}
假设在global类中定义变量与对象,我们在全局中是否能够取得或设置他的值呢?看下面的例子:
public class Global : System.Web.HttpApplication
{
public static string UserName = "James Lee";
protected void Application_Start(object sender, EventArgs e)
{
}
//省略
}
在页面中设置:
WebApp.Global.UserName = "AAAA";
在设置前与设置后输出,你可以看到值的变化,但对变量的设置,会出现什么问题?这些就需要你在开发中去考虑了。
如果使用过MVC框架开发的朋友就很熟其路由规则配置,其实就是设置在Global类的Application_Start事件中,如下面:
public class MvcApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"UpcomingDinners",
"Dinners/Page/{page}",
new { controller = "Dinners", action = "Index" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
其实我们经常用的Application对象是HttpApplication类的一个属性,至此HttpApplication已经完成,下次转到HttpHandler流程了。
随便谈识文断字,查阅大量的资料,等于收集整理加上自己的见解吧,且当成长过程中的回忆。
HttpModule是向实现类提供模块初始化和处置事件。当一个HTTP请求到达HttpModule时,整个ASP.NET Framework系统还并没有对这个HTTP请求做任何处理,也就是说此时对于HTTP请求来讲,HttpModule是一个HTTP请求的“必经之路”,所以可以在这个HTTP请求传递到真正的请求处理中心(HttpHandler)之前附加一些需要的信息在这个HTTP请求信息之上,或者针对截获的这个HTTP请求信息作一些额外的工作,或者在某些情况下干脆终止满足一些条件的HTTP请求,从而可以起到一个Filter过滤器的作用。
首先你要实现IHttpModule接口这个接口只有两个方法,一个是Init方法一个Dispose方法.
using System;
namespace System.Web
{
// Summary:
// Provides module initialization and disposal events to the implementing class.
public interface IHttpModule
{
// Summary:
// Disposes of the resources (other than memory) used by the module that implements
// System.Web.IHttpModule.
void Dispose();
//
// Summary:
// Initializes a module and prepares it to handle requests.
//
// Parameters:
// context:
// An System.Web.HttpApplication that provides access to the methods, properties,
// and events common to all application objects within an ASP.NET application
void Init(HttpApplication context);
}
}
一个HTTP请求在HttpModule容器的传递过程中,会在某一时刻(ResolveRequestCache事件)将这个HTTP请求传递给HttpHandler容器。在这个事件之后,HttpModule容器会建立一个HttpHandler的入口实例,但是此时并没有将HTTP请求控制权交出,而是继续触发AcquireRequestState事件以及PreRequestHandlerExcute事件。在PreRequestHandlerExcute事件之后,HttpModule窗口就会将控制权暂时交给HttpHandler容器,以便进行真正的HTTP请求处理工作。
而在HttpHandler容器内部会执行ProcessRequest方法来处理HTTP请求。在容器HttpHandler处理完毕整个HTTP请求之后,会将控制权交还给HttpModule,HttpModule则会继续对处理完毕的HTTP请求信息流进行层层的转交动作,直到返回到客户端为止。
HttpModule过程在下面的事件:
BeginRequest 指示请求处理开始。
AuthenticateRequest PostAuthenticateRequest 封装请求身份验证过程。
AuthorizeRequest PostAuthorizeRequest 封装请求授权过程。
ResolveRequestCache PostResolveRequestCache 封装检查是否能利用以前缓存的输出页面处理请求的过程。
PostMapRequestHandler 指示已发现用于处理请求的 HTTP 处理程序。
AcquireRequestState PostAcquireRequestState 封装对请求会话状态的检索。
PostRequestHandlerExecute 指示用于处理请求的 HTTP 处理程序已执行。
ReleaseRequestState PostReleaseRequestState 封装对请求会话状态的发布。
UpdateRequestCache PostUpdateRequestCache 封装检查是否应对请求的资源的输出进行缓存以备今后重复使用的过程。
EndRequest 指示请求处理结束。
可以利用HttpModule通过调用HttpApplication.CompleteRequest()方法实现当满足某一个条件时终止此次的HTTP请求。需要注意的是,即使调用了HttpApplication.CompleteRequest()方法终止了一个HTTP请求,ASP.NET Framework仍然会触发HttpApplication后面的这3个事件:EndRequest事件、PreSendRequestHeaders事件、PreSendRequestContent事件。
如果存在多个自定义的HttpModule的话,当Module1终止了一个HTTP请求,这个HTTP请求将不会再触发Module2中相应的事件了,但Module2的最后三个事件仍会被触发。
public class CompleteRequestHttpModule : IHttpModule
{
#region IHttpModule 成员
public void Dispose()
{}
public void Init(HttpApplication application)
{
application.BeginRequest += new EventHandler(Application_BeginRequest);
}
void Application_BeginRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
application.CompleteRequest();
application.Context.Response.Write("请求被终止。");
}
#endregion
}
HttpModule示例:
过滤http请求
/// <summary>
/// 页面访问验证模块
/// </summary>
public class AuthenticationModule : IHttpModule
{
#region IHttpModule Members
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.AcquireRequestState += new EventHandler(context_AcquireRequestState);
}
#endregion
private void context_AcquireRequestState(object sender, EventArgs e)
{
HttpContext context = HttpContext.Current;
string path = context.Request.Path.ToLower();
// 只处理aspx文件,因为其他文件无法获得Session对象,无法判断是否已经登录
if (path.EndsWith(".aspx"))
{
// 如果用户没有登录就會返回false
if (!UserRules.Instance.IsCurrentUserLogined)
{
// 对于公共文件夹和根目录的文件不做判断
if (path.StartsWith("/" + AppSettings.PUBLICFOLDERNAME + "/")==false && !(path.LastIndexOf("/") == 0))
{
// 跳转到公共页面首页
context.Response.Redirect(AppSettings.PUBLICLOGOUTFILENAME, false);
context.ApplicationInstance.CompleteRequest();
}
}
}
}
}
在web.confg中添加httpModules节点注册事件
<httpModules>
<add name="AuthenticationModule" type="Business.AuthenticationModule, Business"/>
</httpModules>
判断浏览器的版本
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
/// <summary>
/// BrowserModule 的摘要说明
/// </summary>
public class BrowserModule : IHttpModule
{
public BrowserModule()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
#region IHttpModule 成员
public void Dispose()
{
}
public void Init(HttpApplication context)
{
//添加判断事件
context.BeginRequest += new EventHandler(context_BeginRequest);
context.EndRequest += new EventHandler(context_EndRequest);
}
void context_EndRequest(object sender, EventArgs e)
{
if (isfalse)
{
HttpApplication application = (HttpApplication)sender;
application.Context.Response.Clear();//清空浏览器所有的内容
application.Context.Response.Output.Write("浏览器不符合要求");
}
}
public bool isfalse = false;//判断浏览器是否符合要求
void context_BeginRequest(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;//获得实际发生事件的对象
HttpBrowserCapabilities browser = application.Context.Request.Browser;//获得浏览器的相关信息
//获得浏览器的名字 application.Context.Response.Output.Write(browser.Browser);
//获得浏览器的版本号 application.Context.Response.Output.Write(browser.MajorVersion );
if (browser.Browser != "IE" || browser.MajorVersion < 7)//判断浏览器的版本是否是IE5以上的
{
string strBrowserChooserPage = "";//创建变量装载浏览器不符合条件后要导入的页面
try
{
strBrowserChooserPage = ConfigurationManager.AppSettings["BrowserChooserPage"];//从配置文件中导入要浏览的页面
}
catch (System.Configuration.ConfigurationErrorsException ex)
{
throw new Exception(string.Format("请正确配置web.config AppSetting[BrowserChooserPage]节点,系统错误提示:{0}", ex.Message));
}
catch (System.Exception ex)
{
throw ex;
}
isfalse = true;//为True的时候浏览器不符合要求,处理完context_BeginRequest事件后,最好在处理context_EndRequest事件的时候判断是否合适
}
else
{
application.Context.Response.Output.Write("浏览器符合要求");
}
}
#endregion
}
注册web.config事件
实现URL重写
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
/// <summary>
/// UrlReWriteHttpModule 的摘要说明
/// </summary>
public class UrlReWriteHttpModule : IHttpModule
{
public UrlReWriteHttpModule()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
#region IHttpModule 成员
public void Dispose()
{
throw new Exception("The method or operation is not implemented.");
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
}
void context_BeginRequest(object sender, EventArgs e)
{
HttpContext context = (sender as HttpApplication).Context;
//string Url1 = context.Request.Path;
//string Url2 = context.Request.PathInfo;
//string Url3 = context.Request.Url.ToString();
//得到整个的网络地址
string fullOrigionalpath = context.Request .Url.ToString();
if (fullOrigionalpath.Contains("/Books.aspx"))
{
context.RewritePath("Products.aspx?Category=Books");//重写地址,重写后地址栏中的显示不变,但是真正返回的页面是被重写的页面
}
else if (fullOrigionalpath.Contains("/DVDs.aspx"))
{
context.RewritePath("Products.aspx?Category=DVDs");
}
else if (fullOrigionalpath.Contains("/DVDs.aspx"))
{
context.RewritePath("Products.aspx?Category=CDs.aspx");
}
}
#endregion
}
注册web.config事件
-------------------------------------------------完成------------------------------------------------
从上面的例子看出,其实是一个好简单的实现方法,就是在init中注册处理过程事件,把代码写完后,在web.congif中HttpModules注册就完成了.
开发程序千变万变, 通过这一方法可以编写出好多合适自己用的功能,这需要大家去实践了. 收集与整理,且当成长的回忆.
HttpHandler是HTTP请求的处理中心,真正地对客户端请求的服务器页面做出编译和执行,并将处理过后的信息附加在HTTP请求信息流中再次返回到HttpModule中。HttpHandler与HttpModule不同,一旦定义了自己的HttpHandler类,那么它对系统的HttpHandler的关系将是“覆盖”关系. HttpHandler功能的实现通过实现IHttpHandler接口来达到.
当一个HTTP请求经同HttpModule容器传递到HttpHandler容器中时,ASP.NET Framework会调用HttpHandler的ProcessRequest成员方法来对这个HTTP请求进行真正的处理。以一个ASPX页面为例,正是在这里一个ASPX页面才被系统处理解析,并将处理完成的结果继续经由HttpModule传递下去,直至到达客户端。
对于ASPX页面,ASP.NET Framework在默认情况下是交给System.Web.UI.PageHandlerFactory这个HttpHandlerFactory来处理的。所谓一个HttpHandlerFactory,所谓一个HttpHandlerFactory,是指当一个HTTP请求到达这个HttpHandler Factory时,HttpHandlerFactory会提供出一个HttpHandler容器,交由这个HttpHandler容器来处理这个HTTP请求。
一个HTTP请求都是最终交给一个HttpHandler容器中的ProcessRequest方法来处理的。
IHttpHandler接口声明:
public interface IHttpHandler
{
bool IsReusable { get; }
public void ProcessRequest(HttpContext context); //请求处理函数
}
v2.0.50727 下的machine.config中httpHandlers结点是这样的:<httpHandlers />,并没有给出详细的处理程序,在Web.config中才能看到。
<httpHandlers>
<remove verb="*" path="*.asmx"/>
<add verb="*" path="*.asmx" validate="false" type=""/>
<add verb="*" path="*_AppService.axd" validate="false" type=""/>
<add verb="GET,HEAD" path="ScriptResource.axd" type=""/>
<add verb="GET" path="CrystalImageHandler.aspx" type=""/>
</httpHandlers>
可以看到,在<httpHandlers>结点中将不同的文件类型映射给不同的Handler去处理. 上面的type部分为了好显示我去掉了,其实你打开web.config,可以看到里面的内容的.
Verb属性:指定了处理程序支持的HTTP动作。*-支持所有的HTTP动作;“GET”-支持Get操作;“POST”-支持Post操作;“GET, POST”-支持两种操作
Path属性:指定了需要调用处理程序的路径和文件名(可以包含通配符).“*”、“*.aspx”
Type属性:用名字空间、类名称和程序集名称的组合形式指定处理程序或处理程序工厂的实际类型. ASP.NET运行时首先搜索bin目录中的DLL,接着在GAC中搜索
IHttpHandler工厂
IHttpHandlerFactory的作用是对IHttpHandler进行管理。接口如下:
public interface IHttpHandlerFactory
{
IHttpHandler GetHandler (HttpContext context,string requestType,string url,string pathTranslated);
void ReleaseHandler (IHttpHandler handler);
}
GetHandler返回实现IHttpHandler接口的类的实例
ReleaseHandler使工厂可以重用现有的处理程序实例
HttpHandler的示例:
把硬盘上的图片以流的方式写在页面上
class TestHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
FileStream fs = new FileStream(context.Server.MapPath("test.jpg"), FileMode.Open);
byte[] b = new byte[fs.Length];
fs.Read(b, 0, (int)fs.Length);
fs.Close();
context.Response.OutputStream.Write(b, 0, b.Length);
}
public bool IsReusable
{
get
{
return true;
}
}
}
Web.Config配置文件
<httpHandlers>
<add verb="*" path="*" type="ClassLibrary831.TestHandler,ClassLibrary831"></add>
</httpHandlers>
使用HttpHandler实现图片防盗链
namespace CustomHandler
{
public class JpgHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
// 获取文件服务器端物理路径
string FileName = context.Server.MapPath(context.Request.FilePath);
// 如果UrlReferrer为空,则显示一张默认的禁止盗链的图片
if (context.Request.UrlReferrer.Host == null)
{
context.Response.ContentType = "image/JPEG";
context.Response.WriteFile("/error.jpg");
}
else
{
// 如果 UrlReferrer中不包含自己站点主机域名,则显示一张默认的禁止盗链的图片
if (context.Request.UrlReferrer.Host.IndexOf("yourdomain.com") > 0)
{
context.Response.ContentType = "image/JPEG";
context.Response.WriteFile(FileName);
}
else
{
context.Response.ContentType = "image/JPEG";
context.Response.WriteFile("/error.jpg");
}
}
}
public bool IsReusable{
get{ return true; }
}
}
}
编译后复制DLL文件到Bin文件夹下,然后在web.config注册
<httpHandlers>
<add path="*.jpg" verb="*" type="CustomHandler.JpgHandler, CustomHandler" />
</httpHandlers>
通过IhttpHandler实现图片验证码
ashx是什么文件呢? 其实该文件用于写web handler. 在web里面添加一个这样的文件,可以看到下列的代码:
<%@ WebHandler Language="C#" Class="Handler" %>
using System;
using System.Web;
public class Handler : IHttpHandler
{
public void ProcessRequest (HttpContext context)
{
context.Response.ContentType = "text/plain";
context.Response.Write("Hello World");
}
public bool IsReusable {
get {
return false;
}
}
}
在上面的基础上修改我们需要的代码如下:
<%@ WebHandler Language="C#" Class="Handler" %>
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using System.Web;
using System.Web.SessionState;
public class Handler : IHttpHandler, IRequiresSessionState {
public void ProcessRequest(HttpContext context) {
context.Response.ContentType = "image/gif";
//建立Bitmap对象,绘图
Bitmap basemap = new Bitmap(200, 60);
Graphics graph = Graphics.FromImage(basemap);
graph.FillRectangle(new SolidBrush(Color.White), 0, 0, 200, 60);
Font font = new Font(FontFamily.GenericSerif, 48, FontStyle.Bold, GraphicsUnit.Pixel);
Random r = new Random();
string letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ";
string letter;
StringBuilder s = new StringBuilder();
//添加随机的五个字母
for (int x = 0; x < 5; x++) {
letter = letters.Substring(r.Next(0, letters.Length - 1), 1);
s.Append(letter);
graph.DrawString(letter, font, new SolidBrush(Color.Black), x * 38, r.Next(0, 15));
}
//混淆背景
Pen linePen = new Pen(new SolidBrush(Color.Black), 2);
for (int x = 0; x < 6; x++)
graph.DrawLine(linePen, new Point(r.Next(0, 199), r.Next(0, 59)), new Point(r.Next(0, 199), r.Next(0, 59)));
//将图片保存到输出流中
basemap.Save(context.Response.OutputStream, ImageFormat.Gif);
context.Session["CheckCode"] = s.ToString(); //如果没有实现IRequiresSessionState,则这里会出错,也无法生成图片
context.Response.End();
}
public bool IsReusable {
get { return true; }
}
}
最后的就是需要在页面上引用这个验证码了. 创建一个新的aspx页面.
<img src="Handler.ashx" alt="图片验证码" />
需要注意的就是我们继承了一个IRequiresSessionState接口:
public interface IRequiresSessionState{}
Handler类不仅需要实现 IHttpHandler接口,为了在这个Handler类中使用SessionState,还需要实现IRequiresSessionState接口,对于这个接口,MSDN的解释是这样的:Specifies that the target HTTP handler requires read and write access to session-state values. This is a marker interface and has no methods.(中文大概的意思:指定当前Http Handler需要对SessionState值的读写访问权。这是一个标记接口,没有任何方法)。这个接口没有任何需要实现的方法或属性,大家只要记得:如果想在HttpHandler中使用SessionState,必须实现这个接口,实际上也就是在类的标头将这个接口加进去。
收集整理与自己见解总结, 且当回忆, 下次再讨论一下HttpModule的理论与方法.
此文转载于 http://www.cnblogs.com/whtydn/archive/2009/10/19/1586002.html