深入ASP.NET MVC 之一:IIS到路由表


關於IIS的介紹,可以參考Introduction to IIS Architecture 。IIS到托管代碼的入口點是位於System.Web dll中

public sealed class ISAPIRuntime : MarshalByRefObject, IISAPIRuntime, IISAPIRuntime2, IRegisteredObject

的方法

    public int ProcessRequest(IntPtr ecb, int iWRType)
        {
            IntPtr intPtr = IntPtr.Zero;
            if (iWRType == 2)
            {
                intPtr = ecb;
                ecb = UnsafeNativeMethods.GetEcb(intPtr);
            }
            ISAPIWorkerRequest iSAPIWorkerRequest = null;
            int result;
            try
            {
                bool useOOP = iWRType == 1;
                iSAPIWorkerRequest = ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP);
                iSAPIWorkerRequest.Initialize();
                string appPathTranslated = iSAPIWorkerRequest.GetAppPathTranslated();
                string appDomainAppPathInternal = HttpRuntime.AppDomainAppPathInternal;
                if (appDomainAppPathInternal == null || StringUtil.EqualsIgnoreCase(appPathTranslated, appDomainAppPathInternal))
                {
                    HttpRuntime.ProcessRequestNoDemand(iSAPIWorkerRequest);
                    result = 0;
                }
                else
                {
                         //……       
                }
            }
            catch (Exception ex)
            {
                      //……
            }
            return result;
        }

(注:IIS7的入口似乎是PipeLineRuntime.InitializeApplication(IntPtr appContext),過程有所不同,但是不影響后面的流程)其中ecb是一個指向httprequest的信息的指針,由IIS提供。CreateWorkerRequest根據ecb提供的信息,比如IIS的版本、模式等,創建一個ISAPIWorkerRequest對象,ISAPIWorkerReuqeuest是一個http請求的.NET封裝。創建好WorkerRequest之后,調用HttpRuntime.ProcessRequestNoDemand(iSAPIWorkerRequest);開始執行請求,這個方法是會從httpRuntime對象中的一個隊列中獲取一個workerrequest進行處理,最終調用的是HttpRuntime類中的ProcessRequestInternal(代碼有刪節):

private void ProcessRequestInternal(HttpWorkerRequest wr)
        {
            Interlocked.Increment(ref this._activeRequestCount);
            if (this._disposingHttpRuntime)
                 wr.SendStatus(503, "Server Too Busy");
            HttpContext httpContext;
            try
            {
                httpContext = new HttpContext(wr, false);
            }
            catch
            {
                try
                {
                    wr.SendStatus(400, "Bad Request");
                    //…….
                }
                finally
                {
                    Interlocked.Decrement(ref this._activeRequestCount);
                }
            }
            wr.SetEndOfSendNotification(this._asyncEndOfSendCallback, httpContext);
            HostingEnvironment.IncrementBusyCount();
            try
            {
                httpContext.Response.InitResponseWriter();
                IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(httpContext);
                if (applicationInstance == null)
                {
                    throw new HttpException(SR.GetString("Unable_create_app_object"));
                }
                if (applicationInstance is IHttpAsyncHandler)
                {
                    IHttpAsyncHandler httpAsyncHandler = (IHttpAsyncHandler)applicationInstance;
                    httpContext.AsyncAppHandler = httpAsyncHandler;
                    httpAsyncHandler.BeginProcessRequest(httpContext, this._handlerCompletionCallback, httpContext);
                }
                else
                {
                    applicationInstance.ProcessRequest(httpContext);
                    this.FinishRequest(httpContext.WorkerRequest, httpContext, null);
                }
            }
            catch (Exception e)
            {
                httpContext.Response.InitResponseWriter();
                this.FinishRequest(wr, httpContext, e);
            }
        }

在這段代碼中,HttpRuntime可以根據當前服務器的狀況回送不同的Http狀態碼。如果一切正常,首先根據WorkerRequest創建了HttpContext,HttpApplication根據HttpContext創建了一個IHttpHandler對象,這是一個比較復雜的過程。先看代碼:

        internal static IHttpHandler GetApplicationInstance(HttpContext context)
        {
            if (HttpApplicationFactory._customApplication != null)
            {
                return HttpApplicationFactory._customApplication;
            }
            if (context.Request.IsDebuggingRequest)
            {
                return new HttpDebugHandler();
            }
            HttpApplicationFactory._theApplicationFactory.EnsureInited();
            HttpApplicationFactory._theApplicationFactory.EnsureAppStartCalled(context);
            return HttpApplicationFactory._theApplicationFactory.GetNormalApplicationInstance(context);
        }

