Mvc生命周期深度剖析

来源:http://www.cnblogs.com/scottz/archive/2016/01/07/5109176.html
-Advertisement-
Play Games

客戶端發送請求->IIS, UrlRouting模塊對比URL, 預設如果該URL能對應到實體文件則退出MVC管道把控制權交還給IIS.如果RegisterRoutes中的路由規則對比成功預設情況下交給MvcRouteHandler(IRouteHandler)處理,IRouteHandler的作用...


客戶端發送請求->IIS, UrlRouting模塊對比URL, 預設如果該URL能對應到實體文件則退出MVC管道把控制權交還給IIS.

如果RegisterRoutes中的路由規則對比成功預設情況下交給MvcRouteHandler(IRouteHandler)處理, IRouteHandler的作用是決策使用哪一個HttpHandler處理本次請求,IRouteHandler介面定義如下:

    [TypeForwardedFrom("System.Web.Routing, Version=3.5.0.0, Culture=Neutral, PublicKeyToken=31bf3856ad364e35")]
    public interface IRouteHandler
    {
        IHttpHandler GetHttpHandler(RequestContext requestContext);
    }

微軟的提供的MVC框架中MvcRouteHandler實現了IRouteHandler介面,預設交給MvcHandler處理,代碼如下:

public class MvcRouteHandler : IRouteHandler
    {
        private IControllerFactory _controllerFactory;
        
        public MvcRouteHandler()
        {
        }
        
        public MvcRouteHandler(IControllerFactory controllerFactory)
        {
            this._controllerFactory = controllerFactory;
        }
        
        protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            requestContext.HttpContext.SetSessionStateBehavior(this.GetSessionStateBehavior(requestContext));
            return new MvcHandler(requestContext);
        }
        
        protected virtual SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext)
        {
            string str = (string) requestContext.RouteData.Values["controller"];
            if (string.IsNullOrWhiteSpace(str))
            {
                throw new InvalidOperationException(MvcResources.MvcRouteHandler_RouteValuesHasNoController);
            }
            IControllerFactory factory = this._controllerFactory ?? ControllerBuilder.Current.GetControllerFactory();
            return factory.GetControllerSessionBehavior(requestContext, str);
        }
        
        IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
        {
            return this.GetHttpHandler(requestContext);
        }
    }

As we know, 所有的HttpHandler的入口點為ProcessRequest,IHttpHandler介面定義如下:

    public interface IHttpHandler
    {
        void ProcessRequest(HttpContext context);
        
        bool IsReusable { get; }
    }

在MvcRouteHandler.ProcessRequest中首先傳入HttpContext以及兩個out參數到ProcessRequestInit方法中根據路由參數獲取ControllerFactory類以及對應的Controller, 代碼如下:

        protected internal virtual void ProcessRequest(HttpContextBase httpContext)
        {
            IController controller;
            IControllerFactory factory;
            this.ProcessRequestInit(httpContext, out controller, out factory);
            try
            {
                controller.Execute(this.RequestContext);
            }
            finally
            {
                factory.ReleaseController(controller);
            }
        }
        
        private void ProcessRequestInit(HttpContextBase httpContext, out IController controller, out IControllerFactory factory)
        {
            HttpContext current = HttpContext.Current;
            if ((current != null) && (ValidationUtility.IsValidationEnabled(current) == true))
            {
                ValidationUtility.EnableDynamicValidation(current);
            }
            this.AddVersionHeader(httpContext);
            this.RemoveOptionalRoutingParameters();
            string requiredString = this.RequestContext.RouteData.GetRequiredString("controller");
            factory = this.ControllerBuilder.GetControllerFactory();
            controller = factory.CreateController(this.RequestContext, requiredString);
            if (controller == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBuilder_FactoryReturnedNull, new object[] { factory.GetType(), requiredString }));
            }
        }

在Asp.net Mvc中所有的Controller都實現了IController介面,該介面定義了一個Execute方法,代碼如下:

    public interface IController
    {
        void Execute(RequestContext requestContext);
    }

