網上對TempData的總結為: 保存在session中,Controller每次執行請求時,會從session中一次獲取所有tempdata數據,保存在單獨的內部數據字典中,而後從session中清空tempdata。然後通過key從字典中獲取指定的Tempdata,每訪問一次後對應的Key就會從 ...
網上對TempData的總結為: 保存在session中,Controller每次執行請求時,會從session中一次獲取所有tempdata數據,保存在單獨的內部數據字典中,而後從session中清空tempdata。然後通過key從字典中獲取指定的Tempdata,每訪問一次後對應的Key就會從字典中刪除,因此Tempdata數據最多只能經過一次controller傳遞,並且每個元素最多只能訪問一次。
也許你不需要知道背後的原理,那下麵這張圖就能滿你所需。
若好奇它背後是怎樣實現,跟我查下源碼一探究竟:
一、Controller是何時取TempData數據
看下Controller類的ExecuteCore方法
/// <summary>執行請求</summary>
protected override void ExecuteCore()
{
this.PossiblyLoadTempData();
try
{
string actionName = Controller.GetActionName(this.RouteData);
if (this.ActionInvoker.InvokeAction(this.ControllerContext, actionName))
return;
this.HandleUnknownAction(actionName);
}
finally
{
this.PossiblySaveTempData();
}
}
internal void PossiblyLoadTempData()
{
if (this.ControllerContext.IsChildAction)
return;
this.TempData.Load(this.ControllerContext, this.TempDataProvider);
}
internal void PossiblySaveTempData()
{
if (this.ControllerContext.IsChildAction)
return;
this.TempData.Save(this.ControllerContext, this.TempDataProvider);
}
從中可以看到在請求開始時就去取TempData,在Action調用結束後去保存TempData。
為什麼要再去保存一遍呢?
二、TempDataProvider 臨時數據存儲方案
1、Controller類中,定義了TempDataProvider屬性
/// <summary>獲取用於為下一個請求存儲數據的臨時數據提供程式對象。 </summary>
/// <returns>臨時數據提供程式。</returns>
public ITempDataProvider TempDataProvider
{
get
{
if (this._tempDataProvider == null)
this._tempDataProvider = this.CreateTempDataProvider();
return this._tempDataProvider;
}
set
{
this._tempDataProvider = value;
}
}
/// <summary>創建臨時數據提供程式。</summary>
/// <returns>臨時數據提供程式。</returns>
protected virtual ITempDataProvider CreateTempDataProvider()
{
ITempDataProviderFactory service = this.Resolver.GetService<ITempDataProviderFactory>();
if (service != null)
return service.CreateInstance();
return this.Resolver.GetService<ITempDataProvider>() ?? (ITempDataProvider) new SessionStatesTempDataProvider();
}
從代碼中可知MVC中預設使用的是SessionStatesTempDataProvider
來存儲臨時數據。
2、看一看SessionStatesTempDataProvider
的實現
public class SessionStateTempDataProvider : ITempDataProvider
{
internal const string TempDataSessionStateKey = "__ControllerTempData";
/// <summary>使用指定的控制器上下文來載入臨時數據。</summary>
/// <returns>臨時數據。</returns>
/// <param name="controllerContext">控制器上下文。</param>
/// <exception cref="T:System.InvalidOperationException">檢索會話上下文時出錯。</exception>
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 (IDictionary<string, object>) dictionary;
}
}
return (IDictionary<string, object>) new Dictionary<string, object>((IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase);
}
/// <summary>使用指定的控制器上下文將指定的值保存在臨時數據字典中。</summary>
/// <param name="controllerContext">控制器上下文。</param>
/// <param name="values">值。</param>
/// <exception cref="T:System.InvalidOperationException">檢索會話上下文時出錯。</exception>
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"] = (object) values;
}
else
{
if (session["__ControllerTempData"] == null)
return;
session.Remove("__ControllerTempData");
}
}
}
從圖中可知,SessionStatesTempDataProvider
暴露了LoadTempData
和SaveTempData
兩個方法。
其中從SaveTempData
中session["__ControllerTempData"] = (object) values;
可以看出,TempData是存儲在Session中的。
其中LoadTempData
方法中session.Remove("__ControllerTempData");
就說明瞭從session中獲取tempdata後,對應的tempdata就從session中清空了
原來每次取完TempData後都會從Session中清空,如果TempData未曾使用,那當然要重新保存到Session中啊。(回答了上個問題)
三、TempData 何許類也?
TempData是ControllerBase中定義的屬性,TempData的類型為TempDataDictionary。
那就來看看這個類中定義的幾個核心方法。
1、 定義了索引器
/// <summary>獲取或設置具有指定鍵的對象。</summary>
/// <returns>具有指定鍵的對象。</returns>
public object this[string key]
{
get
{
object obj;
if (!this.TryGetValue(key, out obj))
return (object) null;
this._initialKeys.Remove(key);
return obj;
}
set
{
this._data[key] = value;
this._initialKeys.Add(key);
}
}
要註意這段代碼this._initialKeys.Remove(key);
。
2、再來看看取出TempData的Load方法
/// <summary>使用指定的數據提供程式載入指定的控制器上下文。</summary>
/// <param name="controllerContext">控制器上下文。</param>
/// <param name="tempDataProvider">臨時數據提供程式。</param>
public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
{
IDictionary<string, object> dictionary = tempDataProvider.LoadTempData(controllerContext);
this._data = dictionary != null ? new Dictionary<string, object>(dictionary, (IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase) : new Dictionary<string, object>((IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase);
this._initialKeys = new HashSet<string>((IEnumerable<string>) this._data.Keys, (IEqualityComparer<string>) StringComparer.OrdinalIgnoreCase);
this._retainedKeys.Clear();
}
這個方法是從TempDataProvider中載入出的數據,將全部TempData保存在字典_data
全局變數中,TempData的key全部保存在_initialKeys
全局變數中。
3、TempData如何使用多次?
/// <summary>將字典中的所有鍵都標記為需保留。</summary>
public void Keep()
{
this._retainedKeys.Clear();
this._retainedKeys.UnionWith((IEnumerable<string>) this._data.Keys);
}
/// <summary>將字典中的指定鍵標記為需保留。</summary>
/// <param name="key">字典中要保留的鍵。</param>
public void Keep(string key)
{
this._retainedKeys.Add(key);
}
/// <summary>返回包含與指定鍵關聯的元素的對象,不將該鍵標記為需刪除。</summary>
/// <returns>包含與指定鍵關聯的元素的對象。</returns>
/// <param name="key">要返回的元素的鍵。</param>
public object Peek(string key)
{
object obj;
this._data.TryGetValue(key, out obj);
return obj;
}
從TempData中通過索引器取值後,可以通過Keep或Peek方法,將該臨時數據保留不刪除。
4、再來看看將數據保存到TempData的Save方法
/// <summary>使用指定的數據提供程式保存指定的控制器上下文。</summary>
/// <param name="controllerContext">控制器上下文。</param>
/// <param name="tempDataProvider">臨時數據提供程式。</param>
public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
{
this._data.RemoveFromDictionary<string, object, TempDataDictionary>((Func<KeyValuePair<string, object>, TempDataDictionary, bool>) ((entry, tempData) =>
{
string key = entry.Key;
if (!tempData._initialKeys.Contains(key))
return !tempData._retainedKeys.Contains(key);
return false;
}), this);
tempDataProvider.SaveTempData(controllerContext, (IDictionary<string, object>) this._data);
}
_data 是放Keys + Values
_initialKeys 是放Keys,取值後移除Key
_retainedKeys 是需要保留的Key
當我們根據key從索引器中讀取臨時數據時,該key從_initialKeys中移出。
Save方法首先遍歷_data:
若_initialKeys不存在該key,說明已經取值使用。
若_retainedKeys中也不存在該key,說明取值使用後並未keep。
以上兩個條件都成立時就從_data中移出該TempData。
未成立就說明臨時數據沒有使用,需重新保存到Session中。
總結
- Controller每次執行請求時,會從session中一次獲取所有tempdata數據,保存在單獨的內部數據字典中,而後從session中清空tempdata。
- 在需要的action中通過key從字典中獲取指定的Tempdata,每訪問一次後對應的Key就會從字典中刪除。因此Tempdata數據最多只能經過一次controller傳遞,並且每個元素最多只能訪問一次。
- Action執行完畢後數據字典中未使用的tempdata會重新保存到Session中,供下一個請求訪問。
- 如果tempdata使用後還想供下一個請求使用,可以通過調用
TempData.Keep()
或TempData.Keep("key")
保留至下一次請求。 - 還可以通過
TempData.Peek()
讀取,這種方式同樣會保留至下一次請求。
啰嗦了半天,TempData你懂了嗎?反正我是懂了。
還沒懂,那就回頭看看吧。