asp.net mvc 之旅 —— 第五站 從源碼中分析asp.net mvc 中的TempData

来源:http://www.cnblogs.com/huangxincheng/archive/2016/07/12/5663725.html
-Advertisement-
Play Games

在mvc的controller中,我們知道有很多的臨時變數存放數據,比如說viewData,viewBag,還有一個比較特殊的tempData,關於前兩個或許大家都明白, 基本上是一個東西,就是各自的編程寫法不一樣,最終都會放到viewContext中,然後送到WebPage中,如果你要證明的話,可 ...


  在mvc的controller中,我們知道有很多的臨時變數存放數據,比如說viewData,viewBag,還有一個比較特殊的tempData,關於前兩個或許大家都明白,

基本上是一個東西,就是各自的編程寫法不一樣,最終都會放到viewContext中,然後送到WebPage中,如果你要證明的話,可以看下下麵的代碼。

        /// <summary>Gets the dynamic view data dictionary.</summary>
        /// <returns>The dynamic view data dictionary.</returns>
        [Dynamic]
        public dynamic ViewBag
        {
            [return: Dynamic]
            get
            {
                if (this._dynamicViewDataDictionary == null)
                {
                    this._dynamicViewDataDictionary = new DynamicViewDataDictionary(() => this.ViewData);
                }
                return this._dynamicViewDataDictionary;
            }
        }

        /// <summary>Gets or sets the dictionary for view data.</summary>
        /// <returns>The dictionary for the view data.</returns>
        public ViewDataDictionary ViewData
        {
            get
            {
                if (this._viewDataDictionary == null)
                {
                    this._viewDataDictionary = new ViewDataDictionary();
                }
                return this._viewDataDictionary;
            }
            set
            {
                this._viewDataDictionary = value;
            }
        }

從上面的代碼中可以看到,其實ViewBag就是獲取ViewData的數據,對不對。。。

 

一:TempData

    至於這個東西怎麼用,大家貌似都記得是可訪問一次後即刻消失,好像貌似也就這樣了,當然不知道有沒有人對tempdata的底層代碼進行研究呢???

看一下它的底層到底是怎麼來實現的。

 

1. TempData源代碼

    首先我們看一下TempData的類型是TempDataDictionary,可以看到這個類型肯定是實現了IDictionary介面的自定義字典,

        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;
            }
        }

從上面代碼可以看到,tempdate預設是new了一個TempDataDictionary類,這個類中很好玩的地方在於這裡有一個load方法,這個load方法就是獲取真

正的provider,比如下麵這樣:

        /// <summary>Loads the specified controller context by using the specified data provider.</summary>
        /// <param name="controllerContext">The controller context.</param>
        /// <param name="tempDataProvider">The temporary data provider.</param>
        public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
        {
            IDictionary<string, object> dictionary = tempDataProvider.LoadTempData(controllerContext);
            this._data = ((dictionary != null) ? new Dictionary<string, object>(dictionary, StringComparer.OrdinalIgnoreCase) : new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase));
            this._initialKeys = new HashSet<string>(this._data.Keys, StringComparer.OrdinalIgnoreCase);
            this._retainedKeys.Clear();
        }

這個load方法就是非常重要的,這裡的參數ITempDataProvider就是我們在BeginExecute方法賦值的,繼續往下看,不要著急哦。。。

 

2. BeginExecute

   我們知道,mvc框架其實是截獲了mvcroutehandler來進行截獲url的請求,繼而將後續的處理就由mvc框架來接管,最終會執行到Controller類下麵的

