1. 程式人生 > >窺探ASP.Net MVC底層原理 實現跨越Session的分散式TempData

窺探ASP.Net MVC底層原理 實現跨越Session的分散式TempData

1、問題的引出

我相信大家在專案中都使用過TempData,TempData是一個字典集合,一般用於兩個請求之間臨時快取資料或者頁面之間傳遞訊息。也都知道TempData是用Session來實現的,既然是用Session來實現的,那麼模式就是執行緒模式,這樣的Session是沒法用到分散式系統中的,那麼在多臺機器上部署,怎麼做到Session在多臺機器中共存,這就涉及到分散式儲存。那該如何實現TempData的分散式儲存?在講如何實現時,先給大家說說ASP.Net MVC 的管道機制,本人能力有限,說的不對的地方,還請大家能指出來,共同進步。

2、預備知識


2.1、MVC處理的流程講解

網上有很多講解ASP.Net 的管道機制的,都講解的很好,大家可以找找看,今天我來點不一樣的,通過Reflector,Debug進原始碼一步一步除錯給大家看,下面開始吧:

1)俗話說的好,工欲善其事必先利其器,下面我們在VS2012上裝Reflector

選擇"擴充套件和更新",在彈出來的對話方塊中安裝我們的利器

安裝完成之後會在VS上面出現如下的選單:

點選該選單,選擇下面的選項:

在彈出來的對話中勾選所有以system.web開頭的dll,生成PDB檔案,因為只有生成它,我們才能除錯原始碼,如下圖的勾選情況:

OK,裝好之後我們就開始探索的旅程了~~~~~~~~

2)窺探ASP.Net MVC請求處理流程

Part 1

這裡先附上一張一次請求 http://localhost:42132/Home/Index/1  處理響應的整體流程圖:

 看不明白的不要著急,下面會通過除錯的方式詳細介紹請求處理響應的流程,動動你的小手,下面開始劃重點了~~

我們上網時,在瀏覽器地址輸入網址:Http://www.cnblogs.com,按下回車,一張網頁就呈現在我們眼前。這究竟發生了什麼?對於一名優秀的Programmer來說,我想有必要一下熟悉瀏覽器--->伺服器請求的過程。

1)ASP.Net

ASP.NET是執行在公共語言執行時刻時(CLR)上的應用程式框架。他用來在伺服器端構建功能強大的web應用程式。當瀏覽器請求 ASP.NET 檔案時,IIS 會把該請求傳遞給伺服器上的 ASP.NET 引擎,ASP.NET 引擎會逐行地讀取該檔案,並執行檔案中的指令碼,最後,ASP.NET 檔案會以純 HTML 的形式返回瀏覽器。

客戶端瀏覽器和伺服器之間的請求響應是通過Socket進行通訊,基於HTTP協議,客戶端傳送一次HTTP請求,伺服器接收到請求,處理之後向瀏覽器迴應響應報文。那麼什麼是HTTP協議呢?

2)Http協議

當瀏覽器尋找到Web伺服器地址後,瀏覽器將幫助我們把對伺服器的請求轉換為一系列引數(訊息)發給Web伺服器,瀏覽器和Web伺服器的對話中,需要使用雙方都能理解語法規範進行通訊,這種程式之間進行通訊的語法規定,我們稱之為協議。瀏覽器與伺服器之間的協議是應用層協議,當前遵循的協議是HTTP/1.1。HTTP/1.1協議時Web開發的基礎,這是一個無狀態協議,客戶端瀏覽器和伺服器通過Socket通訊進行請求和響應完成一次會話。每次會話中,通訊雙方傳送的資料稱為訊息,分為兩種:請求訊息和響應訊息。

對於訊息而言,一般他有三部分組成,並且訊息的頭和訊息體之間用一個空行進行分隔:

下面用Fiddler我們可以清晰看到瀏覽器和伺服器之間的通訊內容:

注意:在請求頭和請求體之間是有一空行的,是Http協議規定的。

如果想更加詳細的瞭解Http協議的內容,可以參考下面的兩篇文章:

http://www.cnblogs.com/EricaMIN1987_IT/p/3837436.html

http://www.cnblogs.com/wxisme/p/6212797.html

瞭解了什麼是HTTP協議之後,我們在回到先前提出的那個問題,瀏覽器的請求怎樣到達伺服器?

 3)Http.sys和TCP.sys元件

