[Day05] Asp.Net重要物件HttpApplication(一) 初始化建立IHttpMoudule

前言:

上一篇文章分享HttpApplicationFactory.GetApplicationInstance方法返回一個HttpApplicationHttpRuntime來呼叫使用.

今天開始介紹HttpApplication這個很重要的類別,它可謂是我們Asp.net中很複雜但重要的類別

Global.cs是繼承HttpApplication類別,但為什麼需要繼承這個類別呢? 讓我們繼續看下去.

 

查看原始碼好站 Reference Source
此文的程式碼比較多我會在原始碼上邊上說明相對應編號方便大家觀看

此篇同步發布在筆者Blog [Day05] Asp.Net重要物件HttpApplication(一) 初始化建立IHttpMoudule

初始化HttpApplication (InitInternal)

GetNormalApplicationInstance返回一個HttpApplication物件前會呼叫初始化HttpApplication.InitInternal方法

這個方法主要做下面幾件事情

  1. 初始化HttpModule,讀取Host configappconfig 註冊的HttpMoudle,並調用Init方法,使用AOP編成方式註冊使用事件
  2. 提供一個Hock給繼承Application物件來初始化設定使用
  3. 判斷要走管道模式還是經典模式
  4. 建置Pipleline流程
  5. 建立許多實現IExecutionStep接口的物件並添加到目前HttpApplication物驗的_execSteps集合中.從這裡我們可以看到HttpApplication是以異步的方式處理請求

HttpModule是在InitInternal方法中被讀取執行.

我們可以透過 HttpContext.ApplicationInstance.Modules ,得知目前所有載入HttpModule.

下面是InitInternal原始碼(核心動作有寫中文註解)

internal void InitInternal(HttpContext context, HttpApplicationState state, MethodInfo[] handlers){
	// Remember state
	_state = state;

	PerfCounters.IncrementCounter(AppPerfCounter.PIPELINES);

	try {
		try {
			_initContext = context;
			_initContext.ApplicationInstance = this;

			context.ConfigurationPath = context.Request.ApplicationPathObject;

			using (new DisposableHttpContextWrapper(context)) {

				// 1.初始化HttpModule.
				if (HttpRuntime.UseIntegratedPipeline) {

					try {
						context.HideRequestResponse = true;
						_hideRequestResponse = true;
						InitIntegratedModules();
					}
					finally {
						context.HideRequestResponse = false;
						_hideRequestResponse = false;
					}
				}
				else {
					InitModules();
				}

				// Hookup event handlers via reflection
				if (handlers != null)
					HookupEventHandlersForApplicationAndModules(handlers);

				// Initialization of the derived class
				_context = context;
				if (HttpRuntime.UseIntegratedPipeline && _context != null) {
					_context.HideRequestResponse = true;
				}
				_hideRequestResponse = true;

				try {
					//2.提供一個Hock給繼承Application物件來初始化設定使用
					Init();
				}
				catch (Exception e) {
					RecordError(e);
				}
			}

			if (HttpRuntime.UseIntegratedPipeline && _context != null) {
				_context.HideRequestResponse = false;
			}
			_hideRequestResponse = false;
			_context = null;
			_resumeStepsWaitCallback= new WaitCallback(this.ResumeStepsWaitCallback);

			//3. 判斷要走管道模式還是經典模式
			if (HttpRuntime.UseIntegratedPipeline) {
				_stepManager = new PipelineStepManager(this);
			}
			else {
				_stepManager = new ApplicationStepManager(this);
			}

            //4. 建置Pipleline流程
			_stepManager.BuildSteps(_resumeStepsWaitCallback);
		}
		finally {
			_initInternalCompleted = true;

			// Reset config path
			context.ConfigurationPath = null;
			// don't hold on to the context
			_initContext.ApplicationInstance = null;
			_initContext = null;
		}
	}
	catch { // Protect against exception filters
		throw;
	}
}

載入所有註冊HttpModule(InitModules方法)

這個方法讀取註冊的HttpModule並共同放在一起,在一起呼叫InitModulesCommon方法來呼叫所有Modules的Init方法

private void InitModules() {
	
	HttpModulesSection pconfig = RuntimeConfig.GetAppConfig().HttpModules;
	HttpModuleCollection moduleCollection = pconfig.CreateModules();
	HttpModuleCollection dynamicModules = CreateDynamicModules();
	moduleCollection.AppendCollection(dynamicModules);

	_moduleCollection = moduleCollection; 

	InitModulesCommon();
}

private void InitModulesCommon() {
	int n = _moduleCollection.Count;

	for (int i = 0; i < n; i++) {
		_currentModuleCollectionKey = _moduleCollection.GetKey(i);
		_moduleCollection[i].Init(this);
	}

	_currentModuleCollectionKey = null;
	InitAppLevelCulture();
}

_moduleCollection[i].Init(this); 其中的this就是把HttpApplication物件本身傳入這也是為什麼我們繼承IHttpMoudel介面可以共同使用同一個HttpApplication物件.

public interface IHttpModule
{

	void Init(HttpApplication context);

	void Dispose();
}

上面呼叫的就是void Init(HttpApplication context)方法.

如果要取得目前所註冊HttpModule可透過HttpApplication.Modules屬性

HttpModule添加Asp.net事件原理解析.

我們在HttpModule上10多個事件作擴充在ASP.net是如何完成呢?

首先我們來看看事件方法原始碼.

發現每個事件都會呼叫AddSyncEventHookup方法來建立事件,此方法有幾個參數

  1. object key:此事件識別資訊(每個事件都有自己的Object),如BeginRequest事件傳入EventBeginRequest物件.
  2. Delegate handler:使用者撰寫事件方法.
  3. RequestNotification notification:屬於哪種分群.