customApplication應該是使用 ASP.NET State Service的時候的情況,DebugHandler應該是調試狀態下的情況,不作深究,除此以外,一共有三大步驟,首先要確保調用且僅調用了一次 ApplicationFactory的Init方法,在這個方法中,主要完成了以下工作(代碼有刪節):

        private void Init()
        {
             this._appFilename = HttpApplicationFactory.GetApplicationFile();
             this.CompileApplication();
        }

其中GetApplicationFile為:

internal static string GetApplicationFile()
        {
            return Path.Combine(HttpRuntime.AppDomainAppPathInternal, "global.asax");
        }

ASP.NET在這里關聯上了global.asax .在CompileApplication方法中,除了回去編譯ASP.NET項目中未編譯的代碼,還有兩件重要的工作:

private void CompileApplication()
        {
            this._theApplicationType = BuildManager.GetGlobalAsaxType();
            //Use BuilderManager to build code
            this.ReflectOnApplicationType();
        }

首先是設置了theApplicationType,比如默認情況下,一個ASP.NET MVC的applicationType將是 MvcApplication,也就是global.asax中那個類。ReflectOnApplicationType 代碼如下:

        private void ReflectOnApplicationType()
        {
            ArrayList arrayList = new ArrayList();
            MethodInfo[] methods = this._theApplicationType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
            MethodInfo[] array = methods;
            for (int i = 0; i < array.Length; i++)
            {
                MethodInfo methodInfo = array[i];
                if (this.ReflectOnMethodInfoIfItLooksLikeEventHandler(methodInfo))
                {
                    arrayList.Add(methodInfo);
                }
            }
            Type baseType = this._theApplicationType.BaseType;
            if (baseType != null && baseType != typeof(HttpApplication))
            {
                methods = baseType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic);
                MethodInfo[] array2 = methods;
                for (int j = 0; j < array2.Length; j++)
                {
                    MethodInfo methodInfo2 = array2[j];
                    if (methodInfo2.IsPrivate && this.ReflectOnMethodInfoIfItLooksLikeEventHandler(methodInfo2))
                    {
                        arrayList.Add(methodInfo2);
                    }
                }
            }
            this._eventHandlerMethods = new MethodInfo[arrayList.Count];
            for (int k = 0; k < this._eventHandlerMethods.Length; k++)
            {
                this._eventHandlerMethods[k] = (MethodInfo)arrayList[k];
            }
        }

簡單來說,這個方法反射了global.asax中的那個類,並且將里面的類似於EventHandler的方法放到this._eventHandlerMethods中。

例如,ReflectOnMethodInfoIfItLooksLikeEventHandler中的代碼片段:

if (StringUtil.EqualsIgnoreCase(name, "Application_OnStart") || StringUtil.EqualsIgnoreCase(name, "Application_Start"))
            {
                this._onStartMethod = m;
                this._onStartParamCount = parameters.Length;
            }

確保ApplicationFactory的Init被調用過之后,接下來,要保證Global.asax中的App_Start被調用。EnsureAppStartCalled 的核心代碼就是調用了this._onStartMethod,這個方法在上面介紹Init方法中已經被初始化好。EnsureAppStartCalled做的事情雖然簡單,但是其實現還是挺繁瑣的,估計是為了線程安全性等考慮,不再分析其具體實現。最后,就是真正的獲取一個ApplicationHandler的方法了:

        private HttpApplication GetNormalApplicationInstance(HttpContext context)
        {
            HttpApplication httpApplication = null;
            lock (this._freeList)
            {
                if (this._numFreeAppInstances > 0)
                {
                    httpApplication = (HttpApplication)this._freeList.Pop();
                    this._numFreeAppInstances--;
                    if (this._numFreeAppInstances < this._minFreeAppInstances)
                    {
                        this._minFreeAppInstances = this._numFreeAppInstances;
                    }
                }
            }
            if (httpApplication == null)
            {
                httpApplication = (HttpApplication)HttpRuntime.CreateNonPublicInstance(this._theApplicationType);
                using (new ApplicationImpersonationContext())
                {
                    httpApplication.InitInternal(context, this._state, this._eventHandlerMethods);
                }
            }
            if (AppSettings.UseTaskFriendlySynchronizationContext)
            {
                httpApplication.ApplicationInstanceConsumersCounter = new CountdownTask(1);
                httpApplication.ApplicationInstanceConsumersCounter.Task.ContinueWith(delegate(Task _, object o)
                {
                    HttpApplicationFactory.RecycleApplicationInstance((HttpApplication)o);
                }, httpApplication, TaskContinuationOptions.ExecuteSynchronously);
            }
            return httpApplication;
        }