我們知道要訪問一個網站,必須要其部署在相應伺服器軟體上(如IIS),於IIS相關的核心驅動程式有兩個:一個是TCP.sys和Http.sys,所謂的TCP,是用來定義在網路上資料傳輸方式的協議,它是一個位於OSI七層協議棧的傳輸層的協議。HTTP協議是一個定義在應用層的協議,它定義了資料互動的謂詞資料的格式等,但是傳輸層上是使用TCP協議進行資料包傳送。瞭解了以上內容有助於理解http.sys和TCP.sys之間的關係:TCP.sys位於Windows通訊的最底層,凡是使用TCP協議傳輸的HTTP協議資料包都會被tcp.sys完成組包後再交給http.sys進行處理。當請求的資料包包含一個HTTP請求時,就會有tcp.sys轉給http.sys進行處理,http.sys在核心態上處理完HTTP請求後,IIS就會把HTTP請求對應的HTTP上下文物件轉到對應的應用程式程序中,由對應的w3wp.exe程序對請求進行處理。由於IIS本身只能處理靜態頁面比如html、htm等,對於動態的頁面比如cshtml,IIS本身是無法處理的,那麼怎樣能讓IIS能夠支援ASP.Net動態也的處理呢?答案就是採用ISAPI。ISAPI可以理解為是IIS的一種擴充套件外掛,當IIS發現某種伺服器上的資源自給無法處理時,就會按照配置資訊把請求轉給對應的ISAPI的擴充套件來執行;IIS會等待ISAPI的執行結果,然後把結果傳給客戶的瀏覽器。

4)IIS伺服器擴充套件

ISAPI(伺服器應用程式設計介面),它為開發人員提供了強大的可程式設計能力,只要按照標準介面開發不同型別的Web應用程式的ISAPI擴充套件程式,就能實現對IIS功能上的擴充套件,從而使IIS可以處理不同型別的客戶端請求。IIS管理器提供了應用程式配置功能,可以對不同的客戶端請求配置不同的ISAPI擴充套件程式ISAPI擴充套件程式通常以DLL形式存在,可以被IIS載入並呼叫。有了基於ISAPI的擴充套件擴充套件程式,IIS伺服器就可以根據客戶端請求的資源副檔名,來決定應由哪個ISAPI擴充套件程式來處理客戶端請求,然後就可以將請求轉發給合適的ISAPI擴充套件程式。

5)IIS中處理程式對映

Part 2

1)整體把握ASP.Net MVC和ASP.Net WebForm處理流程的差異

 ASP.Net是一項動態網頁開發技術,在歷史發展的長河中WebForm曾一時成為了ASP.Net的代名詞,而ASP.Net MVC的出現讓這項技術更加喚發朝氣。但是,不管是ASP.Net WebForm還是ASP.Net MVC在請求處理機制上大部分都是相同的,只是在請求處理管道上的處理事件做了不同的操作。

2)ASP.Net MVC的管道機制

第一個進入ASP.Net管道的是:PipelineRuntime.ProcessRequestNotification方法

 當你在瀏覽器中輸入http://localhost:42132/Home/Index/1按回車之後,請求首先會到達PipelineRuntime.ProcessRequestNotification方法,如下圖所示:

 注意呼叫堆疊資訊,我們的請求到達ASP.Net管道時,首先會經過PipelineRuntime類中的ProcessRequestNotification方法,至於該方法裡面的引數暫時可以忽略,抄起你的小手,劃重點了,在該方法內部,又呼叫了ProcessRequestNotificationHelper方法,下面轉到該方法內部,如下圖所示:

在該方法內部,呼叫了InitializeRequestContext方法,主要用來初始化請求上下文,我們接著轉到該方的內部,如下圖所示:

 注意InitializeRequestContext方法內部的這段程式碼  context = new HttpContext(wr, false);     例項化HttpContext物件,接下來我們看看,在new HttpContext物件的時候都做了些神馬:

在該方法內部又呼叫了Init方法,進行Httprequest和HttpResponse物件進行分裝,如下圖所示:

好了,實線收回到  ProcessRequestNotificationHelper方法中,在該方法中回執行  HttpRuntime.ProcessRequestNotification(wr, httpContext);  ,如下圖所示:

在該方中,第一個引數和第二個引數,就是我們上面例項化的物件,轉到該方的內部,你會看到不一樣的世界,如下圖所示:

在該方法的內部又呼叫了  HttpRuntime.ProcessRequestNotificationPrivate 方法,在該方法的內部try中 EnsureFirstRequestInit 方法,確保網站第一次被訪問時,呼叫了Global檔案中了Application_Start方法,不信你看:

 全域性事件中例如Application_Start方法如何保證只執行一次?在_theApplicationFactory.EnsureAppStartCalled(context);方法中,判斷_appOnStartCalled標誌,如果是false則呼叫FireApplicationOnStart方法觸發Application_Start方法,然後更改_appOnStartCalled標誌。

注意了,重點來了,趕快抄起你的小手,劃重點了,通過HttpApplicationFactory.GetApplicationInstance方法來建立APPlication物件,其實這裡的application物件就是Global例項物件,有圖有真相。我們來詳細瞭解一下HttpApplicationFactory是怎麼來建立application物件的,下面我們轉到GetApplicationInstance方法內部,如下圖所示:

 在轉到GetNormalApplicationInstance方法內部,窺探一下application物件是如何生成的,如下圖所示:

