相信大家都使用过ASP.NET进行过基于Web的应用开发,ASP.NET是什么?如果站在一个相对High Level的角度,我们可以这样来定义ASP.NET:ASP.NET是一个基于Web的开发平台,提供构建企业级应用所需的Service、Programming Model和Software的Infrastructure。如果我们以一个Low Level的角度来看,它本质上就是一个消息处理器:他接受IIS(确切地说应该是ASP.NET ISAPI)Forward的Http Request (我们可以看成是一个Request Message),经过一系列的处理,最终产生一个用户希望的Response(这也是一个Message,对于.aspx Page来说是一个Html document,对于一个Web Service来说是一个Soap)。所以本篇文章的主要目的在于站在一个相对Low Level的角度介绍ASP.NET的整个Http Request Processing Model。我们访问一个基于ASP.NET的资源,IIS是第一道屏障,在第一个部分我分别就IIS 5.x和IIS 6的差异介绍了IIS对Http Request的处理,今天我们来继续后面的故事。
一、从Unmanaged Environment到Managed Environment
上一部分我们说到IIS收到一个基于ASP.NET资源文件的访问,它会把Http Request交给一个ASP.NET ISAPI Extension处理。ASP.NET ISAPI 会加载CLR,从而创建一个托管的环境。ASP.NET ISAPI Extension定义在一个名为aspnet_isapi.dll中,aspnet_isapi.dll是一个纯Native的、高效的Dll,也就是说,虽然ASP.NET ISAPI通过加载CLR创建一个托管的环境,但是ASP.NET ISAPI本省却运行在一个Unmanaged的环境中。而我们的ASP.NET Application确是完全的Managed code,运行在一个Managed的环境中。要了解ASP.NET Http Runtime Pipeline这个纯托管的Runtime,我们必须先了解从Unmanaged Environment到Managed Environment的这道桥梁。
![](https://i-blog.csdnimg.cn/blog_migrate/c74f919696159f1052cb954071891021.jpeg)
上图简单表述了在IIS 6环境下,从非托管环境到托管环境的过程。从图中我们可以看到,ASP.NET ISAPI运行在一个非托管环境之中。ASP.NET ISAPI经过系列COM级别的class调用(由于这些被调用的Class都是一个个undocumented class,所以要真正说清楚调用流程中每个具体的细节是一件很难的事情,而且也确实没有很大的必要去挖掘它,因为具体的实现可能会经常变动,如果对此具有好奇心的朋友可以通过一些Tool,比如Reflector去仔细研究一下),最终的调用降临到一个托管的、继承自System.Web.Hosting.ISAPIRuntime类的对象上。ISAPIRuntime 是一个特殊的class,他实现了Interface System.Web.Hosting.IISAPIRuntime。下面是该Interface的定义。通过定义我们可以看到,这是一个基于COM的Interface,也就是说Caller可以通过COM的方式调用实现该Interface的Class的对象。在这里,这个最初的Caller就是ASP.NET ISAPI。从这里我们可以总结出:ASP.NET ISAPI通过调用System.Web.Hosting.ISAPIRuntime Instance的ProcessRequest方法,进而从非托管的环境进入了托管的环境。
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid(
"
08a2c56f-7c16-41c1-a8be-432917a1a2d1
"
)]
public
interface
IISAPIRuntime
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
void StartProcessing();
void StopProcessing();
[return: MarshalAs(UnmanagedType.I4)]
int ProcessRequest([In] IntPtr ecb, [In, MarshalAs(UnmanagedType.I4)] int useProcessModel);
void DoGCCollect();
}
ISAPI ECB (Execution Control Block) & ISAPIWorkerRequest
通过System.Web.Hosting.IISAPIRuntime Interface中的ProcessRequest方法的Siganature,我们可以看出该方法包含两个参数,其中一个是名为ecb的Unmanaged Pointer,另一个是useProcessModel。ECB全称是Execution Control Block,在整个Http Request Processing过程中起着非常重要的作用,我们现在来简单介绍一个ECB。
ISAPI顾名思义,就是实现了一些基于Internet Server的API。Aspnet_isapi.dll实现了这些API,对于IIS来说,它可以调用这些API进入托管的环境实现对ISAPIRuntime的调用,对于ISAPIRuntime来说,它需要调用ASP.NET ISAPI实现一些必要的功能,比如获得Server Variable的数据,获得通过Post Mehod传回Server的数据;以及最终将Response的内容返回给ASP.NET ISAPI,并通过ASP.NET ISAPI返回到Client。一般地ISAPIRuntime不能直接调用ASP.NET ISAPI,而是通过一个对象指针实现对其的调用,这个对象就是ECB,ECB实现了对ISAPI的访问。
还有一点特别需要强调的是,ISAPI对ISAPIRutime的调用是异步的,也就是说ISAPI调用ISAPIRutime之后立即返回。这主要是出于Performance和Responsibility考虑的,因为ASP.NET Application天生就是一个多线程的应用,为了具有更好的响应能力,异步操作是最有效的解决方式。但是这里就会有一个问题,我们知道我们对ASP.NET 资源的调用本质上是一个Request/Response的Message Exchange Pattern,异步调用往往意味着ISAPI将Request传递给ISAPIRuntime,将不能得到ISAPIRuntime最终生成的Response,这显然是不能接受的。而ECB解决了这个问题,ISAPI在调用ISAPIRutime的ProcessRequest方法时会将自己对应的ECB的指针传给它,ISAPIRutime不但可以将最终生成的Response返回给ISAPI,还能通过ECB调用ISAPI获得一些所需的数据。
明白ECB是怎么回事之后,我们通过Reflector简单了解一下ISAPIRutime的ProcessRequest的实现:
ProcessRequest
public int ProcessRequest(IntPtr ecb, int iWRType)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
IntPtr zero = IntPtr.Zero;
if (iWRType == 2)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
zero = ecb;
ecb = UnsafeNativeMethods.GetEcb(zero);
}
ISAPIWorkerRequest wr = null;
try
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
bool useOOP = iWRType == 1;
wr = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP);
wr.Initialize();
string appPathTranslated = wr.GetAppPathTranslated();
string appDomainAppPathInternal = HttpRuntime.AppDomainAppPathInternal;
if ((appDomainAppPathInternal == null) || StringUtil.EqualsIgnoreCase(appPathTranslated, appDomainAppPathInternal))
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
HttpRuntime.ProcessRequestNoDemand(wr);
return 0;
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged, SR.GetString("Hosting_Phys_Path_Changed", new object[]
{ appDomainAppPathInternal, appPathTranslated }));
return 1;
}
catch (Exception exception)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
try
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
WebBaseEvent.RaiseRuntimeError(exception, this);
}
catch
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
}
if ((wr == null) || (wr.Ecb != IntPtr.Zero))
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
throw;
}
if (zero != IntPtr.Zero)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
UnsafeNativeMethods.SetDoneWithSessionCalled(zero);
}
if (exception is ThreadAbortException)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
Thread.ResetAbort();
}
return 0;
}
}
对于上面的代码,我觉得没有必要去深究,但是对于那些具有强烈好奇心的朋友除外J。基本上上面的代码完成下面两个任务:
通过传入的ECB和iWRType创建一个叫做ISAPIWorkerRequest的对象:
bool
useOOP
=
iWRType
==
1
;
wr
=
ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP);
然后调用HttpRuntime.ProcessRequestNoDemand(wr),通过将创建的ISAPIWorkerRequest的对象作为参数传入。
HttpRuntime.ProcessRequestNoDemand的调用真正进入了ASP.NET Runtime Pipeline,这是一个相对复杂的过程。在这里我想简单说说ISAPIWorkerRequest这个重要class,ISAPIWorkerRequest是一个Abstract class,它已通过ECB创建基于当前Request的Context的信息,针对不同的IIS版本,具有不同的ISAPIWorkerRequest subclass,比如:ISAPIWorkerRequestOutOfProc(IIS 5.x), ISAPIWorkerRequestInProcForIIS6, ISAPIWorkerRequestInProcForIIS7。ProcessRequest通过ISAPI传入的iWRType来创建不同HttpWorkerRequest,从而屏蔽了不同IIS的差异,后续的步骤就不需要考虑这种差异了,这是Abstract Factory的典型用法。
二、ASP.NET Runtime Pipeline(续ASP.NET Http Runtime Pipeline - Part I)
现在我们真正进入ASP.NET管辖的范畴,下图基本上囊括整个处理过程涉及的对象,接下来我们一起来讨论这一系列的对象如何相互协作去处理Http Request,并最终生成我们所需的Http Response。
![](https://i-blog.csdnimg.cn/blog_migrate/a6589da65ef88345f498574244362591.jpeg)
HttpContext
上面我们介绍了ISAPI在调用ISAPIRuntime的时候将对应的ISAPI ECB Pointer作为参数传递给了ProcessRequest方法,这个ECB pointer可以看成是托管环境和非托管环境进行数据交换的唯一通道,Server Variable和Request Parameter通过它传入ASP.NET作为进一步处理的依据,ASP.NET最后生成的Response通过它传递给ISAPI,并进一步传递给IIS最终返回到Client端。
借助这个传进来的ECB Pointer,我们创建了一个ISAPIWorkerRequest。ISAPIWorkerRequest作为参数传入HttpRuntime.ProcessRequestNoDemand的调用。HttpRuntime.ProcessRequestNoDemand最终体现在调用ProcessRequestInternal。下面是真个方法的实现:
ProcessRequestInternal
private void ProcessRequestInternal(HttpWorkerRequest wr)
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
HttpContext context;
try
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
context = new HttpContext(wr, false);
}
catch
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
wr.SendStatus(400, "Bad Request");
wr.SendKnownResponseHeader(12, "text/html; charset=utf-8");
byte[] bytes = Encoding.ASCII.GetBytes("<html><body>Bad Request</body></html>");
wr.SendResponseFromMemory(bytes, bytes.Length);
wr.FlushResponse(true);
wr.EndOfRequest();
return;
}
wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, context);
Interlocked.Increment(ref this._activeRequestCount);
HostingEnvironment.IncrementBusyCount();
try
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
try
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
this.EnsureFirstRequestInit(context);
}
catch
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
if (!context.Request.IsDebuggingRequest)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
throw;
}
}
context.Response.InitResponseWriter();
IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context);
if (applicationInstance == null)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
throw new HttpException(SR.GetString("Unable_create_app_object"));
}
if (EtwTrace.IsTraceEnabled(5, 1))
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, applicationInstance.GetType().FullName, "Start");
}
if (applicationInstance is IHttpAsyncHandler)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
IHttpAsyncHandler handler2 = (IHttpAsyncHandler) applicationInstance;
context.AsyncAppHandler = handler2;
handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context);
}
else
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
applicationInstance.ProcessRequest(context);
this.FinishRequest(context.WorkerRequest, context, null);
}
}
catch (Exception exception)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
context.Response.InitResponseWriter();
this.FinishRequest(wr, context, exception);
}
}
对象上面的代码没有必要深究,我们只需要了解大体的执行流程就可以了,下面这一段伪代码基本上体现整个执行过程:
HttpContext context
=
new
HttpContext(wr,
false
);
IHttpHandler applicationInstance
=
HttpApplicationFactory.GetApplicationInstance(context);
首先通过创建的ISAPIWorkerRequest创建按一个HttpContext对象,随后通过HttpApplicationFactory.GetApplicationInstance创建一个IHttpHandler对象(一般情况下就是一个HttpApplication对象)。
正如他的名字体现的,HttpContext体现当前Request的上下文信息,它的生命周期知道整个Request处理结束或者处理超时。通过HttpContext对象我们可以访问属于当前Request的一系列常用的对象:Server,Session,Cache,Application,Request,Response,Trace,User,Profile等等。此外我们可以认为将一些数据放在Items属性中作为状态管理的一种方式,不过这种状态管理和其他一些常用的方式,比如Session,Cache,Application,Cookie等,具有根本性的不同之处是其生命周期仅仅维持在当前Request的Context中。
2. HttpApplication
就像其名称体现的一样,HttpApplication基本上可以看成是真个ASP.NET Application的体现。HttpApplication和置于虚拟根目录的Gloabal.asax对应。通过HttpApplicationFactory.GetApplicationInstance创建一个基于Gloabal.asax的HttpApplication对象。在HttpApplicationFactory.GetApplicationInstance方法返回创建的HttpApplication对象之前,会调用一个名为InitInternal的内部方法,该方法会做一些列的初始化的操作,在这些初始化操作中,最典型的一个初始化方法为InitModules(),该方法的主要的目的就是查看Config中注册的所有HttpModule,并根据配置信息加载相应的Assembly,通过Reflection创建对应的HttpModule,并将这些Module加到HttpApplication 的_moduleCollection Filed中。
HttpApplication本身并包含对Request的任何处理,他的工作方式是通过在不同阶段出发不同Event来调用我们注册的Event Hander。
下面列出了HttpApplication所有的Event,并按照触发的时间先后顺序排列:
- BeginRequest:
- AuthenticateRequest & Post AuthenticateRequest
- AuthorizeRequest & Post AuthorizeRequest
- ResolveRequestCache & Post ResolveRequestCache
- PostMapRequestHandler:
- AcquireRequestState & AcquireRequestState:
- PreRequestHandlerExecute & Post RequestHandlerExecute:
- ReleaseRequestState & Post ReleaseRequestState
- UpdateRequestCache & PostUpdateRequestCache
- EndRequest:
ASP.NET Application, AppDomain and HttpApplication
对于一个ASP.NET Application来说,一个Application和一个虚拟目录相对应,那么是不是一个Application 对应着一个AppDomain呢?一个Application是否就唯一对应一个Httpapplication对象呢?答案是否定的。
我们首先来看看Application和HttpApplication的关系,虽然我们对一个Application的Request最终都由一个HttpApplication对象来承载。但不能说一个Application就唯一对应一个固定的HttpApplication对象。原因很简单,ASP.NET天生具有多线程的特性,需要通过相应不同的Client的访问,如果我们只用一个HttpApplication来处理这些并发的请求,会对Responsibility造成严重的影响,通过考虑到Performance的问题,ASP.NET对HttpApplication的使用采用Pool的机制:当Request到达,ASP.NET会现在HttpApplication Pool中查找未被使用的HttpApplication对象,如果没有,则创建之,否则从Pool直接提取。对于Request处理完成的HttpApplication对象,不会马上销毁,而是把它放回到Pool中供下一个Request使用。
对于Application和AppDomain的关系,可能你会说一个Application肯定只用运行在一个AppDomain之中。在一般情况下这句话无可厚非,但是这却忽略了一种特殊的场景:在当前Application正在处理Request的时候,我们把web.config以及其他一些相关文件修改了,而且这种改变是可以马上被ASP.NET检测到的,为了使我们的变动能够及时生效,对于改动后的第一个Request,ASP.NET会为期创建一个新的AppDomain,而对于原来的AppDomain,也许还在处理修改前的Request,所有原来的Appdomain会持续到将原来的Request处理结束之后,所以对于一个Application,可能出现多个AppDomain并存的现象。
3. HttpModule
我们上面提到HttpApplication就是一个ASP.NET Application的体现,HttpApplication本身并不提供对Request的处理功能,而是通过在不同阶段出发不同的Event。我们能做的只能是根据我们具体的需求将我们的功能代码作为Event Handler注册到需要的HttpApplication Event上面。注册这些Event Handler,我们首先想到的肯定就直接在HttpApplication对应的Global.asax中定义我们的EventHandler好了。这是最直接的办法,而且Global.asax提供一个简洁的方式是我们的实现显得简单:不需要向一般注册Event一样将Delegate添加到对应的Event上面,而是直接通过方法名称和对应的Event匹配的方式直接将对应的方法作为相关的Event Handler。比如Application_ AcquireRequestState就是AcquireRequestState Event handler。
但是这种方式在很多情况下却达不到我们的要求,更多地,我们需要的是一种Plug-in的实现方式:我们在外部定义一些Request Processing的功能,需要直接运用到我们的Application之中。通过使用HttpModule封装这些功能模块,并将其注册到我们的Application的发式可以很简单的实现这种功能。
HttpModule实现了System.Web.IHttpModule interface,该Interface很简单,仅仅有两个成员:
[AspNetHostingPermission(SecurityAction.InheritanceDemand, Level
=
AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.LinkDemand, Level
=
AspNetHostingPermissionLevel.Minimal)]
public
interface
IHttpModule
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
// Methods
void Dispose();
void Init(HttpApplication context);
}
我们只要在Init方法中注册相应的HttpApplication Event Handler就可以了:
public
class
BasicAuthCustomModule : IHttpModule
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
public void Init(HttpApplication application)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
application.AuthenticateRequest +=
new EventHandler(this.OnAuthenticateRequest);
}
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void Dispose()
{ }
public void OnAuthenticateRequest(object source, EventArgs eventArgs)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
![](/Images/OutliningIndicators/InBlock.gif)
}
}
所有的HttpModule同machine.config或者Web.config的httpModules Section定义,下面是Machine.config定义的所有httpModule。
<
httpModules
>
<
add
name
="OutputCache"
type
="System.Web.Caching.OutputCacheModule"
/>
<
add
name
="Session"
type
="System.Web.SessionState.SessionStateModule"
/>
<
add
name
="WindowsAuthentication"
type
="System.Web.Security.WindowsAuthenticationModule"
/>
<
add
name
="FormsAuthentication"
type
="System.Web.Security.FormsAuthenticationModule"
/>
<
add
name
="PassportAuthentication"
type
="System.Web.Security.PassportAuthenticationModule"
/>
<
add
name
="UrlAuthorization"
type
="System.Web.Security.UrlAuthorizationModule"
/>
<
add
name
="FileAuthorization"
type
="System.Web.Security.FileAuthorizationModule"
/>
<
add
name
="ErrorHandlerModule"
type
="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
/>
</
httpModules
>
但是HttpModule如何起作用的,我们来回顾一下上面一节介绍的:HttpApplicationFactory.GetApplicationInstance方法返回创建的HttpApplication对象之前,会调用一个名为InitInternal的内部方法,该方法会做一些列的初始化的操作,在这些初始化操作中,最典型的一个初始化方法为InitModules(),该方法的主要的目的就是查看Config中注册的所有HttpModule,并根据配置信息加载相应的Assembly,通过Reflection创建对应的HttpModule,并将这些Module加到HttpApplication 的_moduleCollection Filed中,最后依次调用每个HttpModule的Init方法。下面是其实现:
private
void
InitModules()
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
this._moduleCollection = RuntimeConfig.GetAppConfig().HttpModules.CreateModules();
this.InitModulesCommon();
}
![](/Images/OutliningIndicators/None.gif)
![](/Images/OutliningIndicators/None.gif)
private
void
InitModulesCommon()
![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
int count = this._moduleCollection.Count;
for (int i = 0; i < count; i++)
![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
this._currentModuleCollectionKey = this._moduleCollection.GetKey(i);
this._moduleCollection[i].Init(this);
}
this._currentModuleCollectionKey = null;
this.InitAppLevelCulture();
}
HttpHandler
如果说HttpModule关注的是所有Inbound Request的处理的话,Handler确实关注基于某种类型的ASP.NET Resource的Request。比如一个.apsx的Web Page通过一个System.Web.UI.Page来处理。HttpHandler和他所处理的Resource通过Config中的system.web/handlers section来定义,下面是Machine.config中的定义。
<
httpHandlers
>
<
add
verb
="*"
path
="trace.axd"
type
="System.Web.Handlers.TraceHandler"
/>
<
add
verb
="*"
path
="*.aspx"
type
="System.Web.UI.PageHandlerFactory"
/>
<
add
verb
="*"
path
="*.ashx"
type
="System.Web.UI.SimpleHandlerFactory"
/>
<
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
="*.rem"
type
="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
validate
="false"
/>
<
add
verb
="*"
path
="*.soap"
type
="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
validate
="false"
/>
<
add
verb
="*"
path
="*.asax"
type
="System.Web.HttpForbiddenHandler"
/>
<
add
verb
="*"
path
="*.ascx"
type
="System.Web.HttpForbiddenHandler"
/>
<
add
verb
="GET,HEAD"
path
="*.dll.config"
type
="System.Web.StaticFileHandler"
/>
<
add
verb
="GET,HEAD"
path
="*.exe.config"
type
="System.Web.StaticFileHandler"
/>
<
add
verb
="*"
path
="*.config"
type
="System.Web.HttpForbiddenHandler"
/>
<
add
verb
="*"
path
="*.cs"
type
="System.Web.HttpForbiddenHandler"
/>
<
add
verb
="*"
path
="*.csproj"
type
="System.Web.HttpForbiddenHandler"
/>
<
add
verb
="*"
path
="*.vb"
type
="System.Web.HttpForbiddenHandler"
/>
<
add
verb
="*"
path
="*.vbproj"
type
="System.Web.HttpForbiddenHandler"
/>
<
add
verb
="*"
path
="*.webinfo"
type
="System.Web.HttpForbiddenHandler"
/>
<
add
verb
="*"
path
="*.asp"
type
="System.Web.HttpForbiddenHandler"
/>
<
add
verb
="*"
path
="*.licx"
type
="System.Web.HttpForbiddenHandler"
/>
<
add
verb
="*"
path
="*.resx"
type
="System.Web.HttpForbiddenHandler"
/>
<
add
verb
="*"
path
="*.resources"
type
="System.Web.HttpForbiddenHandler"
/>
<
add
verb
="GET,HEAD"
path
="*"
type
="System.Web.StaticFileHandler"
/>
<
add
verb
="*"
path
="*"
type
="System.Web.HttpMethodNotAllowedHandler"
/>
</
httpHandlers
>
需要注意的是,我们不但可以单纯地定义一个实现了System.Web.IHttpHandler的Type,也可以定义一个实现了System.Web.IHttpHandlerFactory 的Type。System.Web.UI.Page是一个典型的Httphandler,相信对此大家已经很熟悉了。在最后还说说另一个典型的HttpHandler:System.Web.HttpForbiddenHandler,从名称我们不难看出,它用于那些禁止访问的Resource,现在应该知道了为了Global.asax不同通过IIS访问了吧。