🚫 Ad Blocker Detected

Please disable your AD blocker to continue using this site. Ads help us keep the content free! please press keyboard F5 to refresh page after disabled AD blocker

請關閉廣告攔截器以繼續使用本網站。廣告有助於我們保證內容免費。謝謝! 關閉後請按 F5 刷新頁面

0%

掌控HttpApplication物件建立 - HttpApplicationFactory (第4天)

Agenda

前言:

附上Asp.net執行請求流程圖.

瀏覽器請求IIS流程

在前一篇我們說到HttpRunTime會透過GetApplicationInstance來取得一個IHttpHandler對象.

今天跟著原始碼來了解到底回傳一個什麼IHttpHandler物件給HttpRunTime使用.

 查看原始碼好站 Reference Source

HttpApplication物件

HttpApplication是整個ASP.NET基礎的核心。一個HttpApplication物件在某個時刻只能處理一個請求,只有完成對某個請求處理後,該HttpApplication才能用於後續的請求的處理。

所以ASP.NET利用物件程序池機制來建立或者取得HttpApplication物件。具體來講,當第一個Http請求抵達的時候,ASP.NET會一次建立多個HttpApplication物件,並將其置於池中,選擇其中一個物件來處理該請求。

而如果程序池中沒有HttpApplication物件,Asp.net會建立新的HttpApplication物件處理請求

HttpApplication物件處理Http請求整個生命週期是一個相對複雜的過程,在該過程的不同階段會觸發相應的事件。我們可以註冊相應的事件(如同上一篇介紹事件表)

下圖就是模擬HttpApplicationObjectPool樣子

HttpApplication

取得使用 HttpApplication物件 (GetApplicationInstance)

讓我們看看GetApplicationInstan方法做了什麼事情.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static HttpApplicationFactory _theApplicationFactory = new HttpApplicationFactory();

internal static IHttpHandler GetApplicationInstance(HttpContext context) {
if (_customApplication != null)
return _customApplication;

// Check to see if it's a debug auto-attach request
if (context.Request.IsDebuggingRequest)
return new HttpDebugHandler();

_theApplicationFactory.EnsureInited();

_theApplicationFactory.EnsureAppStartCalled(context);

return _theApplicationFactory.GetNormalApplicationInstance(context);
}

_theApplicationFactory是一個靜態物件

_theApplicationFactory呼叫三個方法EnsureInited,EnsureAppStartCalled,GetNormalApplicationInstance,讓我們一一來解析做了些什麼事情吧

HttpApplicationFactory 初始化 (EnsureInited方法)

通過查找Init方法的代碼以及其中2行如下代碼裡的細節,我們可以得知,這2行代碼主要是從global.asax獲取內容,然後進行編譯。

HttpApplicationFactory.EnsureInited()方法檢查HttpApplicationFactory是否已經被初始化,如果沒有就呼叫HttpApplicationFactory.Init()進行初始化。

Init()中,先獲取網站下global.asax文件完整路徑(透過GetApplicationFile方法),最後呼叫CompileApplication()方法對global.asax進行編譯.

在EnsureInited方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void EnsureInited() {
if (!_inited) {
lock (this) {
if (!_inited) {
Init();
_inited = true;
}
}
}
}

private void CompileApplication() {
// Get the Application Type and AppState from the global file
_theApplicationType = BuildManager.GetGlobalAsaxType();

BuildResultCompiledGlobalAsaxType result = BuildManager.GetGlobalAsaxBuildResult();

if (result != null) {
if (result.HasAppOrSessionObjects) {
GetAppStateByParsingGlobalAsax();
}

_fileDependencies = result.VirtualPathDependencies;
}

if (_state == null) {
_state = new HttpApplicationState();
}

ReflectOnApplicationType();
}

ReflectOnApplicationType方法取得目前特別事件方法,並添加到相對應的MethodInfo成員上

會透過以下三類方法名稱去取方法資訊

  • Application_OnStart or Application_Start
  • Application_OnEnd or Application_End
  • Session_OnEnd or Session_End