微軟提供的預設框架中實現了該介面的Class為ControllerBase, ControllerBase除了實現Execute方法外還定義了一個抽象方法ExecuteCore,而實現了這個方法的就是我們工作中定義Controller時繼承需要繼承的System.Web.MvcController類,接上文講,在MvcRouteHandler.ProcessRequest中取得Controller後接著會進入ControllerBase的Execute方法,代碼如下:

        protected virtual void Execute(RequestContext requestContext)
        {
            if (requestContext == null)
            {
                throw new ArgumentNullException("requestContext");
            }
            if (requestContext.HttpContext == null)
            {
                throw new ArgumentException(MvcResources.ControllerBase_CannotExecuteWithNullHttpContext, "requestContext");
            }
            this.VerifyExecuteCalledOnce();
            this.Initialize(requestContext);
            using (ScopeStorage.CreateTransientScope())
            {
                this.ExecuteCore();
            }
        }

在做了一些驗證和初始化後會進入System.Web.MvcController類的ExecuteCore方法,代碼如下:

        protected override void ExecuteCore()
        {
            this.PossiblyLoadTempData();
            try
            {
                string requiredString = this.RouteData.GetRequiredString("action");
                if (!this.ActionInvoker.InvokeAction(base.ControllerContext, requiredString))
                {
                    this.HandleUnknownAction(requiredString);
                }
            }
            finally
            {
                this.PossiblySaveTempData();
            }
        }

該方法中會通過路由參數得知要運行的ActionName並通過ActionInvoker調用該Action響應客戶端, 到此為止就是我們平時經常使用的Action所做的事情了,如果直接用Response客戶端或返回數據本次請求的生命周期則到此結束.

若返回ViewResult還有有一些額外的操作, 通過ViewEngine獲取到相應的View返回給客戶端,我們先看一下IViewEngine介面的定義:

    public interface IViewEngine
    {
        ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache);
        ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache);
        void ReleaseView(ControllerContext controllerContext, IView view);
    }

.Net內建了兩套ViewEngine,一套為WebFormViewEngine,另一套為我們比較常用的RazorViewEngine,代碼如下:

public class RazorViewEngine : BuildManagerViewEngine
    {
        internal static readonly string ViewStartFileName = "_ViewStart";
        
        public RazorViewEngine() : this(null)
        {
        }
        
        public RazorViewEngine(IViewPageActivator viewPageActivator) : base(viewPageActivator)
        {
            base.AreaViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" };
            base.AreaMasterLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" };
            base.AreaPartialViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.cshtml", "~/Areas/{2}/Views/{1}/{0}.vbhtml", "~/Areas/{2}/Views/Shared/{0}.cshtml", "~/Areas/{2}/Views/Shared/{0}.vbhtml" };
            base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" };
            base.MasterLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" };
            base.PartialViewLocationFormats = new string[] { "~/Views/{1}/{0}.cshtml", "~/Views/{1}/{0}.vbhtml", "~/Views/Shared/{0}.cshtml", "~/Views/Shared/{0}.vbhtml" };
            base.FileExtensions = new string[] { "cshtml", "vbhtml" };
        }
        
        protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
        {
            string layoutPath = null;
            bool runViewStartPages = false;
            IEnumerable<string> fileExtensions = base.FileExtensions;
            return new RazorView(controllerContext, partialPath, layoutPath, runViewStartPages, fileExtensions, base.ViewPageActivator) { DisplayModeProvider = base.DisplayModeProvider };
        }
        
        protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
        {
            string layoutPath = masterPath;
            bool runViewStartPages = true;
            IEnumerable<string> fileExtensions = base.FileExtensions;
            return new RazorView(controllerContext, viewPath, layoutPath, runViewStartPages, fileExtensions, base.ViewPageActivator) { DisplayModeProvider = base.DisplayModeProvider };
        }
    }

可以看到在ViewEngine中已經內建了很多找到某個具體View文件的格式,這也是為什麼我們如果不自定義MVC框架的某些模塊必須嚴格按照微軟提供的目錄結構來分割文件。

Tip: 這實際上是有好處的,也是微軟提倡的Convention Over Configuration(覺得沒法有一個簡單詞概括所以就不翻譯了), 意思就是他提供了一個標準的模式,大家都遵守這樣一個規則,以降低不同的人維護同一個項目的複雜度: 我們知道如果每個團隊甚至每個人寫出的項目如果都是不同的目錄結構與項目框架,如果臨時換另一個團隊或另一個人來接手是需要花很多時間來熟悉的, 項目越複雜也就越難以維護。