/// <devdoc><para>[To be supplied.]</para></devdoc>
public event EventHandler BeginRequest {
	add { AddSyncEventHookup(EventBeginRequest, value, RequestNotification.BeginRequest); }
	remove { RemoveSyncEventHookup(EventBeginRequest, value, RequestNotification.BeginRequest); }
}


/// <devdoc><para>[To be supplied.]</para></devdoc>
public event EventHandler AuthenticateRequest {
	add { AddSyncEventHookup(EventAuthenticateRequest, value, RequestNotification.AuthenticateRequest); }
	remove { RemoveSyncEventHookup(EventAuthenticateRequest, value, RequestNotification.AuthenticateRequest); }
}

// internal - for back-stop module only
internal event EventHandler DefaultAuthentication {
	add { AddSyncEventHookup(EventDefaultAuthentication, value, RequestNotification.AuthenticateRequest); }
	remove { RemoveSyncEventHookup(EventDefaultAuthentication, value, RequestNotification.AuthenticateRequest); }
}

//....

private void AddSyncEventHookup(object key, Delegate handler, RequestNotification notification, bool isPostNotification = false) {
	ThrowIfEventBindingDisallowed();

	// add the event to the delegate invocation list
	// this keeps non-pipeline ASP.NET hosts working
	Events.AddHandler(key, handler);

	// For integrated pipeline mode, add events to the IExecutionStep containers only if
	// InitSpecial has completed and InitInternal has not completed.
	if (IsContainerInitalizationAllowed) {
		// lookup the module index and add this notification
		PipelineModuleStepContainer container = GetModuleContainer(CurrentModuleCollectionKey);
		//WOS 1985878: HttpModule unsubscribing an event handler causes AV in Integrated Mode
		if (container != null) {
			SyncEventExecutionStep step = new SyncEventExecutionStep(this, (EventHandler)handler);
			container.AddEvent(notification, isPostNotification, step);
		}
	}
}

上面AddSyncEventHookup傳入object keyHttpapplication物件在一開始就會建立下面這些靜態方法(當作每個事件Key)

// event handlers
private static readonly object EventDisposed = new object();
private static readonly object EventErrorRecorded = new object();
private static readonly object EventRequestCompleted = new object();
private static readonly object EventPreSendRequestHeaders = new object();
private static readonly object EventPreSendRequestContent = new object();
private static readonly object EventBeginRequest = new object();
private static readonly object EventAuthenticateRequest = new object();
private static readonly object EventDefaultAuthentication = new object();
private static readonly object EventPostAuthenticateRequest = new object();
private static readonly object EventAuthorizeRequest = new object();
private static readonly object EventPostAuthorizeRequest = new object();
private static readonly object EventResolveRequestCache = new object();
private static readonly object EventPostResolveRequestCache = new object();
private static readonly object EventMapRequestHandler = new object();
private static readonly object EventPostMapRequestHandler = new object();
private static readonly object EventAcquireRequestState = new object();
private static readonly object EventPostAcquireRequestState = new object();
private static readonly object EventPreRequestHandlerExecute = new object();
private static readonly object EventPostRequestHandlerExecute = new object();
private static readonly object EventReleaseRequestState = new object();
private static readonly object EventPostReleaseRequestState = new object();
private static readonly object EventUpdateRequestCache = new object();
private static readonly object EventPostUpdateRequestCache = new object();
private static readonly object EventLogRequest = new object();
private static readonly object EventPostLogRequest = new object();
private static readonly object EventEndRequest = new object();

最後把事件資訊添加到Events集合中,已便建立管道時使用.

/// <devdoc>
///    <para>[To be supplied.]</para>
/// </devdoc>
protected EventHandlerList Events {
	get {
		if (_events == null) {
			_events = new EventHandlerList();
		}
		return _events;
	}
}

透過上面機制就可以確保對於Events取得事件時順序.

管道模式 vs 經典模式

下面兩張圖是管道模式經典模式

經典模式

經典模式

管道模式

管道模式

圖片來源

除了執行流程不一樣跟一些差異外,他們最終還是為了要找到一個HttpHandler來執行.

取得執行HttpHandler物件

如果有認真看原始碼的小夥伴,會發現HttpApplicationProcessRequest目前是throw一個錯誤.

那他是怎麼找到使用HttpHandler物件並完成請求的呢?

void IHttpHandler.ProcessRequest(HttpContext context) {
	throw new HttpException(SR.GetString(SR.Sync_not_supported));
}

因為HttpRunTime是呼叫異步請求BeginProcessRequest方法.

這邊提一下 啟動吧!Asp.Net IsapiRunTime & HttpRuntime會先判斷app物件是否實現IHttpAsyncHandler.

HttpApplication有實現IHttpAsyncHandler介面.所以優先執行異步請求.

if (app is IHttpAsyncHandler) {
	// asynchronous handler
	IHttpAsyncHandler asyncHandler = (IHttpAsyncHandler)app;
	context.AsyncAppHandler = asyncHandler;
	asyncHandler.BeginProcessRequest(context, _handlerCompletionCallback, context);
}
else {
	// synchronous handler
	app.ProcessRequest(context);
	FinishRequest(context.WorkerRequest, context, null);
}

小結

今天我們學到

  1. HttpApplication去讀取所有註冊的HttpModule並呼叫他們的Init方法.
  2. 經典模式管道模式除了執行流程不同最終目標還是找尋一個HttpHandler
  3. HttpRunTime是呼叫異步請求
  4. 了解HttpModule添加Asp.net事件原理解析

很多文章都會提到10多個事件(BeginRequestEndRequest.....等)

下篇會介紹StepManager如何建立管道和如何呼叫事件並找尋HttpHandler來執行.


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