Controller最終是通過調用ControllerBase類的Execute(RequestContext)方法來完成一個Action的建立與執行操作, 代碼如下:
protected virtual void Execute(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } Initialize(requestContext); ExecuteCore(); }
代碼分為兩步:
- Initialize(requestContext): 建立ControllerContext類的一個執行個體。
- ExecuteCore(): 載入TempData, 建立及執行Action,處理Action返回的ActionResult , 儲存TempData資料。
ExecuteCore()的代碼如下:
protected override void ExecuteCore() { TempData.Load(ControllerContext, TempDataProvider); try { string actionName = RouteData.GetRequiredString("action"); if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) { HandleUnknownAction(actionName); } } finally { TempData.Save(ControllerContext, TempDataProvider); } }
代碼又分為三個部分:
- TempData.Load(ControllerContext, TempDataProvider): 從HttpContextBase.Session中載入TempData資料
- ActionInvoker.InvokeAction(ControllerContext, actionName): 建立,執行Action,並處理Action返回的ActionResult
- TempData.Save(ControllerContext, TempDataProvider): 儲存TempData
第1, 第3部分都是對TempData的操作,下面的文字將詳細介紹這兩個步驟。
1. TempData.Load(ControllerContext, TempDataProvider)
TempDataProvider: 就是SessionStateTempDataProvider,他是一個繼承了ITempDataProvider介面的Session輔助類
TempDataProvider.Load的源碼:
public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider) { IDictionary<string, object> providerDictionary = tempDataProvider.LoadTempData(controllerContext); _data = (providerDictionary != null) ? new Dictionary<string, object>(providerDictionary, StringComparer.OrdinalIgnoreCase) : new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); _initialKeys = new HashSet<string>(_data.Keys); _modifiedKeys.Clear(); }
這裡就是載入TempData的全部過程:
- 調用SessionStateTempDataProvider中的LoadTempData(…)方法並近回一個IDictionary<string, object>對象
- 初始化_data對象,如果providerDictionary不是null那麼把providerDictionary中的資料存放到_data對象中,其實這個_data就是TempData用來儲存資料的容器,
- 初始化_initialKeys對象並將_data中的全部Key值放入其中,它用來緩衝已載入的Key值,如果對TempData中插入新資料時, _initialKeys對象中的資料不會有任務變化
- 清除_modifiedKeys中的值,用來儲存TempData新的Key值,當有一個新的索引值對插入到TempData中,這個key值會被儲存到_modifiedKeys對象中
再來深入tempDataProvider.LoadTempData(controllerContext)方法,其實就是SessionStateTempDataProvider類中的LoadTempData(…)方法:
public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext) { HttpContextBase httpContext = controllerContext.HttpContext; if (httpContext.Session == null) { throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled); } Dictionary<string, object> tempDataDictionary = httpContext.Session[TempDataSessionStateKey] as Dictionary<string, object>; if (tempDataDictionary != null) { // If we got it from Session, remove it so that no other request gets it httpContext.Session.Remove(TempDataSessionStateKey); return tempDataDictionary; } else { return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); } }
SessionStateTempDataProvider類中定義了一個常量,用它來作為儲存在Session中TempData對象的Key。
internal const string TempDataSessionStateKey = "__ControllerTempData";
在LoadTempData(…)方法中, 首先在Session中尋找與key相對應的Dictionary<string, object>對象,如果Session中存在這個對象,那麼就清除Session中的這個索引值對並返回找到的Dictionary<string, object>對象, 否則建立一個新的Dictionary<string, object>對象並返回。
這裡的重點就是httpContext.Session.Remove(TempDataSessionStateKey) 方法,這也是TempData的特點,即同一個TempData只能被傳遞一次。當在Session中找到TempData後立即將它從Session中清除掉,下一次執行LoadTempData(…)方法時絕不會再找到同一個TempData。
再次說明:只要將一個索引值對放入到TempData中,不論是同一個Controller中的不同Action,還是不同Controller中的不同Action都可以接收到這個TempData,但是僅此一次,當程式再次提交回服務端後是絕不可能再獲得同樣的值的TempData對象, 因為它已經在Session中被清除掉。
2. TempData.Save(ControllerContext, TempDataProvider)
這是整個Controller.Execute(…)生命週期中最後一個操作, 它的作用是儲存新的TempData到HttpContext.Base.Session中。Save方法的具體代碼:
public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider) { if (_modifiedKeys.Count > 0) { // Apply change tracking. foreach (string x in _initialKeys) { if (!_modifiedKeys.Contains(x)) { _data.Remove(x); } } // Store the dictionary tempDataProvider.SaveTempData(controllerContext, _data); } }
上面提到過_initialKeys和_modifiedKeys的重要性,它們的作用在這裡將體現出來,
- _initialKeys: 儲存在Action執行之前TempData中的全部Key值,如果在action執行過程中有新的索引值對插入到TempData中, 對_initialKeys對象中的值不會有任何影響。
- _modifiedKeys: 在Action執行過程中, 如果有新的索引值對插入到TempData中,這個新的Key會插入到_modifiedKeys對象中。
Save方法中首先判斷_modifiedKeys對象中元素的數量,是否有新的索引值對被插入到TempData中,foreach和下面的if語句是從TempData中刪除無用的索引值對資料,最後調用 tempDataProvider.SaveTempData(controllerContext, _data)方法,將TempData儲存在HttpContextBase.Session中。
注意:在載入TempData過程中會將上次定義的TempData中資料放入到_data中,這裡的foreach, if語句是去掉不再需要傳遞的索引值對,確保TempData中僅保留本次Action執行過程中產生的索引值對資料。