可以看到,首先可用的HttpApplication都是緩存在一個List中的,如果沒有可用的HttpApplication,則會根據theApplicationType來創建一個,核心方法是調用InitInternal方法,注意到最后一個參數是this._eventHandlerMethods,這就是global.asax中的各個EventHandler。InitInternal方法也是一個比較復雜的方法,里面對於IIS采用的是Integrated模式還是Classic模式進行分別的處理,主要完成的工作時HttpModule的初始化和處理請求過程中每個步驟觸發事件處理程序的准備。先看Integrate模式下Module的初始化:

        private void InitIntegratedModules()
        {
            this._moduleCollection = this.BuildIntegratedModuleCollection(HttpApplication._moduleConfigInfo);
            this.InitModulesCommon();
        }

第一步是根據配置的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);
            }
            this._currentModuleCollectionKey = null;
            this.InitAppLevelCulture();
        }
注意加粗的代碼,它調用了IHttpModule的Init方法。這是ASP.NET掛載Module關鍵之處。接下來看
public class UrlRoutingModule : IHttpModule

這個類的Init實現。這個HttpModule是實現URL路由的關鍵。在.NET 4之前它是位於System.Web.Routing.dll之中的,.NET 4之后它已經被合並入System.Web.dll中了成為了Asp.NET不可分割的一部分。

        protected virtual void Init(HttpApplication application)
        {
            if (application.Context.Items[UrlRoutingModule._contextKey] != null)
            {
                return;
            }
            application.Context.Items[UrlRoutingModule._contextKey] = UrlRoutingModule._contextKey;
            application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
        }

最核心的代碼是最后一行,它注冊了 PostResolveRequestCache事件的響應程序OnApplicationPostResolveRequestCache。 響應程序的核心代碼如下:

public virtual void PostResolveRequestCache(HttpContextBase context)
        {
            RouteData routeData = this.RouteCollection.GetRouteData(context);
            if (routeData == null)
            {
                return;
            }
            IRouteHandler routeHandler = routeData.RouteHandler;
            if (routeHandler == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, SR.GetString("UrlRoutingModule_NoRouteHandler"), new object[0]));
            }
            if (routeHandler is StopRoutingHandler)
            {
                return;
            }
            RequestContext requestContext = new RequestContext(context, routeData);
            context.Request.RequestContext = requestContext;
            IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
            if (httpHandler == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, SR.GetString("UrlRoutingModule_NoHttpHandler"), new object[]
                {
                    routeHandler.GetType()
                }));
            }
            if (!(httpHandler is UrlAuthFailureHandler))
            {
                context.RemapHandler(httpHandler);
                return;
            }
            if (FormsAuthenticationModule.FormsAuthRequired)
            {
                UrlAuthorizationModule.ReportUrlAuthorizationFailure(HttpContext.Current, this);
                return;
            }
            throw new HttpException(401, SR.GetString("Assess_Denied_Description3"));
        }

在這里,代碼根據RouteData獲得RouteHandler,RouteHandler獲得HttpHandler。這里的詳情下文再分析,至此,一個HTTP請求將會通過IIS傳遞到路由模塊了。下面再會這段代碼何時會被觸發。先回到InitInternal方法中第二項工作,也就是處理請求過程中每個步驟觸發事件處理程序的准備。ASP.NET首先定義了一個枚舉來表示處理一個request的處理周期

    public enum RequestNotification
    {
        BeginRequest = 1,
        AuthenticateRequest = 2,
        AuthorizeRequest = 4,
        ResolveRequestCache = 8,
        MapRequestHandler = 16,
        AcquireRequestState = 32,
        PreExecuteRequestHandler = 64,
        ExecuteRequestHandler = 128,
        ReleaseRequestState = 256,
        UpdateRequestCache = 512,
        LogRequest = 1024,
        EndRequest = 2048,
        SendResponse = 536870912
    }

在InitInternal中,InitModule完成之后緊接着調用了

private void HookupEventHandlersForApplicationAndModules(MethodInfo[] handlers)
     