通過檢視這段程式碼,它首先維護著一個HttpApplication池(_freeList,本質上就是一個Stack棧),然後判斷可用的HttpApplication例項的數量(_numFreeAppInstances)是否大於0?如果存在可用的,則從池中出棧,然後將可用數量減1。最後,再判斷可用的數量是否小於最低限制的數量,如果小於那麼則將最低限制的數量設定為目前可用的數量。那麼,如果目前HttpApplication池暫時沒有可用的例項呢?

程式碼 state = (HttpApplication) HttpRuntime.CreateNonPublicInstance(this._theApplicationType);    內部通過反射建立了application物件,注意了,重點來了,趕快抄起你的小手,劃重點了,在GetNormalApplicationInstance方法,內部application物件(也就是Global物件),呼叫了InitInternal方法,該方法的功能整體上是這樣的:建立系統配置檔案和使用者配置檔案中的HttpModule物件,如下圖所示:

HttpApplication.InitInternal方法的內部,又呼叫了 this.InitModules(),在該方法中,首先通過讀取Web.config配置檔案中關於HttpModule的資訊,然後將其傳遞給HttpModule的集合,如下圖所示:

 那在ASP.NET中已經預定了哪些HttpModule,我們通過 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config找到web.config檔案

 

然後,又呼叫了InitModulesCommon方法,遍歷上面這個_moduleCollection集合,分別對其每一個HttpModule執行其對應的Init方法。

 現在我們把視線收回到 HttpApplication.InitInternal()方法內部,在該方法內部又呼叫了this._stepManager.BuildSteps(this._resumeStepsWaitCallback); 它完成了19個請求處理管道事件的註冊工作。如下圖所示:

從上面的程式碼可知,ApplicationStepManager物件的BuildSteps方法被呼叫,完成HttpApplication 19個管道事件的註冊。這個方法很重要,它將建立各種HttpApplication.IExecutionStep儲存到一個數組列表 _execSteps 中:如上圖中 steps.CopyTo(this._execSteps)。這樣做的目的在於:便於在後面的BeginProcessRequest方法內部呼叫ResumeSteps方法依次執行這些物件的Execute()方法,完成各個事件的執行。打起精神,抄起你的小手,劃重點了,在完成HttpApplication 19個管道事件的註冊後,開始依次跑管道事件,在執行每個管道事件的時候,會觸發HttpModule中各個事件對應的執行方法,下面列出部分方法被觸發執行的情況,如下圖所示:

來來來,打起精神,抄起你的小手,重點來了!!!重點來了!!!重點來了!!!重要的事情說三遍!

看見沒,URLRoutingModule,它是一個實現了IHttpModule介面,重寫了Init方法,在該方法內部,第七個管道事件上沒註冊了 OnApplicationPostResolveRequestCache方法,如下圖所示:

 也就是說,我們的ASP.Net MVC 網站已經進入到第七個管道事件 PostResolveRequestCache ,我們的MVC就是通過這種方法來實現的。下面 我們轉到該方法內部,看看到底幹了些神馬,如下圖所示:

 在說明該方法時,我們先補充一些關於HttpModule和HttpHandler,首先附上一張管道事件的圖片,如下圖所示:

我們再來理解一下什麼是HttpModule和HttpHandler,他們有助我們在ASP.NET頁面處理過程的前後注入自定義的程式碼邏輯的原理。首先他們之間主要的差別在於:

(1)整體把握:

ASP.NET 請求處理過程是基於管道模型的,這個管道模型是由多個HttpModule和HttpHandler組成,ASP.NET 把http請求依次傳遞給管道中各個HttpModule,最終被HttpHandler處理,處理完成後,再次經過管道中的HTTP模組,把結果返回給客戶端。我們可以在每個HttpModule中都可以干預請求的處理過程。

 

注意:在http請求的處理過程中,只能呼叫一個HttpHandler,但可以呼叫多個HttpModule。 
當請求到達HttpModule的時候,系統還沒有對這個請求真正處理,但是我們可以在這個請求傳遞到處理中心(HttpHandler)之前附加一些其它資訊,或者截獲的這個請求並作一些額外的工作,也或者終止請求等。在HttpHandler處理完請求之後,我們可以再在相應的HttpModule中把請求處理的結果進行再次加工返回客戶端。

 (2)IHttpModule

比如我們的MVC中的URLRoutingModule,就是實現了IHttpModule介面,重寫了裡面的Init方法。

IHttpModule定義如下:

public interface IHttpModule
{
void Dispose();
void Init(HttpApplication context);
}

Init 方法:系統初始化的時候自動呼叫,這個方法允許HTTP模組向HttpApplication 物件中的事件註冊自己的事件處理程式。URLRoutingModule就是這樣實現的

 (3)IHandler