取得這些資訊會提供EnsureAppStartCalled去呼叫Application_OnStart方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void ReflectOnApplicationType() {
ArrayList handlers = new ArrayList();
MethodInfo[] methods;

Debug.Trace("PipelineRuntime", "ReflectOnApplicationType");

// get this class methods
methods = _theApplicationType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
foreach (MethodInfo m in methods) {
if (ReflectOnMethodInfoIfItLooksLikeEventHandler(m))
handlers.Add(m);
}

// get base class private methods (GetMethods would not return those)
Type baseType = _theApplicationType.BaseType;
if (baseType != null && baseType != typeof(HttpApplication)) {
methods = baseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
foreach (MethodInfo m in methods) {
if (m.IsPrivate && ReflectOnMethodInfoIfItLooksLikeEventHandler(m))
handlers.Add(m);
}
}

// remember as an array
_eventHandlerMethods = new MethodInfo[handlers.Count];
for (int i = 0; i < _eventHandlerMethods.Length; i++)
_eventHandlerMethods[i] = (MethodInfo)handlers[i];
}

Application_Start方法為什麼只會呼叫一次? (EnsureAppStartCalled)

HttpApplicationFactory.EnsureAppStartCalled方法建立一個HttpApplication物件並觸發Application_OnStart事件(執行Global.asax中的Application_Start(object sender, EventArgs e))

在處理完事件Application_OnStartHttpApplication物件會立即被回收掉,因為系統初始化只需要一次

但是其中GetSpecialApplicationInstance裡會對IIS7做一些特殊的事情這裡就不多提

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void EnsureAppStartCalled(HttpContext context) {
if (!_appOnStartCalled) {
lock (this) {
if (!_appOnStartCalled) {
using (new DisposableHttpContextWrapper(context)) {

WebBaseEvent.RaiseSystemEvent(this, WebEventCodes.ApplicationStart);

FireApplicationOnStart(context);
}

_appOnStartCalled = true;
}
}
}
}

private void FireApplicationOnStart(HttpContext context) {
if (_onStartMethod != null) {
HttpApplication app = GetSpecialApplicationInstance();

app.ProcessSpecialRequest(
context,
_onStartMethod,
_onStartParamCount,
this,
EventArgs.Empty,
null);

RecycleSpecialApplicationInstance(app);
}
}

在處理完事件Application_OnStart呼叫RecycleSpecialApplicationInstance回收HttpApplication物件

返回一個 HttpApplication 物件 (GetNormalApplicationInstance)

方法中主要做.

  1. 判斷_freeList集合中是否有可用HttpApplication物件(物件程序池中),如果沒有就利用HttpRuntime.CreateNonPublicInstance(_theApplicationType)透過反射建立一個新的HttpApplication返回(呼叫完IHttpHandler.ProcessRequst方法後會將這個物件存入_freeList中),最後將
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private HttpApplication GetNormalApplicationInstance(HttpContext context) {
HttpApplication app = null;

if (!_freeList.TryTake(out app)) {
// If ran out of instances, create a new one
app = (HttpApplication)HttpRuntime.CreateNonPublicInstance(_theApplicationType);

using (new ApplicationImpersonationContext()) {
app.InitInternal(context, _state, _eventHandlerMethods);
}
}

if (AppSettings.UseTaskFriendlySynchronizationContext) {
// When this HttpApplication instance is no longer in use, recycle it.
app.ApplicationInstanceConsumersCounter = new CountdownTask(1); // representing required call to HttpApplication.ReleaseAppInstance
app.ApplicationInstanceConsumersCounter.Task.ContinueWith((_, o) => RecycleApplicationInstance((HttpApplication)o), app, TaskContinuationOptions.ExecuteSynchronously);
}
return app;
}

所以最終我們是返回一個HttpApplication物件來使用.

小結

今天我們學到

  1. IHttpHandler GetApplicationInstance(HttpContext context)其實是返回一個HttpApplication物件.
  2. EnsureAppStartCalled方法中呼叫FireApplicationOnStart方法動態建立一個HttpApplication物件,呼叫完Application_OnStart事件就回收掉並使用一個flag布林值代表已經呼叫過.
  3. 這個工廠會有一個 _freeList 集合來存取之前用過的HttpApplication物件,如果集合中沒有適合的HttpApplication物件就會使用反射返回一個新的HttpApplication並將他初始化.
  4. 所以HttpRuntime呼叫的是HttpApplication物件的ProcessRequest方法

下篇會跟大家介紹HttpApplication類別成員詳細資訊

__此文作者__:Daniel Shih(石頭)
__此文地址__: https://isdaniel.github.io/Ithelp-day4/
__版權聲明__:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!

如果本文對您幫助很大,可街口支付斗內鼓勵石頭^^