BeginExecute,如果你不信,我可以開心加愉快的給你上代碼,比如下麵這樣:

        protected virtual IAsyncResult BeginExecute(RequestContext requestContext, AsyncCallback callback, object state)
        {
            Action action2 = null;
            if (this.DisableAsyncSupport)
            {
                if (action2 == null)
                {
                    action2 = delegate {
                        this.Execute(requestContext);
                    };
                }
                Action action = action2;
                return AsyncResultWrapper.BeginSynchronous(callback, state, action, _executeTag);
            }
            if (requestContext == null)
            {
                throw new ArgumentNullException("requestContext");
            }
            base.VerifyExecuteCalledOnce();
            this.Initialize(requestContext);
            BeginInvokeDelegate<Controller> beginDelegate = (asyncCallback, callbackState, controller) => controller.BeginExecuteCore(asyncCallback, callbackState);
            EndInvokeVoidDelegate<Controller> endDelegate = delegate (IAsyncResult asyncResult, Controller controller) {
                controller.EndExecuteCore(asyncResult);
            };
            return AsyncResultWrapper.Begin<Controller>(callback, state, beginDelegate, endDelegate, this, _executeTag, -1, null);
        }

上面這段代碼中,你一定要看清楚上面標紅的地方,這裡我們看到了,其實這裡是一個非同步的beginxxx,endxxx的操作,問題就是在這裡,首先我們從

beginInvoke說起。

 

<1> beginDelegate

       這個非同步操作中,我們可以看到,其實執行的是一個controller.BeginExecuteCore(asyncCallback, callbackState) 方法,對吧,然後我們可以

感興趣的看一下這個方法幹了什麼?

        protected virtual IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
        {
            IAsyncResult result;
            this.PossiblyLoadTempData();
            try
            {
                Action action2 = null;
                string actionName = GetActionName(this.RouteData);
                IActionInvoker invoker = this.ActionInvoker;
                IAsyncActionInvoker invoker = invoker as IAsyncActionInvoker;
                if (invoker != null)
                {
                    BeginInvokeDelegate<ExecuteCoreState> beginDelegate = (asyncCallback, asyncState, innerState) => innerState.AsyncInvoker.BeginInvokeAction(innerState.Controller.ControllerContext, innerState.ActionName, asyncCallback, asyncState);
                    EndInvokeVoidDelegate<ExecuteCoreState> endDelegate = delegate (IAsyncResult asyncResult, ExecuteCoreState innerState) {
                        if (!innerState.AsyncInvoker.EndInvokeAction(asyncResult))
                        {
                            innerState.Controller.HandleUnknownAction(innerState.ActionName);
                        }
                    };
                    ExecuteCoreState invokeState = new ExecuteCoreState {
                        Controller = this,
                        AsyncInvoker = invoker,
                        ActionName = actionName
                    };
                    return AsyncResultWrapper.Begin<ExecuteCoreState>(callback, state, beginDelegate, endDelegate, invokeState, _executeCoreTag, -1, null);
                }
                if (action2 == null)
                {
                    action2 = delegate {
                        if (!invoker.InvokeAction(this.ControllerContext, actionName))
                        {
                            this.HandleUnknownAction(actionName);
                        }
                    };
                }
                Action action = action2;
                result = AsyncResultWrapper.BeginSynchronous(callback, state, action, _executeCoreTag);
            }
            catch
            {
                this.PossiblySaveTempData();
                throw;
            }
            return result;
        }

從上面的代碼中,你應該看到了有一個 this.PossiblyLoadTempData()方法,看這個名字我們大概就可以猜得到這個方法和tempdate肯定有莫大的關係。

說時遲那時快,我們可以看下這個方法到底幹了什麼。。。在一系列跟蹤之後,我們最後會到這個代碼裡面去了,如下所示:

        internal void PossiblyLoadTempData()
        {
            if (!base.ControllerContext.IsChildAction)
            {
                base.TempData.Load(base.ControllerContext, this.TempDataProvider);
            }
        }

 

請大家看清了,這裡我們調用了剛纔文章開頭出說到的Tempdata.Load方法,那麼問題來了,這裡的TempDataProvider到底是怎麼來的。我們繼續來看代碼:

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

 

看到沒有,然後TempDataProvider然來是調用了CreateTempDataProvider方法來實現的,下一步我們來看一下CreateTempDataProvider到底幹了什麼。

        protected virtual ITempDataProvider CreateTempDataProvider()
        {
            ITempDataProviderFactory service = this.Resolver.GetService<ITempDataProviderFactory>();
            if (service != null)
            {
                return service.CreateInstance();
            }
            return (this.Resolver.GetService<ITempDataProvider>() ?? new SessionStateTempDataProvider());
        }