HttpHandler是HTTP請求的處理中心,真正地對客戶端請求的伺服器頁面做出編譯和執行,並將處理過後的資訊附加在HTTP請求資訊流中再次返回到HttpModule中。
    HttpHandler與HttpModule不同,一旦定義了自己的HttpHandler類,那麼它對系統的HttpHandler的關係將是“覆蓋”關係。
    IHttpHandler介面宣告
    public interface IHttpHandler
    {
        bool IsReusable { get; }
        public void ProcessRequest(HttpContext context); //請求處理函式
    }

(注:該部分參考來源:ivan.yu的.net空間)關於更詳細的介紹可以參考這位前輩的文章:http://www.cnblogs.com/yuanyuan/archive/2010/11/15/1877709.html,講解的非常詳細。

後面我會,結合HttpModule和HttpHandler講解幾個實戰的例子。

 好了,回到URLRoutingModule中Init方法在第七個管道事件上註冊的  OnApplicationPostResolveRequestCache方法,我們的MVC在第七個事件主要做的事情是建立一個MVCHandler存入到HttpContext物件的ReMapHandler屬性中,但是對於靜態檔案是不需要經過MVC處理的。下面我們來看看在該方法內部是如何實現的, RouteData routeData = this.RouteCollection.GetRouteData(context);通過該方法獲取到封裝的路由資訊的RouteData例項。也就是當請求到達UrlRoutingModule的時候,UrlRoutingModule會觸發註冊的事件方法,在該方法內部通過 GetRouteData方法 ,根據URL到路由表裡面查詢匹配URL規則的路由,若匹配,把請求交給IRouteHandler,即MVCRouteHandler。我們可以看下GetRouteData的原始碼,如下圖所示:

注意了,重點來了,抄起你的小手,開始劃重點,在GetRouteData方法紅色框中標註的程式碼,會返回RouteData物件,那我們看看,RouteData物件中到底有些神馬,如下圖所示:

注意了這裡把MVCRouteHandler物件賦值給了RouteHandler了,最終返回,把值賦值給routeData變數。接著我們把視線收回到第七個管道事件註冊的方法中,

接著,會判斷一下routeData是否為NUll,routeData是不為null的,所以接下來,通過routeData.RouteHandler拿到了MVCRouteHandler物件,重點來啦,趕快抄起小手!!!接下來繼續執行,當執行到IHttpHandler  HttpHandler=routeHandler.GetHttpHandler(requestContext)時,我們的MVCHandler就誕生了,最後把建立的MVCHandler物件,存入到了RemapHandler不信,如下圖所示:

不信,如下圖所示:

那我們的MVCHandler建立好了,之後該怎麼執行呢?很簡單,繼續執行下面的管道事件唄,接著到第八個管道事件了,在第八個管道事件,先檢查HttpContext裡面的remapHandler,發現不為空,直接略過執行後面的是那件,在第十一和管道事件和第十二個管道事件之間呼叫MVCHandler的BeginProcessRequest方法,不信如下圖所示:

在該方法內部,會執行ProcessRequestInit方法,進行處理請求的初始化工作,如下圖所示:

看到沒,我們的控制器的名字:Home,注意了,重點來啦!!!重點來啦!!!重點來啦!!!重要的事情說三遍!!!

this.ControllerBuilder.GetControllerFactory()方法拿到Controller Factory物件,然後,呼叫CreateController方法,拿到對應的controller物件。下面我們看看能不是如何實現了,不要忘了這篇文章講的是如何實現跨越Session的分散式的TempData。CreateController方法中有兩個引數,一個是RequestContext物件,通過他我們能拿到請求的先關資訊,第二個引數是一個string型別的controller名稱,它的值來源於URL,如下圖所示:

首先要注意,我們的Controller Factory就是DefaultControllerFactory物件(作用:為請求提供服務的controller例項),在該方法中,通過反射去建立對應的controller物件。在該方中有兩個特別重要的方法,GetControllerTypeGetControllerInstance方法。GetControllerType方法方法,返回Type型別,為請求匹配對應的controller類。GetControllerInstance方法返回是IController型別,作用是根據指定的controller型別建立型別的例項。重寫GetControllerInstance方法可以實現對建立controller例項的過程進行控制,最常見的就是依賴注入,這裡我們暫且不講。那麼GetControllerInstance又是如何來獲取例項呢? 下面我們轉到GetControllerInstance方法內部,如下圖所示:

 看到沒,它是通過ControllerActivator來拿到controller例項的,轉到內部,如下圖所示:

 看到沒,這段程式碼是不是很熟悉,是不是有點像我們使用autofac的影子。好了我們總結一下Controller物件的建立過程:首先當我們的DefaultControllerFactory類接收到一個controller例項的請求時,在DefaultControllerFactory類內部通過GetControllerType方法來獲取controller的型別,然後把這個型別傳遞給GetControllerInstance方法以獲取controller例項,所以在GetControllerInstance方法中就需要有某個東西來建立controller例項,這個建立的過程就是controller被啟用的過程。那我們的controller物件建立完畢,接下來就是要呼叫Controller裡面的Execute方法,執行對應的Action方法。接著我們把視線收回到MVCHandler中的BeginProcessRequest方法內部,在該方法內部又執行了 asyncController.BeginExecute(this.RequestContext, asyncCallback, asyncState);方法,為什麼會執行Controller裡面的ExecuteCore方法呢??首先我們補充一點關於IController的知識:

