前面三節講了控制項的構造、呈現和資料繫結,我想該差不多了。本想講一個自訂控制項來終結控制項部分,但是我個人不太喜歡控制項這些東西,所以也就懶的寫相關的內容,抱歉了。雖然我不喜歡使用控制項,但我還是喜歡整個WebForm的設計。一個字:“太神了”。前面章節將Page生命週期的時候有朋友評論說內容太少了,今天開始就從來圍繞生命週期的話,講講相關的內容吧。
IHttpModule是個什麼東西呢?對我們Web開發有什麼用呢?
先從名字來看他是一個介面,介面就是讓人來繼承的,我們要用它就得繼承他,並實現他的方法。Module的意思是模組、組件的意思。如果說我們實現了這個介面,並配置了web.config,讓IIS的知道我們的web程式使用了這個組件;那麼我們的程式是不是就比預設的web程式多了個組件?!顯然,而且在必要的時候會調用我們組件裡定義的方法,這就是HttpModule的用處。說白了,就是我們給IIS寫擴充,但該擴充僅僅是針對於使用了(配置config)的web程式。其實每個web應用程式都是一個IIS進程,而這個進程的設定檔就是web.config。
弄明白了他的意義,我們就開始!我們建立一個Web應用程式,並建立一個類,繼承IHttpModule,並實現他的方法,在Config的Modules節點裡<add name="" type=""/>,OK!
namespace WebApplication1 { public class MyHttpModule : IHttpModule { public void Dispose() { } public void Init(HttpApplication context) { context.Context.Response.Write(1); } } }
<?xml version="1.0"?> <configuration> <system.web> <compilation debug="true" targetFramework="4.0" /> <httpModules> <add name="MyHttpModule" type="WebApplication1.MyHttpModule,WebApplication1"/> </httpModules> </system.web> <system.webServer> <modules runAllManagedModulesForAllRequests="true"> <add name="MyHttpModule" type="WebApplication1.MyHttpModule,WebApplication1"/><!--注意:type="類的FullName,類所在的dll名"--> </modules> </system.webServer> </configuration>
web.config的配置有2個,上面的那個是給非IIS7用的,下面的顯然就是給IIS7用的。啟動程式,what happend?! 是不是頁的頭部多了個1,有木有!!我們開啟任何頁面都會有個1,說明我們的模組起到作用了,也說明每個請求都會執行HttpModule的Init方法?是不是呢?
我們把代碼改一下:
public void Init(HttpApplication context) { context.Context.Response.Write(1); context.BeginRequest += OnBeginRequest; } public void OnBeginRequest(object sender, EventArgs e) { var app = (HttpApplication)sender; var url = app.Context.Request.RawUrl; app.Context.Response.Write(url); }
分別給Init和OnBeginRequest 兩個方法加斷點,重新編譯下,然後F5看看。Init只走1次,而OnBeginRequest卻走了3次,ur的值l分別是 default.aspx style.css 和 favorite.ico;可以看出任何url請求,包括靜態檔案,都會經過執行我們定義的事件方法!看來這要比只處理aspx慢不少!
Init的必須走一次啊,要不然事件不被訂閱3次了?,但為什麼只走1次呢?這到底是為什麼呢? 呵呵,其實很簡單,MyHttpModule就執行個體化一次哦,執行個體化後執行Init初始化,然後就駐留在應用程式集區了,直到應用程式集區被回收,或他被各種原因搞崩潰;而OnBeginRequest是被HttpApplication類的BeginRequest事件訂閱的。事件訂閱是個什麼概念?事件是個特殊的委託,委託是個什麼概念?委託是個方法指標。所以,只要委託被執行,就會執行他指向的方法體,也就是OnBeginRequest,可見OnBeginRequest的執行,是和HttpApplication的BeginRequest有關係的,和MyHttpModule本身已經沒關係了。
走了3次說明3個Request都執行了BeginRequest,難道每個請求都執行個體化一個HttpApplication?從名字我就能看出不會的,因為Application(應用程式)嘛,我們目前啟動並執行就一個,怎麼會不斷的執行個體化!想刨根問題,徹底整明白,就得翻出Framework的源碼,調試!
(------------聲明,下面的源碼可以不用完全理解,也可以跳過,只要知道跟Request有關就行了------------)
下面來調查下HttpApplication的初始化過程!
用Reflector查閱System.Web名字空間下的類,可以看到HttpApplicationFactory類,他負責HttpApplication的建立。當我們啟動網站後,第一次的時候比較慢,為什麼呢? 因為初始化的構建工作。
System.Web.Complilation名字空間下有一堆的構建類,其中就有構建Global.asax的,也就是我們的HttpApplication類,然後緩衝到Factory的堆棧裡,我們需要的時候pop出來。 (你可能有疑問,pop了不就沒了嗎? 其實app在執行的時候還會push回去,詳見HttpApplication.ReleaseAppInstance方法)
HttpApplicationFactory有個GetApplicationInstance方法,就是用來擷取HttpApplication的:
internal static IHttpHandler GetApplicationInstance(HttpContext context) { if (_customApplication != null) { return _customApplication; } if (context.Request.IsDebuggingRequest) { return new HttpDebugHandler(); } _theApplicationFactory.EnsureInited(); _theApplicationFactory.EnsureAppStartCalled(context); return _theApplicationFactory.GetNormalApplicationInstance(context); }private HttpApplication GetNormalApplicationInstance(HttpContext context) { HttpApplication application = null; lock (this._freeList) { if (this._numFreeAppInstances > 0) { application = (HttpApplication) this._freeList.Pop();//如果_freeList裡有,就直接擷取,只有第一次構建的時候沒有 this._numFreeAppInstances--; if (this._numFreeAppInstances < this._minFreeAppInstances) { this._minFreeAppInstances = this._numFreeAppInstances; } } } if (application == null) { application = (HttpApplication) HttpRuntime.CreateNonPublicInstance(this._theApplicationType); using (new ApplicationImpersonationContext()) { application.InitInternal(context, this._state, this._eventHandlerMethods);//這裡是初始化application的,並且會經過複雜的一坨代碼後push到_freeList裡 } } return application; }
跟蹤這個方法,我們可以斷定,Application是被緩衝起來的,不是每次都是執行個體化的。
通過Reflector的分析,我們能發現這個GetApplicationInstance方法是被HttpRuntime.ProcessRequestNow調用的,終於回到我們的Request來了。
private void ProcessRequestNow(HttpWorkerRequest wr) { HttpContext context; try { context = new HttpContext(wr, false);//執行個體化上下文 } catch { 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 { try { this.EnsureFirstRequestInit(context); } catch { if (!context.Request.IsDebuggingRequest) { throw; } } context.Response.InitResponseWriter();//執行個體化HttpWriter,輸出用的,我們的控制項輸出全靠他 IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context);//擷取handler也就是httpapplication if (applicationInstance == null) { throw new HttpException(SR.GetString("Unable_create_app_object")); } if (EtwTrace.IsTraceEnabled(5, 1)) { EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, applicationInstance.GetType().FullName, "Start"); } if (applicationInstance is IHttpAsyncHandler)//是否非同步,顯然我們的HttpApplication是繼承這個介面的,所以走這個if { IHttpAsyncHandler handler2 = (IHttpAsyncHandler) applicationInstance; context.AsyncAppHandler = handler2; handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context);//從BeginRequest就開始執行application的step } else { applicationInstance.ProcessRequest(context);//直接執行 this.FinishRequest(context.WorkerRequest, context, null); } } catch (Exception exception) { context.Response.InitResponseWriter(); this.FinishRequest(wr, context, exception); } }
很好,上面的代碼讓我們看清了一個Request的執行過程。而每個Request都會走這個方法!HttpRuntime有個RequestQueue(請求隊列),會依次執行所有的Request。終於知道為什麼走3次了吧:) 就是application被用了3次。感興趣的同學可以再去跟蹤RequestQueue,我就不貼了。
另外,HttpApplication,意味著他是整個網站的boss,我們定義的myhttpmodule不過是他眾多Modules裡的其中之一。而且我們也可以定義多個module,config裡面add多個就可以了。
在Application初始化的過程中,有初始化module的一段,我貼出來大家看看:
private void InitModules() { this._moduleCollection = RuntimeConfig.GetAppConfig().HttpModules.CreateModules(); this.InitModulesCommon(); }
其中CreateModules就是從web.config的module節點執行個體化我們配置的module
internal HttpModuleCollection CreateModules() { HttpModuleCollection modules = new HttpModuleCollection(); foreach (HttpModuleAction action in this.Modules) { modules.AddModule(action.Entry.ModuleName, action.Entry.Create()); } modules.AddModule("DefaultAuthentication", new DefaultAuthenticationModule()); return modules; }
最後,初始化所有的module,包括系統的一些module。
private void InitModulesCommon() { int count = this._moduleCollection.Count; for (int i = 0; i < count; i++) { this._currentModuleCollectionKey = this._moduleCollection.GetKey(i); this._moduleCollection[i].Init(this);//初始化每個HttpModule } this._currentModuleCollectionKey = null; this.InitAppLevelCulture(); }
(--------------跳到這裡----------------)
HttpApplication類公開了很多事件。上面的樣本程式用到了BeginRequest,這個事件是最先開始執行的事件;其作用很明白,就是Request開始執行時,我們要準備點什嗎?或許你可能需要urlrewriter:)。下面插播“事件廣告”,廣告之後馬上飛來。
上面我只是簡單的提了一句“事件是特殊的委託”,並沒有詳細說為什麼特殊。不知道同學你是否理解事件的意義呢?事件的意義是什嗎?
我是這麼理解的。“事件”,代表著一件事情的發生。我們打一個比方,我把每天的生活設計成一個類。那麼,一天的生活包含什嗎?包含從早到晚,包含很多事情要去做,甚至包含一些固定的事情。
細品這3個包含:
從早到晚,意味著這是一個過程,從頭到尾,從始到終;很多事情要去做,說明在這個過程中要執行很多事;而一些固定的事情,比如吃飯,睡覺。
我們可以把早和晚,看作是建構函式和解構函式;把很多事情要做看作是事件;把固定的事情看作是方法。
因為每個人一天的生活都不一定是相同的,所以每天要去做的事我們沒法寫成方法!我們最多隻能定義一些固有的模式的方法抽象,比如起床後做什麼,午飯後做什麼,睡覺前做什麼。這不就是事件嗎?
我們在設計類到時候,如果類的使用有時候也涉及到一些執行過程的問題,而在這個過程中會發生一些未知的事情(未知意味著由外部類來提供,自己提供就是已知了),我們便把這些未知設計成抽象的方法。
由於過程的順序是固定的,比如午飯後做什麼就必須實在午飯後,所以午飯後做什麼事件不能被別人在早上使用(你就是上帝不能把午飯的事情給我挪到早飯,挪了就叫早飯後了)。
同樣的道理,事件的執行不能由外部來決定,這就是事件有別於委託的地方(委託沒有使用限制,隨時隨地都可以用),這也是事件的意義。 整個過程也就是所謂的“生命週期”。
代碼和現實就是這麼的一致,耐人尋味。
廣告回來~~ 繼續看HttpApplication的事件,我把他們按執行的順序貼了出來;從名字就能看出大概的作用。有些我從來沒用過:)
BeginRequest //請求開始
AuthenticateRequest
PostAuthenticateRequest
AuthorizeRequest
PostAuthorizeRequest
ResolveRequestCache
PostResolveRequestCache
PostMapRequestHandler
AcquireRequestState //獲得請求狀態,這時候已經有session了
PostAcquireRequestState
PreRequestHandlerExecute //準備交給HttpHandler處理
Error //請求出現了異常!!!
PostRequestHandlerExecute
ReleaseRequestState //發布請求的狀態
PostReleaseRequestState
UpdateRequestCache
PostUpdateRequestCache
EndRequest //結束請求
PreSendRequestHeaders //準備發送要求標頭資訊,在這我們還能修改內容
PreSendRequestContent //準備發送請求內容,這裡就改不了了
這才是真正的整個生命週期,是不是!而面試題一般考的是Page類的生命週期,這已經過時了,web開發又不光Webform,所以考page類,沒技術含量:)
在HttpApplication裡,把這些事件作為Step,Step by Step的執行下去,下面是HttpApplication構建Step的代碼:
internal override void BuildSteps(WaitCallback stepCallback) { ArrayList steps = new ArrayList(); HttpApplication app = base._application; bool flag = false; UrlMappingsSection urlMappings = RuntimeConfig.GetConfig().UrlMappings; flag = urlMappings.IsEnabled && (urlMappings.UrlMappings.Count > 0); steps.Add(new HttpApplication.ValidatePathExecutionStep(app)); if (flag) { steps.Add(new HttpApplication.UrlMappingsExecutionStep(app)); } 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(new HttpApplication.CallHandlerExecutionStep(app)); app.CreateEventExecutionSteps(HttpApplication.EventPostRequestHandlerExecute, steps); app.CreateEventExecutionSteps(HttpApplication.EventReleaseRequestState, steps); app.CreateEventExecutionSteps(HttpApplication.EventPostReleaseRequestState, steps); steps.Add(new HttpApplication.CallFilterExecutionStep(app)); app.CreateEventExecutionSteps(HttpApplication.EventUpdateRequestCache, steps); app.CreateEventExecutionSteps(HttpApplication.EventPostUpdateRequestCache, steps); this._endRequestStepIndex = steps.Count; app.CreateEventExecutionSteps(HttpApplication.EventEndRequest, steps); steps.Add(new HttpApplication.NoopExecutionStep()); this._execSteps = new HttpApplication.IExecutionStep[steps.Count]; steps.CopyTo(this._execSteps); this._resumeStepsWaitCallback = stepCallback; }
從構建的順序我們也能看出執行的順序,每個Step都有一個Execute的方法,挨個執行下去,如果程式出現異常,則直接跳出。而我們的Page執行是在CallHandlerExecutionStep這個Step裡。
好啦,就講到這唄?今天漢字比較少,代碼比較多;您還有啥不能明白的就評論裡聊吧。沒用過的同學動寫個吧,HttpModule是個好東西哦。PS:我們公司面試的筆試題裡有道題“給一個正在啟動並執行網站增加一個異常監控功能,該怎麼實現?” 你能想到怎麼做嗎?:)