從上面這個代碼,我們應該就明白了,然來我們的tempdata預設是由SessionStateTempDataProvider來提供的,好了,接下來我們就可以繼續看看

SessionStateTempDataProvider大概實現的業務邏輯。

  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介面,裡面有兩個方法LoadTempData 和SaveTempData方法,而

LoadTempData方法的邏輯很奇葩,你可以仔細觀察一下哦,如果 if (session != null)滿足就清空字典的數據,否則就不清除,這個邏輯大概就向

你展示了為什麼數據只能被讀取一次,下次讀取的時候,就走了這個if(session!=null)給清空了,你怎麼可能再讀取session中的數據呢。。。這個

就是為什麼tempdata只能被讀取一次的真相,是不是很好玩。

 

<2> EndExecuteCore

    有人可能會問了,第二個方法SaveTempData是什麼時候執行的,當然就是EndExecuteCore裡面了,比如你看:

        protected virtual void EndExecuteCore(IAsyncResult asyncResult)
        {
            try
            {
                AsyncResultWrapper.End(asyncResult, _executeCoreTag);
            }
            finally
            {
                this.PossiblySaveTempData();
            }
        }

可以看到它的預設實現是session,當然你也可以實現一個自定義的provider,比如用cache來存放這個臨時數據,或者是redis,mongodb等等。。。

當然還有更多有趣的東西等待你發掘哦~~~

 


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

-Advertisement-
Play Games
更多相關文章
  • 我們在開發中常需要啟動長時間運行的程式,需要在關閉ssh連接時,仍然保持程式正常運行,為此我們需要用到nohup命令。當然也可以使用screen命令,我在之前的文章中已有介紹,可以參考。 nohup命令使用起來並不難,之前我也並沒有深入的瞭解。在最近的開發過程中由於需要啟動代理服務,因此使用了noh ...
  • centos中安裝mysql很簡單如下命令即可 yum install mysql 裝好了, 運行mysql ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.so ...
  • 這3種漏洞常規加固都要對應操作系統打官方漏洞升級包。既然這麼說那下麵就是不常規的: Openssh: 改ssh版本:whereis ssh //查看ssh目錄cd 到該目錄cp ssh ssh.bak //備份sshssh -V //查看ssh版本sed -i 's/OpenSSH_6.9p1/Op ...
  • 在EF 中怎麼使用事務? 這個問題糾結了我好久,直到有人跟我一起討論,我和同事一起討論查資料。 查的好多資料都是使用 這種方式。而我在實際使用中是沒法使用的。 所以我就一直找其他的方式,無意中看到某個網站的的database,然後我就採用了下麵的方式: using (var dbContext = ...
  • 前言 如果你在系統中用到了Solr的話,那麼肯定會碰到從Solr中反推數據的需求,基於資料庫數據生產索引後,那麼Solr索引的數據相對準確,在電商需求中經常會碰到菜單、導航分類(比如電腦、PC的話會有很多品牌)、新車二手車導航會有車的品牌。還會根據價格區間自由組合組成自定義查詢條件。常用高級用法如下 ...
  • 從前面的示例中,可以看到客戶端捕獲了異常,這是我們處理異常的前提。為了有利於我們進行有效的調試,WCF提供了ServiceDebug Service Behavior。我們可以通過設置屬性設為true,那麼如果服務端拋出異常,WCF會簡單得包裝這個異常並把它置於Soap中Response到服務端的訪... ...
  • 目錄: 作為一個小型的數據存儲傳遞的工具——XML,大家肯定也不陌生,今天就關於XML的一些簡單操作做些總結。 這些都是在控制台進行操作的····· 1.創建XML 1)創建普通XML static void Main(string[] args) { //通過代碼來創建XML文檔 //1、引用命名 ...
  • 當前電腦的時間: DateTime.Now//當前時間 DateTime.Now.ToLongDateString();//顯示日期 xxxx年xx月xx日 ,一個是中文日期 DateTime.Now.ToLongDateString();//顯示日期 xxxx年xx月xx日 ,一個是中文日期 Da ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...