(1)我們新增的Controller都是一個繼承自抽象類System.Web.MVC.Controller,該類又繼承自ControllerBase,ControllerBase又實現了IController介面,在該介面中只有一個方法,就是Execute方法,當請求送到了一個實現了IController介面的Controller類時,Execute方法就會被呼叫。如下所示:

public interface IController
{
  void Execute(RequestContext requestContext);
}
ControllerBase實現了Execute方法,如下所示:

注意到沒有,this.ExecuteCore()和 Protected abstract void ExecuteCore(),我們再看一下Controller的實現,你就會明白下面的執行流程了,如下圖所示:

看到沒,我們的Controller類實現了ControllerBase中的ExecuteCore這個抽象方法。注意下1和3是在執行Action方法前和後執行的,後面會講解到底是什麼,繼續看我們MVC執行的流程

 注意:Controller中的一切對請求的處理都是從Execute方法開始的!!!,下面我們轉到BeginExecute方法的內部,如下圖所示:

 

來來來,抄起小手,劃重點了,注意到沒有,return後面的AsyncRequestWrapper.Begin方法了嗎?在第三個引數中有這樣一句程式碼:this.BeginExecuteCore,這裡的this值的就是Controller,F11自然會進入到該方法,如下圖所示:

 首先要明白,當Controller Factory建立好了一個類的例項後,MVC框架則需要一種方式來呼叫這個例項的Action方法,如果建立了controller是繼承Controller抽象類的話,那麼則是有Action Invoker來完成呼叫action方法的任務,MVC預設使用的是ControllerActionInvoker類。然後我們看看程式碼的具體實現:首先,通過路由資料獲取Action名稱,例如請求URL為:http://xxx.com/Home/Index,這裡獲取的Action名稱即為Index。然後,通過IActionInvoker invoker = this.ActionInvoker;拿到Action的啟用器。那麼問題來了,這個ActionInvoker又是啥東東?我們先看看這個介面的定義程式碼如下:

public interface IActionInvoker
{
   bool InvokeAction(ControllerContext controllerContext, string actionName);
}
我們發現原來是一個叫做ControllerActionInvoker的類實現了IActionInvoker介面,ControllerActionInvoker類如下圖所示:

接著執行: asyncInvoker.BeginInvokeAction(this.ControllerContext, actionName, asyncCallback, asyncState);,轉到內部,如下圖所示:

 

 在該方法的內部,主要是獲取Controller與Action的描述資訊和過濾器資訊。獲取引數資訊後並開始真正執行Action,在action方法執行完之後,開始View的呈現,

我們知道ActionResult是一個抽象類,那麼這個InvokeActionResult應該是由其子類來實現。於是,我們找到ViewResult,但是其並未直接繼承於ActionResult,再找到其父類ViewResultBase,它則繼承了ActionResult。於是,我們來檢視它的ExecuteResult方法,如下圖所示:

 在該方法內部,找到檢視引擎,找到檢視,執行檢視生成HTML,下面我們一步一步來看看,如何執行的。先檢查是否傳入指定的檢視名稱,如果沒有傳入,則取Action方法的名字作為待會要讀取的檢視名字,程式碼如下:this.ViewName=context.RouteData.GetRequiredString("action");接著找到對應的檢視引擎,程式碼如下result=this.FindView(context)。在FindView方法內部,迴圈檢視引擎集合,看看哪個檢視引擎可以找到對應的檢視,就返回哪個檢視引擎的結果,此結果中就包含檢視介面物件,找到了RazorViewEngine物件,呼叫檢視引擎的FindView方法,但這個方法在RazorViewEngine類中沒有,而是在父類的父類中定義(繼承關係:RazorViewEngine-->BuildManagerViewEngine-->VirtualPathProviderViewEngine),獲取控制器名稱、檢視的路徑,同時還獲得了母版頁的路徑,最終返回ViewEngineResult,然後獲取返回的ViewEngineResult裡的View物件,然後呼叫它的Render方法來生成HTML程式碼並寫入到Response中,程式碼如下:TextWriter  writer=context。HttpContext.Response.Output;ViewContext viewContext=new  ViewContext(context,view,ViewData,TempData,writer); View.Render(viewContext,writer);最後生成HTML。大家可能通過文字來不是好理解,下面我在附上一張我自己畫的流程圖,是根據我自己除錯程式碼理解的,如下圖所示:(想要下面流程圖的可以提下,到時候發給你)