這個方法的作用是將Global.asax中的方法(事件響應)加到合適的事件上,這里的事件可以是各個HttpModule的事件。實際上就是根據命名規則去找到相應的HttpModule的事件。這部分的實現也是很有技巧性,本文不多做分析,可以參考 http://aspnetresources.com/articles/event_handlers_in_global_asax 。 再接下來,InitInternal實例化了一個StepManager,同樣有Integrate和Classic的兩種,下面以Integrate的PipelStepManager為例,緊接着調用了StepManager的BuildStep方法:

            internal override void BuildSteps(WaitCallback stepCallback)
            {
                HttpApplication application = this._application;
                HttpApplication.IExecutionStep step = new HttpApplication.MaterializeHandlerExecutionStep(application);
                application.AddEventMapping("ManagedPipelineHandler", RequestNotification.MapRequestHandler, false, step);
                application.AddEventMapping("ManagedPipelineHandler", RequestNotification.ExecuteRequestHandler, false, application.CreateImplicitAsyncPreloadExecutionStep());
                HttpApplication.IExecutionStep step2 = new HttpApplication.CallHandlerExecutionStep(application);
                application.AddEventMapping("ManagedPipelineHandler", RequestNotification.ExecuteRequestHandler, false, step2);
                HttpApplication.IExecutionStep step3 = new HttpApplication.TransitionToWebSocketsExecutionStep(application);
                application.AddEventMapping("ManagedPipelineHandler", RequestNotification.EndRequest, true, step3);
                HttpApplication.IExecutionStep step4 = new HttpApplication.CallFilterExecutionStep(application);
                application.AddEventMapping("AspNetFilterModule", RequestNotification.UpdateRequestCache, false, step4);
                application.AddEventMapping("AspNetFilterModule", RequestNotification.LogRequest, false, step4);
                this._resumeStepsWaitCallback = stepCallback;
            }

 

這里面實例化了很多具體的IExecutionStep對象,並且和RequestNotification關聯起來。這些step將是完成一個request的必要步驟。AddEventMapping的核心代碼如下:

            PipelineModuleStepContainer moduleContainer = this.GetModuleContainer(moduleName);
            if (moduleContainer != null)
            {
                moduleContainer.AddEvent(requestNotification, isPostNotification, step);
            }

moduleContainer 中有一個IExecutionStep列表,里面的step是按照requestNotification的順序排列的,這點非常重要。至此,InitInternal的工作基本完成了。HttpApplication的一個實例也已經初始化完畢,直接跳回至ProccessRequestInternal方法,接下來就是調用BeginProcessRequest開始真正的處理了。這個方法的核心是調用 StepManager的ResumeSteps方法。更具體的,對於使用Integrated模式的ASP.NET的項目來說,是調用了PipelineStepManager的ResumeSteps方法。這個方法也很復雜,但是核心的代碼就是兩行:

     HttpApplication.IExecutionStep nextEvent = this._application.CurrentModuleContainer.GetNextEvent(context.CurrentNotification, context.IsPostNotification, context.CurrentModuleEventIndex);
     error = this._application.ExecuteStep(nextEvent, ref flag3);

也就是從PipelineModuleStepContainer中取出准備好的step逐個執行。本文不再分析每個step的具體內容,有了以上的准備,接下來看本文的主題,routing module是什么時候被執行的。 回到上面routing module的Init方法中注冊事件的方法,其內部實現是:

        public event EventHandler PostResolveRequestCache
        {
            add
            {
                this.AddSyncEventHookup(HttpApplication.EventPostResolveRequestCache, value, RequestNotification.ResolveRequestCache, true);
            }
            remove
            {
                this.RemoveSyncEventHookup(HttpApplication.EventPostResolveRequestCache, value, RequestNotification.ResolveRequestCache, true);
            }
        }

AddSyncEventHookup的核心代碼如下:

                PipelineModuleStepContainer moduleContainer = this.GetModuleContainer(this.CurrentModuleCollectionKey);
                if (moduleContainer != null)
                {
                    HttpApplication.SyncEventExecutionStep step = new HttpApplication.SyncEventExecutionStep(this, (EventHandler)handler);
                    moduleContainer.AddEvent(notification, isPostNotification, step);
                }

在這里,他添加了一個SyncEventExecutionStep到moduleContainer中,因此,在執行到HttpApplication.EventPostResolveRequestCache的step的時候,SyncEventExecutionStep的Execute方法將被執行,這個方法的核心代碼是:

this._handler(this._application, this._application.AppEvent);

 

這里的_handler,將會是new EventHandler(this.OnApplicationPostResolveRequestCache); _application是HttpApplication實例。

因此,routing module的代碼就被執行了。

小結:本文大致分析了System.Web中的源代碼,以asp.net中一個request的生命周期是如何的,介紹了routing module是怎樣掛載到這個處理流程中的。Routing module只是一個普通的http module,其他的http module原理類似。下文將重點介紹routing module是如何工作的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM