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

 

一、問題的引出

我相信你們在項目中都使用過TempData,TempData是一個字典集合,通常用於兩個請求之間臨時緩存數據或者頁面之間傳遞消息。也都知道TempData是用Session來實現的,既然是用Session來實現的,那麼模式就是線程模式,這樣的Session是無法用到分佈式系統中的,那麼在多臺機器上部署,怎麼作到Session在多臺機器中共存,這就涉及到分佈式存儲。那該如何實現TempData的分佈式存儲?在講如何實現時,先給你們說說ASP.Net MVC 的管道機制,本人能力有限,說的不對的地方,還請你們能指出來,共同進步。html

二、預備知識


2.一、MVC處理的流程講解web

網上有不少講解ASP.Net 的管道機制的,都講解的很好,你們能夠找找看,今天我來點不同的,經過Reflector,Debug進源碼一步一步調試給你們看,下面開始吧:redis

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.二、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

三、實現分佈式的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.一、把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 CrossSessionTempData2 : ITempDataProvider
11     {
12 
13         internal const string TempDataSessionStateKey = "__ControllerTempData";
14 
15         public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
16         {
17             var cache = controllerContext.HttpContext.Cache;
18 
19             if (cache != null)
20             {
21                 Dictionary<string, object> dictionary = cache[TempDataSessionStateKey] as Dictionary<string, object>;
22                 if (dictionary != null)
23                 {
24                     cache.Remove(TempDataSessionStateKey);
25                     return dictionary;
26                 }
27             }
28             return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
29         }
30 
31         /// <summary>Saves the specified values in the temporary data dictionary by using the specified controller context.</summary>
32         /// <param name="controllerContext">The controller context.</param>
33         /// <param name="values">The values.</param>
34         /// <exception cref="T:System.InvalidOperationException">An error occurred the session context was being retrieved.</exception>
35         public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
36         {
37             if (controllerContext == null)
38             {
39                 throw new ArgumentNullException("controllerContext");
40             }
41             var cache = controllerContext.HttpContext.Cache;
42             bool flag = values != null && values.Count > 0;
43             if (cache == null)
44             {
45                 if (flag)
46                 {
47                     throw new InvalidOperationException("");
48                 }
49             }
50             else
51             {
52                 CacheDependency dp = new CacheDependency(controllerContext.HttpContext.Server.MapPath("/Data/123.txt"));
53                 if (flag)
54                 {
55                     
56 
57                     
58                     cache.Insert(TempDataSessionStateKey, values, dp);
59 
60                     return;
61                 }
62 63                 if (cache[TempDataSessionStateKey] != null)
64                 {
65                     cache.Remove(TempDataSessionStateKey);
66                 }
67             }
68         }
69     }
70 }

 

添加一個controller,代碼以下:

 

咱們在Index中設置TempData的值,而後再List中讀取。按說咱們只有概念文件依賴是存在緩存中發TempData的值纔會消失,下面咱們運行一把,看看運行結果:

先訪問:http://localhost:42913/Default/Index

在執行Index action方法以前會執行LoadTempData方法,以下圖所示:

接着,設置TempData的值,以下圖所示:

接着執行Save方法,以下圖所示:

看到沒,把TempData的值存入到Cache中了,接着我方訪問如下http://localhost:42913/Default/List,TempData的值就會顯示出來:

首先也會執行LoadTempData方法

再執行List裏面的代碼,在執行SaveTempData方法, 返回視圖:

無論怎麼刷新,值依然存在,可是隻要咱們修改依賴文件1.txt值立馬就消失了。

保存,再次刷新頁面,數據就丟失了。

3.二、把TempData的值存入到NoSQL Memcached中實現真正的分佈式

關於Memcached的安裝和操做請參考個人這篇博客:

ASP.Net MVC4+Memcached+CodeFirst實現分佈式緩存

 MemcacheHelper:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Web;
 5 using Memcached.ClientLibrary;
 6 
 7 namespace WebDemo.Models
 8 {
 9     public static class MemcacheHelper
10     {
11         private static MemcachedClient mc;
12 
13         static MemcacheHelper()
14         {
15             //經過客戶端來進行memcached的集羣配置,在插入數據的時候,使用一致性哈希算法,將對應的value值存入Memcached
16             String[] serverlist = { "127.0.0.1:11211" };
17 
18             // 初始化Memcached的服務池
19             SockIOPool pool = SockIOPool.GetInstance("test");
20             //設置服務器列表
21             pool.SetServers(serverlist);
22             //各服務器之間負載均衡的設置比例
23             pool.SetWeights(new int[] { 1 });
24             pool.Initialize();
25             //建立一個Memcached的客戶端對象
26             mc = new MemcachedClient();
27             mc.PoolName = "test";
28             //是否啓用壓縮數據:若是啓用了壓縮,數據壓縮長於門檻的數據將被儲存在壓縮的形式
29             mc.EnableCompression = false;
30             
31         }
32         /// <summary>
33         /// 插入值
34         /// </summary>
35         /// <param name="key"></param>
36         /// <param name="value"></param>
37         /// <param name="expiry">過時時間</param>
38         /// <returns></returns>
39         public static bool Set(string key, object value,DateTime expiry){
40             return mc.Set(key, value, expiry);
41         }
42         /// <summary>
43         /// 獲取值
44         /// </summary>
45         /// <param name="key"></param>
46         /// <returns></returns>
47         public static object Get(string key)
48         {
49             return mc.Get(key);
50         }
51     }
52 }
View Code

 引用對應的dll:

自定義的咱們的DataProvider:

 1  public class CrossSessionTempData2 : ITempDataProvider
 2     {
 3 
 4         internal const string TempDataSessionStateKey = "__ControllerTempData";
 5 
 6         public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext)
 7         {
 8           
 9             Dictionary<string, object> dictionary = MemCaheHelper.Get(TempDataSessionStateKey) as Dictionary<string, object>;
10             if (dictionary != null)
11             {
12                 MemCaheHelper.Set(TempDataSessionStateKey, dictionary, DateTime.MinValue);
13                 return dictionary;
14             }
15             return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
16         }
17 
18         public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values)
19         {
20             if (controllerContext == null)
21             {
22                 throw new ArgumentNullException("controllerContext");
23             }
24             
25             bool flag = values != null && values.Count > 0;
26             if (flag)
27             {
28                
29                 MemCaheHelper.Set(TempDataSessionStateKey, values,DateTime.Now.AddMinutes(1));
30                 return;
31             }
32 
33             if (MemCaheHelper.Get(TempDataSessionStateKey) != null)
34             {
35                 MemCaheHelper.Set(TempDataSessionStateKey,values,DateTime.MinValue);
36             }
37           
38 
39         }
40     }

運行效果完美!!!!至此,咱們的分佈式TempData的功能已經實現。後面我會的代碼提供給你們。其實咱們也能夠把值存入到redis中,原理和MemCached差很少,本身能夠嘗試一下。

四、總結:

這篇文章花了很長時間,但願對你有幫助,若是你們覺的還能夠的話,幫忙點下推薦。若是對TempData仍是不太瞭解,能夠參考這位園友的文章TempData知多少

附件下載:

分佈式TempData代碼

MemCached

Reflector註冊機(最好安裝8.5版本的)

流程圖

參考文章:

木碗城主:http://www.cnblogs.com/OceanEyes/archive/2012/08/13/aspnetEssential-1.html

Edison Chou:http://www.cnblogs.com/edisonchou/p/3855969.html

MIN飛翔:http://www.cnblogs.com/EricaMIN1987_IT/p/3837436.html

Liam Wang:http://www.cnblogs.com/willick/p/3331521.html

一線碼農:http://www.cnblogs.com/huangxincheng/p/5663725.html

做者:郭崢

出處:http://www.cnblogs.com/runningsmallguo/

本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接。

相關文章
相關標籤/搜索