到這裡我們ASP.Net MVC 的一次請求處理響應的流程就結束了,好了,不是很理解的話,不要緊,下去可以通過程式碼除錯的方法,自己好好除錯除錯,慢慢理解。來來來,把思路整理一下,回到我的TempData。通過上面流程的講解,大家知道在執行action方法之前和之後都會分別執行PossiblyLoadTempData()和PossiblySaveTempData(),如下圖所示:

從中可以看到在請求開始時就去取TempData,在Action呼叫結束後去儲存TempData。為什麼要再去儲存一遍呢?

2.2、TempData原始碼的講解

TempData是什麼

(1)可以儲存一次,只能讀取一次,如果第二次讀取,將不會有tempdata資料,這樣就起到了臨時變數的作用

(2) 是一個string object的字典。
(3) action執行前後,都會對temp進行操作

(4)一般用於兩個請求之間臨時快取資料或者頁面之間傳遞訊息

TempData原始碼分休

public TempDataDictionary TempData
{
  get
  {
   if ((this.ControllerContext != null) && this.ControllerContext.IsChildAction)
       {
    return this.ControllerContext.ParentActionViewContext.TempData;
        }


   if (this._tempDataDictionary == null)
    {
    this._tempDataDictionary = new TempDataDictionary();
     }
  return this._tempDataDictionary;
}
set
{
this._tempDataDictionary = value;
}
}

step1:先來看看上面提到了兩個方法內部是如何實現的

他們內部又呼叫了Load和Save方法,轉到定義,如下圖所示:

這兩個方法內部又通過,TempDataprovider分別呼叫了LoadTempDataSaveTempData方法,再分別轉到這兩個方法內部,如下所示:

public interface ITempDataProvider
{
IDictionary<string, object> LoadTempData(ControllerContext controllerContext);
void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values);
}

注意:它是一個介面。裡面是這兩個方法,肯定有子類實現該介面中的兩個方法,通過除錯原始碼,你就會知道上面Load和Save方法中最後一個引數,tempDataProvider就是SessionStateTempDataProvider,不信我們來看下原始碼:

// Generated by .NET Reflector from C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Web.Mvc\v4.0_4.0.0.0__31bf3856ad364e35\System.Web.Mvc.dll
namespace System.Web.Mvc
{
    using System;
    using System.Collections.Generic;
    using System.Web;
    using System.Web.Mvc.Properties;
    
    public class SessionStateTempDataProvider : ITempDataProvider
    {
        internal const string TempDataSessionStateKey = "__ControllerTempData";
        
        public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
        {
            HttpSessionStateBase session = controllerContext.HttpContext.Session;
            if (session != null)
            {
                Dictionary<string, object> dictionary = session["__ControllerTempData"] as Dictionary<string, object>;
                if (dictionary != null)
                {
                    session.Remove("__ControllerTempData");
                    return dictionary;
                }
            }
            return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
        }
        
        public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
        {
            if (controllerContext == null)
            {
                throw new ArgumentNullException("controllerContext");
            }
            HttpSessionStateBase session = controllerContext.HttpContext.Session;
            bool flag = (values != null) && (values.Count > 0);
            if (session == null)
            {
                if (flag)
                {
                    throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
                }
            }
            else if (flag)
            {
                session["__ControllerTempData"] = values;
            }
            else if (session["__ControllerTempData"] != null)
            {
                session.Remove("__ControllerTempData");
            }
        }
    }
}

看到沒,我們的SessionStateTempDataProvider類實現了ITempDataProvider介面,重寫了Load和Save方法。

從圖中可知,SessionStatesTempDataProvider暴露了LoadTempData和SaveTempData兩個方法。

其中從SaveTempData中session["__ControllerTempData"] = (object) values;可以看出,TempData是儲存在Session中的。

其中LoadTempData方法中session.Remove("__ControllerTempData");就說明了從session中獲取tempdata後,對應的tempdata就從session中清空了

原來每次取完TempData後都會從Session中清空,如果TempData未曾使用,那當然要重新儲存到Session中啊。這就回答了為什麼要再去儲存一遍的問題。

那問題來了,我們要實現分散式的TempData,在MVC哪個地方注入呢?我們再來回顧一下MVC的管道和action方法執行前後發現:PossiblyLoadTempData和PossiblySaveTempData是在呼叫Controller中對應的action方法時執行的,並且Controller中有 TempDataProvider屬性,程式碼如下:

public ITempDataProvider TempDataProvider
        {
            get
            {
                if (this._tempDataProvider == null)
                {
                    this._tempDataProvider = this.CreateTempDataProvider();
                }
                return this._tempDataProvider;
            }
            set
            {
                this._tempDataProvider = value;
            }
        }