接著通過ViewEngine通過CreateView實例化一個IView對象並返回,IView介面定義如下:

    public interface IView
    {
        void Render(ViewContext viewContext, TextWriter writer);
    }

顯然就是Render用於響應客戶端的方法了,在RazorViewEngine中將會返回一個實現了IView介面的RazorView對象並調用Render方法最終輸出頁面到客戶端:

        protected override void RenderView(ViewContext viewContext, TextWriter writer, object instance)
        {
            if (writer == null)
            {
                throw new ArgumentNullException("writer");
            }
            WebViewPage page = instance as WebViewPage;
            if (page == null)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.CshtmlView_WrongViewBase, new object[] { base.ViewPath }));
            }
            page.OverridenLayoutPath = this.LayoutPath;
            page.VirtualPath = base.ViewPath;
            page.ViewContext = viewContext;
            page.ViewData = viewContext.ViewData;
            page.InitHelpers();
            if (this.VirtualPathFactory != null)
            {
                page.VirtualPathFactory = this.VirtualPathFactory;
            }
            if (this.DisplayModeProvider != null)
            {
                page.DisplayModeProvider = this.DisplayModeProvider;
            }
            WebPageRenderingBase startPage = null;
            if (this.RunViewStartPages)
            {
                startPage = this.StartPageLookup(page, RazorViewEngine.ViewStartFileName, this.ViewStartFileExtensions);
            }
            HttpContextBase httpContext = viewContext.HttpContext;
            WebPageRenderingBase base4 = null;
            object model = null;
            page.ExecutePageHierarchy(new WebPageContext(httpContext, base4, model), writer, startPage);
        }

至此完整的生命周期介紹完畢,實際上關於後面的ViewEngine部分只是粗淺的介紹了一下運轉流程,關於具體細節需要更大的篇幅來介紹在此就不再展開,目前除了微軟內建的ViewEngine外, 也有很多優秀的第三方ViewEngine可供大家參考比如SparkViewEngine、NDjango、NHaml等. 

由於個人水平有限,理解有誤的地方懇請斧正!

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Windows下搭建Git開發環境主要有以下三種方法: 1,VS,vs2013和vs2015中已經集成了git插件了 2,msysGit+TortoiseGit 3,msysGit+SourceTree 當然還有其它IDE工具... 註:msysGit是Git在Windows下的版本。
  • 最近遇到VS2013,在打開解決方案時,報如下錯誤:“未找到與約束ContractNameMicrosoft.Internal.VisualStudio.PlatformUI.ISolutionAttachedCollectionService RequiredTypeIdentity Micros...
  • #//*************************************************************#//編輯人:#//編輯單位:#//編輯作用:移動電腦到對應的OU下#//編製時間:2016.01.05#//******************************...
  • 以後SqlSugar所有更新都會在這個貼子更新SqlSugar是一款輕量級的MSSQL ORM ,除了具有媲美ADO的性能外還具有和EF相似簡單易用的語法。學習列表 0、功能更新1、SqlSugar基礎應用2、使用SqlSugar處理大數據3、使用SqlSugar實現Join待更新4、使用SqlSu...
  • 前一段時間在給移動端寫介面時遇到一個調用介面發送郵箱 session 一直獲取不到的問題。我來給遇到問題的同志們說一說自個在網上查了好多資料,問了一些朋友後。終於找到解決方案了。大家都知道webapi預設是不開啟session會話支持的。所以需要Global文件中要重寫方法如下: public o....
  • 平常我們在做多個條件判斷的時候喜歡用switch(表達式){ case : 常量1 表達式1; break; case : 常量2 表達式2; break; case : 常量3 表達式3; break;.... default: 常量4 表達式4;...
  • 由於工作中需要,我接觸了dsoframer控制項,我辦公電腦是64系統,在使用時,總是報沒有註冊類錯誤。我很是奇怪,dsoframer.ocx控制項我都註冊過的呀。然後在網上查閱了許多相關資料。悲哀的是,感覺網上都是千篇一律的。說64系統需要在syswow64文件夾下註冊控制項,有的說同時需要也在syst...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...