上一篇博文《ASP.NET那點不為人知的事(一)》中我們提到HttpApplication有19個標准事件,在HttpApplication的第8個事件PostMapRequestHandlerExcute觸發的時候,標志着已經獲取了處理當前請求的處理程序對象,在第11個事件PreRequestHandlerExcute之后,HttpApplication將執行這個處理程序,接下來我們繼續討論以下話題:
HttpContext狀態管理
什么是HttpContext狀態管理
HttpContext通過屬性User和Handler傳遞了當前請求的用戶和處理請求所使用的處理程序。如果我們還需要從HttpApplication 前面的事件向后面的事件處理程序傳遞一些參數,我們可以通過HttpContext的Items屬性來完成,用Reflect查看可知這是一個字典:public IDictionary Items { get { if (this._items == null) { this._items = new Hashtable(); } return this._items; } }
由於HttpContext對象貫穿了整個HttpApplication的管道事件的處理過程,所以,根據Items這個屬性,從處理過程的前面階段將數據傳遞給后面的處理過程。所以這種傳遞參數的方式稱為基於HttpContext的狀態管理。
處理HttpApplication的事件
有必要再回顧一下HttpApplication的19個管道事件。
HttpApplication提供了基於事件的擴展機制,允許程序員借助於處理管道中的事件進行處理過程的擴展。
由於HttpApplication對象是由ASP.NETt基礎架構來創建和維護的,那么如何才能獲取這個對象的引用呢, 以便於注冊HttpApplication對象的事件? 我們可以通過IHttpModule來創建HttpApplication的事件處理程序。public interface IHttpModule { // Methods void Dispose(); void Init(HttpApplication context); }實現了IHttpModule接口的類稱為HttpModule。
IHttpModule接口中的Init方法,接受一個HttpApplicaton類型的參數。在ASP.NET中,每當創建一個HttpApplication對象實例,將遍歷注冊的HttpModule類型,通過反射,依次創建每個注冊HttpModule類型的一個對象,並將這個HttpApplication實例通過Init方法傳遞給各個HttpModule,這個HttpModule就可以再第一時間完成針對HttpApplication對象的事件注冊了。
常見的HttpModule
在ASP.NET中已經預定了許多HttpModule,已經在服務器的網站配置文件(C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\web.config)中注冊了:
<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="RoleManager" type="System.Web.Security.RoleManagerModule" /> <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" /> <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" /> <add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" /> <add name="Profile" type="System.Web.Profile.ProfileModule" /> <add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" /> <add name="ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" /> <add name="ScriptModule-4.0" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </httpModules>根據前一篇文章分析,我們再來回顧一下HttpModule是怎樣注冊HttpApplication的事件的:
- HttpApplication實例的初始化:
internal void InitInternal(HttpContext context, HttpApplicationState state, MethodInfo[] handlers) { this._state = state; PerfCounters.IncrementCounter(AppPerfCounter.PIPELINES); try { try { this._initContext = context; this._initContext.ApplicationInstance = this;//是在這兒初始化嗎?我猜的( ⊙ o ⊙ ) context.ConfigurationPath = context.Request.ApplicationPathObject; ..... } 點擊查看ApplicationInstance:
public HttpApplication ApplicationInstance { [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] get { return this._appInstance; } set { if ((this._isIntegratedPipeline && (this._appInstance != null)) && (value != null)) { throw new InvalidOperationException(SR.GetString("Application_instance_cannot_be_changed")); } this._appInstance =value; } }
點擊查看_appInstance 是什么類型private HttpApplication _appInstance;
- 接着,初始化HttpModule,可以發現先從web.config文件中配置的所有HttpModule模塊,然后再獲取其余的HttpModule:
private void InitModules() { HttpModuleCollection modules = RuntimeConfig.GetAppConfig().HttpModules.CreateModules(); HttpModuleCollection other = this.CreateDynamicModules(); modules.AppendCollection(other); this._moduleCollection = modules; this.InitModulesCommon(); }
- 點擊進入CreateModules方法,發現利用了反射來創建HttpModule(Activator.CreateInstance)
[PermissionSet(SecurityAction.Assert, Unrestricted=true)] internal static object CreateNonPublicInstance(Type type, object[] args) { return Activator.CreateInstance(type, BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, args, null); }
- 當創建了一個HttpApplication對象實例,就會遍歷注冊的HttpModule類型,通過反射,依次創建每個注冊HttpModule類型的一個對象,並將這個HttpApplication實例通過Init方法傳遞給各個HttpModule,這個HttpModule就可以再第一時間完成針對HttpApplication對象的事件注冊了。
if (HttpRuntime.UseIntegratedPipeline) { this._stepManager = new PipelineStepManager(this); } else { this._stepManager = new ApplicationStepManager(this); } this._stepManager.BuildSteps(this._resumeStepsWaitCallback); } ......
internal override void BuildSteps(WaitCallback stepCallback) { ......
app.CreateEventExecutionSteps(HttpApplication.EventBeginRequest, steps); app.CreateEventExecutionSteps(HttpApplication.EventAuthenticateRequest, steps); app.CreateEventExecutionSteps(HttpApplication.EventDefaultAuthentication, steps); app.CreateEventExecutionSteps(HttpApplication.EventPostAuthenticateRequest, steps); app.CreateEventExecutionSteps(HttpApplication.EventAuthorizeRequest, steps); app.CreateEventExecutionSteps(HttpApplication.EventPostAuthorizeRequest, steps); app.CreateEventExecutionSteps(HttpApplication.EventResolveRequestCache, steps); app.CreateEventExecutionSteps(HttpApplication.EventPostResolveRequestCache, steps); steps.Add(new HttpApplication.MapHandlerExecutionStep(app)); app.CreateEventExecutionSteps(HttpApplication.EventPostMapRequestHandler, steps); app.CreateEventExecutionSteps(HttpApplication.EventAcquireRequestState, steps); app.CreateEventExecutionSteps(HttpApplication.EventPostAcquireRequestState, steps); app.CreateEventExecutionSteps(HttpApplication.EventPreRequestHandlerExecute, steps); steps.Add(app.CreateImplicitAsyncPreloadExecutionStep()); steps.Add(newHttpApplication.CallHandlerExecutionStep(app));//---------------------->用於創建處理用戶請求的對象(Handler) ...... }了解了HttpModule對HttpApplication對象的事件注冊后,我們再來分析一下:
- HttpApplication選擇處理程序的依據是什么?
- HttpApplication作用是什么?
- HttpApplication如何得到這個處理程序對象?
接下來我們再一一分析下:
- 當瀏覽器發送請求的時候,請求被處理需要用處理程序(必須實現了IHttpHandler接口或者IHttpAsyncHandler)來處理(在第8個事件PostMapRequestHandler觸發獲得處理當前請求的處理程序,在第11個事件PreRequestHandlerExcute之后,HttpApplication將執行這個處理程序),在ASP.NET中,所有請求都要經過HttpApplication管道的處理,根據請求的擴展名來確定使用哪種處理程序。
- HttpApplication作用:可以將它看做請求到達處理程序和離開處理程序的一個管道,這個管道統一處理了所以的請求機制,使得我們可以在請求被真正處理之前和處理之后進行預處理和處理后工作(如獲取Session,更新緩存等)。需要注意的是HttpApplication的事件是按照固定的次序依次觸發。
處理程序工廠
處理程序工廠(實現IHttpHandlerFactory接口)的優點:因為我們知道,實現了處理程序接口的類就可以被用來創建處理程序對象直接使用,如果需要對處理程序對象進行管理,例如:我們可以創建一個處理程序對象池,就可以不用再每次使用處理程序的時候創建一個新的對象,而是直接可以從池中取一個現有的對象直接使用,提高效率。
常見的處理程序工廠:
internal class SimpleHandlerFactory : IHttpHandlerFactory2, IHttpHandlerFactory { // Methods [TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")] internal SimpleHandlerFactory(); public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path); public virtual void ReleaseHandler(IHttpHandler handler); IHttpHandler IHttpHandlerFactory2.GetHandler(HttpContext context, string requestType, VirtualPath virtualPath, string physicalPath); }可以通過GetHandler方法來從這個處理程序工廠獲得一個處理程序對象實例。
通過配置文件,對於擴展名為ashx的請求是通過SimpleHandlerFactory處理程序工廠完成的,當請求一個ashx擴展名的服務器上資源時,SimpleHandlerFactory將找到對應的ashx文件,通過這個文件找到對應的處理程序。最后,SimpleHandlerFactory通過反射創建一個此類型處理程序對象實例<add path="*.ashx" verb="*" type="System.Web.UI.SimpleHandlerFactory" validate="True" />頁面處理程序工廠:PageHandlerFactory(重點)
- 對於Web開發,ASP.NET為了提高輸出HTML代碼效率,采用了模版的方式來生成一個處理程序。模版的擴展名為aspx,並且通過一個內置的處理工廠PageHandlerFactory,根據匹配請求名稱的aspx文件,將aspx形式的模版編譯生成處理程序代碼,其實PageHandlerFactory通過aspx文件生成兩個類,一個為與后台代碼中定義的類同名的部分類(Partial),這個部分類(Partial)將與后台代碼中定義的類在編譯時合並為一個派生自Page的頁面派生類,但是,在ASP.NET,創建實際的頁面對象的類並不是這個類,而是第二個類,一般情況下,這個類的名字后面加上_aspx(注:這個類派生自前面所說的那個合成類)這才是實際創建頁面對象的頁面類,然后,將這個頁面類(實現了IHttpHandler接口,即就是處理程序HttpHandler)反射出來返回給HttpApplication完成請求的處理。
- 需要注意的是,aspx模版的解析和代碼的生成僅僅出現在第一次處理的時候,以后的請求直接使用已經編譯生成的程序集,所以這個處理過程並不會降低網站的處理速度。
[PermissionSet(SecurityAction.InheritanceDemand, Unrestricted=true), PermissionSet(SecurityAction.LinkDemand, Unrestricted=true)] public class PageHandlerFactory : IHttpHandlerFactory2, IHttpHandlerFactory { // Fields private bool _isInheritedInstance; // Methods protected internal PageHandlerFactory(); public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string virtualPath, string path); private IHttpHandler GetHandlerHelper(HttpContext context, string requestType, VirtualPath virtualPath, string physicalPath); public virtual void ReleaseHandler(IHttpHandler handler); IHttpHandler IHttpHandlerFactory2.GetHandler(HttpContext context, string requestType, VirtualPath virtualPath, string physicalPath); }
- 根據配置文件可以看到,對於擴展名為aspx的請求,將由PageHandlerFactory這個處理程序工廠進行處理
<add path="*.aspx" verb="*" type="System.Web.UI.PageHandlerFactory" validate="True" />
- 在PageHandlerFactory的內部,通過PageParser這個類解析指定的aspx文件生成Page類的派生類,而這個派生類即用來創建頁面處理程序對象實例。
public sealed class PageParser : TemplateControlParser
{......
public static IHttpHandler GetCompiledPageInstance(string virtualPath, string inputFile, HttpContext context);
......
}
- PageParser的靜態方法GetCompiledPageInstance方法可以通過一個aspx文件創建一個相應的頁面處理程序對象實例,用於處理請求。
[SecurityPermission(SecurityAction.Demand, Unrestricted=true)] public static IHttpHandler GetCompiledPageInstance(string virtualPath, string inputFile, HttpContext context) { if (!string.IsNullOrEmpty(inputFile)) { inputFile = Path.GetFullPath(inputFile); } return GetCompiledPageInstance(VirtualPath.Create(virtualPath), inputFile, context); }
- 而GetCompiledPageInstance方法內部又使用了BuildManager類來創建頁面對象實例
private static IHttpHandler GetCompiledPageInstance(VirtualPath virtualPath, string inputFile, HttpContext context) { IHttpHandler handler; ....... BuildResultCompiledType type = (BuildResultCompiledType) BuildManager.GetVPathBuildResult(context, virtualPath, false, true, true, true); handler = (IHttpHandler) HttpRuntime.CreatePublicInstance(type.ResultType); ....... return handler; }
- 最后通過GetVPathBuildResult方法通過頁面的虛擬路徑通過代碼生成得到派生的頁面類,然后通過反射創建這個頁面對象:
internal static BuildResult GetVPathBuildResult(HttpContext context, VirtualPath virtualPath, bool noBuild, bool allowCrossApp, bool allowBuildInPrecompile, [Optional, DefaultParameterValue(true)] bool ensureIsUpToDate) { if (HttpRuntime.IsFullTrust) { return GetVPathBuildResultWithNoAssert(context, virtualPath, noBuild, allowCrossApp, allowBuildInPrecompile, true, ensureIsUpToDate); } return GetVPathBuildResultWithAssert(context, virtualPath, noBuild, allowCrossApp, allowBuildInPrecompile, true, ensureIsUpToDate); }Reflect反編譯網站看究竟
由上面分析得知,下面是一個合並的類:
下面這個類派生自_Default類,最終通過反射創建實際的頁面對象,它實現了IHttpHandler接口,也就是一個處理程序HttpHandler,所以頁面毫無疑問也是一個處理程序
我們可以看到default_aspx里面主要是初始化了控件樹(BuildControlTree)和ProcessRequest方法啟動頁面生成過程。
頁面的事件處理管道
頁面對象的ProcessRequest方法將會啟動頁面的生成過程,這個過程是通過頁面的事件處理管道來完成,在處理過程中頁面對象將會依次觸發一系列事件。
總結
未完,待續。