所以注入點我們就找到,在建立Controller Factory中建立Controller例項的時候,把我們自定義的DataProvider類,賦值給TempDataProvider就可以了,下面我們來實現一把分散式的tempData

3、實現分散式的TempData

準備工作:首先我們新建一個MVC專案,新建一個資料夾Infrastructure資料夾,在這個檔案下新增一個類:繼承自DefaultControllerFactory的MyControllerFactory類即我們自定義的Controller Factory,程式碼如下:

 1 public class MyControllerFactory:DefaultControllerFactory
 2     {
 3         public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
 4         {
 5             var iController= base.CreateController(requestContext, controllerName);
 6 
 7             var controller = iController as Controller;
 8             controller.TempDataProvider = new CrossSessionTempData2();
 9 
10 
11             return iController;
12         }
13     }

3.1、把TempData的值存入到cache中

 1 namespace System.Web.Mvc
 2 {
 3     using System;
 4     using System.Collections.Generic;
 5     using System.Web;
 6     using System.Web.Mvc.Properties;
 7     
 8     public class SessionStateTempDataProvider : ITempDataProvider
 9     {
10         internal const string TempDataSessionStateKey = "__ControllerTempData";
11         
12         public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
13         {
14             var cache = controllerContext.HttpContext.Cache;
15             if (cache != null)
16             {
17                 Dictionary<string, object> dictionary =cache["__ControllerTempData"] as Dictionary<string, object>;
18                 if (dictionary != null)
19                 {
20                     cache .Remove("__ControllerTempData");
21                     return dictionary;
22                 }
23             }
24             return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
25         }
26         
27         public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
28         {
29             if (controllerContext == null)
30             {
31                 throw new ArgumentNullException("controllerContext");
32             }
33             var cache = controllerContext.HttpContext.Cache;
34             bool flag = (values != null) && (values.Count > 0);
35             if (cache == null)
36             {
37                 if (flag)
38                 {
39                     throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled);
40                 }
41             }
42             else if (flag)
43             {
44                 cache ["__ControllerTempData"] = values;
45             }
46             else if (cache ["__ControllerTempData"] != null)
47             {
48                 cache .Remove("__ControllerTempData");
49             }
50         }
51     }
52 }

TempData的值存入到cache中之檔案依賴

接著我們需要自定義一個實現了ITempDataProvider介面的DataProvider類,程式碼如下:(2017年6月20日18:13:07 程式碼修改)

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Web;
 5 using System.Web.Caching;
 6 using System.Web.Mvc;
 7 
 8 namespace CrossSessionTempData.Infrastructure
 9 {
10     public class
            
           

相關推薦

窺探ASP.Net MVC底層原理 實現跨越Session分散式TempData

1、問題的引出 我相信大家在專案中都使用過TempData,TempData是一個字典集合,一般用於兩個請求之間臨時快取資料或者頁面之間傳遞訊息。也都知道TempData是用Session來實現的,既然是用Session來實現的,那麼模式就是執行緒模式,這樣的Session是沒法用到分散式系統中的,那麼

ASP.NET MVC底層原理與框架

前言 鄙人有一毛病,喜歡鑽研原理性的東西,感覺只知道怎麼用還不太夠,更想知道如何實現的以及為什麼會這樣。 暑假的時候做積分系統是第一次接觸MVC,感覺MVC就是一個框架,分為Module ,view和controller,瀏覽器傳過來的資訊先到達contro

ASP.NET MVC計劃任務實現方法(定時執行某個功能)

AR tar 註意 eve 文件 ebr send npr 如何實現 系統中定時執行某個任務是比較常用的功能,如一個部門定期向上級部門上報數據是一個典型的例子,下面就簡單說說在.net mvc中如何實現定時執行某個功能的方法。 1、首先修改Glocal.asax文件,在A

ASP.NET MVC中如何實現頁面跳轉

pub ring 項目 再見 name ati 方法 技術 mod 1,最簡單的方式:超鏈接 以下分別是連接到HomeController控制器下的SharpL動作方法,以及百度首頁。代碼如下: 1 <a href="Home\SharpL">打開S

ASP.NET MVC 使用Unity實現Ioc

his mod nbsp clas 輕量 asp.net 列表 odin 安裝 為什麽有這篇文章   最近在學ASP.NET MVC項目中使用Ioc,選用了Unity作為依賴註入的容器組件,在網上找了相關的文章簡單實現了依賴註入,但想用文件配置的方式進行容器註入的註冊,發現

ASP.NET MVC項目實現BasePage基類用作ASPX.CS網頁繼承

bsp targe 解決方法 網頁 接下來 項目開發 空間 所有 會有 在ASP.NET MVC項目開發,還是需要創建一些Web Page來實現一些功能,如呈現報表等... 但是一旦項目的.ASPX網頁太多了,其中的程序代碼也會有代碼冗余,出現這些情況,我們得需要對這些代

Asp.net MVC中如何實現依賴注入(DI)(二)

昨天說了一下Castle與Autofac如何在MVC中的使用,今天再來簡單說一下Spring.Net框架在MVC中如何依賴注入的。 官網:http://www.springframework.net/ 專案結構圖:   首先,我們要在專案中新增Spring.Net的類庫引用,我們可以在N

Asp.NET MVC 使用 SignalR 實現推送功能二(Hubs 線上聊天室 獲取儲存使用者資訊)

簡單介紹 在上一篇中,我們只是介紹了簡單的訊息推送,今天我們來修改一下,實現儲存訊息,歷史訊息和使用者線上 使用者登入註冊資訊 當用戶登入之後,我們註冊一下使用者的資訊,我們在ChatHub中 新建一個方法 Register(使用者帳號,使用者密碼) 前臺js呼叫這個方法實現使用者註冊 

Asp.NET MVC 使用 SignalR 實現推送功能一(Hubs 線上聊天室)

簡介       ASP .NET SignalR 是一個ASP .NET 下的類庫,可以在ASP .NET 的Web專案中實現實時通訊。什麼是實時通訊的Web呢?就是讓客戶端(Web頁面)和伺服器端可以互相通知訊息及呼叫方法,當然這是實時操作的。 WebSockets是HTML5提供的新的API

理解ASP.NET MVC底層執行機制

ASP.NET MVC有三大元件(即模型、檢視、控制器)。所謂模型,就是MVC需要提供的資料來源,負責資料的訪問和維護。所謂檢視,就是用於顯示模型中資料的使用者介面。所謂控制器,就是用來處理使用者的輸入,負責改變模型的狀態並選擇適當的檢視來顯示模型的資料。以下是我繪製的MV

asp.net mvc 用Redis實現分散式叢集共享Session

1、這兩天研究Redis搞分散式session問題,網上找的資料都是用ServiceStack.Redis來實現的,但是在做效能測試的時候發現最新的v4版本有限制每小時候最多請求6000次,因為官網開始商業化要收費了,好坑爹的說,還好我前期弄了個性能測試列子,不然上線以後

ASP.NET MVC判斷基於Cookie的Session過期

new pac direct net ttr container att thum tpc 當我們第一次請求訪問時,可以看到Response的Set-Cookie裏添加了ASP.NET_SessionId的值,以後再訪問時可以看到Resquest裏的Cookie已經包含這個

asp.net mvc 註冊中的郵箱激活功能實現

名稱 work 點擊 rom urn 內容 string 電子郵件 amp 基本流程圖 註冊頁面就不再寫出,現在將發送郵件的代碼粘貼出來 public ActionResult SendEmial() { int

ASP.NET MVC & WebApi 中實現Cors來讓Ajax可以跨域訪問 (轉載)

詳細 簡介 part bsp bob 打印 不能 res user 什麽是Cors? CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。它允許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了

asp.net Mvc實現文件下載

result 絕對路徑 路徑 ati 文件絕對路徑 filepath href encode string <a href=‘~/Download?filePath=路徑" > 下載 </a> public ActionResult Down

asp.net MVC 使用PagedList.MVC實現分頁

nbsp 超出 inf length names .cn www 名稱 ger 在上一篇的EF之DB First中,存在以下的兩個問題: 1. 添加/編輯頁面顯示的是屬性名稱,而非自定義的名稱(如:姓名、專業...) 2. 添加/編輯時沒有加入驗證 3. 數據展示使用分頁

ASP.NET MVC+Bootstrap 實現短信驗證

add this val data charset tracking load fadein 發送 短信驗證大家都已經非常熟悉了,基本上每天都在接觸手機短信的驗證碼,比方某寶,某東購物。站點註冊,網上銀行等等,都要驗證我們的手機號碼真實性。這樣做有什麽優

ASP.NET MVC下的異步Action的定義和執行原理

urn des {0} 不同 exce .class 遠程 是否 了解 Visual Studio提供的Controller創建向導默認為我們創建一個繼承自抽象類Controller的Controller類型,這樣的Controller只能定義同步Action方法。如果我們

ASP.NET MVC 簡單的分頁思想與實現

www 新增 圖文 tom 最簡 基本 描述 clas asp.net , 作為一個程序猿,數據分頁是每個人都會遇到的問題。解決方案更是琳瑯滿目,花樣百出。但基本的思想都是差不多的。   下面給大家分享一個簡單的分頁器,讓初學者了解一下最簡單的分頁思想,以及在ASP.NET

[ASP.NET MVC 小牛之路]05 - 使用 Ninject實現依賴註入

構造註入 ted ets pathinfo ref 最重要的 map ice prot 在[ASP.NET MVC 小牛之路]系列上一篇文章(依賴註入(DI)和Ninject)的末尾提到了在ASP.NET MVC中使用Ninject要做的兩件事情,續這篇文章之後,本文將用一