本篇主要討論ASPNET MVC中TempData是如何實現的通過研讀MVC的源代碼你將清楚的了解MVC是如何實現TempData功能的
TempData特性
TempDataDictionary與ITempDataProvider
TempDataDictionary的設計
SessionStateTempDataProvider與ITempDataProvider
TempData特性Top
TempData的特性就是可以在兩個Action之間傳遞數據它會保存一份數據到下一個Action並隨著再下一個Action的到來而失效所以它被用在兩個Action之間來保存數據比如這樣一個場景你的一個Action接受一些post的數據然後交給另一個Action來處理並顯示到頁面這時就可以使用TempData來傳遞這份數據
那到底TempData是怎樣完成這個功能的呢?下面我們從MVC的源代碼入手來解析TempData的機制
TempDataDictionary與ITempDataProviderTop
首先來看看ITempDataProvider接口從字面意思上看我們先把它翻譯為暫時數據的提供者所遵從的規則它約定了兩個方法
public interface ITempDataProvider
{
IDictionary LoadTempData(ControllerContext controllerContext);
void SaveTempData(ControllerContext controllerContext IDictionary values);
}
這兩個方法是LoadTempData和SaveTempData我們猜想這兩個方法是用來取得TempData容器和保存TempData數據的因為LoadTempData返回一個IDictionary類型而SaveTempData沒有返回類型而參數ControllerContext就是針對不同的用戶上下文來設計的標明是對那一個上下文的TempData進行操作的確是這樣的後面會驗證我們的猜想
再來看看TempDataDictionary我們對這個類的第一印象在哪裡呢?是在ControllerBase類中的TempData屬性在普通的Controller中我們打上tempdatavs幫助我們完成的那個屬性其實就是ControllerBase類中的TempData因此我們明白了不管是在controller中還是在view中所有對TempData的操作都是對TempDataDictionary類型的操作那ITempDataProvider有是怎麼與TempDataDictionary聯系的呢?看一下TempDataDictionary的設計便一目了然
TempDataDictionary的設計Top
public class TempDataDictionary : IDictionary<string object> ISerializable
這是TempDataDictionary的簽名我們看到它繼承了一個IDictionary<stringobject>的字典類型和一個ISerializable的接口因此我們知道它是可以被序列化和反序列化的該類有一個常字符串類型的字段和一個Dictionary<stringobject>類型的字段
internal const string _tempDataSerializationKey = __tempData; internal Dictionary<string object> _data;
在它帶參的構造函數中發現了對_tempDataSerializationKey的使用
protected TempDataDictionary(SerializationInfo info StreamingContext context)
{
_initialKeys = new HashSet<string>(StringComparerOrdinalIgnoreCase);
_modifiedKeys = new HashSet<string>(StringComparerOrdinalIgnoreCase);
_data = infoGetValue(_tempDataSerializationKey typeof(Dictionary<string object>))
as Dictionary<string object>;
}
我們可以看到這是用來從一個流中反序列化得到一個Dictionary類型的過程
另一點在controller中我們可以這樣使用TempData的
TempData[msg] = new Object(); Object obj = TempData[msg] as object; 在了解它的索引器之前我們先看看它的幾個字段和方法TempDataDictionary類重要的字段有三個
internal Dictionary<string object> _data; private HashSet<string> _initialKeys; private HashSet<string> _modifiedKeys; _data用來存放真正的數據_initialKeys用來存放原先數據的key_modifiedKeys用來存放修改過或新添加的數據key為什麼要這樣呢?回想一下TempData的特性TempData只存放一次數據到第三個Action時第一個Action存放的數據就失效了所以_initialKeys被設計來存放那些數據是原來的_modifiedKeys被設計來存放那些數據是修改過的或是新添加上的這樣就區分了舊數據和新數據那下一步就是把舊的刪除把新的記錄了
我們再到索引器看看因為我們對TempData的操作是從索引器開始的下面是索引器的代碼
public object this[string key]
{
get
{
object value;
if (TryGetValue(key out value))
{
return value;
}
return null;
}
set {
_data[key] = value;
_modifiedKeysAdd(key);
}
}
當我們TempData[msg]=new Object();時不僅向_data中添加了數據同時_modifiedKeys也保存了新數據的key那什麼時候新數據被保存舊數據被刪除真正的執行呢?這個過程是在Load和Save方法中發生的下面看它們的具體實現
public void Load(ControllerContext controllerContext ITempDataProvider tempDataProvider)
{
IDictionary<string object> providerDictionary = tempDataProviderLoadTempData( controllerContext); _data = (providerDictionary != null) ? new Dictionary<string object>(providerDictionary
StringComparerOrdinalIgnoreCase) : new Dictionary<string object>
(StringComparerOrdinalIgnoreCase);
_initialKeys = new HashSet<string>(_dataKeys);
_modifiedKeysClear();
}
public void Save(ControllerContext controllerContext ITempDataProvider tempDataProvider)
{
if (_modifiedKeysCount > )
{
// Apply change tracking
foreach (string x in _initialKeys)
{
if (!_modifiedKeysContains(x))
{
_dataRemove(x);
}
}
// Store the dictionary
tempDataProviderSaveTempData(controllerContext _data);
}
}
我們看到TempDataDictionary的Load方法首先是調用了ITempDataProvider的LoadTempData方法來獲取tempdata容器然後讓_initialKeys等於_dataKeys相當於保存了舊數據的key然後清空_modifiedKeys相當於目前沒有新數據而Save方法則是檢查_modifiedKeysCount是否大於就相當於檢查是否有新數據有則調用ITempDataProveder的SaveTempData方法保存掉新數據這裡也驗證了我們先前的猜想是正確的
說到這裡我們似乎還沒有發現沒有一個地方調用TempDataDictionary的Load和Save方法也就是說新舊數據一直在都在_data中似乎舊的數據沒有真正刪除新數據也一直沒有一個安定的家
我們說對TempData中數據的刷新操作(刷新操作即把舊數據刪除把新數據保存)應該發生在執行Action的時候那在什麼地方我們執行了Action呢是在IController的Execute方法中IController<=ControllerBase<=Controller順著這樣的繼承順序我們找到Controller類的ExecuteCore方法這裡是執行Action的地方下面我們看看ExecuteCore方法的實現
protected override void ExecuteCore()
{
TempDataLoad(ControllerContext TempDataProvider);
try {
string actionName = RouteDataGetRequiredString(action);
if (!ActionInvokerInvokeAction(ControllerContext actionName))
{ HandleUnknownAction(actionName);
}
}
finally
{
TempDataSave(ControllerContext TempDataProvider);
}
}
我們看到在這裡Action執行之前TempDataLoadAction執行之後TempDataSave這就實現了TempData的刷新操作
SessionStateTempDataProvider與ITempDataProviderTop
到這裡我們發現似乎還不知道到底數據是怎麼被保存的我們只知道ITempDataProvider提供了一個保存數據和獲取容器的這麼一個約定那麼具體的實現肯定是繼承了ITempDataProvider接口的類來做SessionStateTempDataProvider就是這麼一個類
我們知道是在Controller類中的ExecuteCore方法中執行了刷新操作我們還知道TempDataDictionary的Load和Save方法需要一個ITempDataProvider的方法那麼我們可以推斷肯定要去Controller類中尋找ITempDataProvider的實現如我們所料
public ITempDataProvider TempDataProvider {
get {
if (_tempDataProvider == null)
{
_tempDataProvider = new SessionStateTempDataProvider();
}
return _tempDataProvider;
}
set {
_tempDataProvider = value;
}
}
這裡使用了屬性注入強硬的注入了一個SessionStateTempDataProvider對象那麼具體是怎樣實現存儲的就要去看一下SessionStateTempDataProvider類了
SessionStateTempDataProvider有一個常字符串字段
internal const string TempDataSessionStateKey = __ControllerTempData; 下面是LoadTempData方法
public virtual IDictionary LoadTempData(ControllerContext controllerContext)
{
HttpContextBase ;
if ( == null)
{
throw new InvalidOperationException(
MVCResourcesSessionStateTempDataProvider_SessionStateDisabled);
}
Dictionary<string object> tempDataDictionary = [TempDataSessionStateKey] as Dictionary<string object>;
if (tempDataDictionary != null)
{
// If we got it from Session remove it so that no other request gets it
(TempDataSessionStateKey);
return tempDataDictionary;
}
else
{
return new Dictionary<string object>(StringComparerOrdinalIgnoreCase);
}
}
上面的代碼很簡單原來它把Dictionary類型的數據存進了Session[__ControllerTempData]裡讀的時候也只是簡單的類型轉換一下就返回了
下面是SaveTempData方法
public virtual void SaveTempData(ControllerContext controllerContext IDictionary values)
{
HttpContextBase ;
if ( == null)
{
throw new InvalidOperationException(
MVCResourcesSessionStateTempDataProvider_SessionStateDisabled);
}
[TempDataSessionStateKey] = values;
} SaveTempData方法也很簡單
總結Top
ITempDataProvider只是一個提供臨時數據存取的一個約定的接口它並不提供如何管理新舊數據TempDataDictionary類才是真正管理新舊數據的管理者但是這個管理者需要一個存取新舊數據的途徑也就是說它告訴ITempDataProvider該存什麼該取什麼然後由ITempDataProvider真正的去執行存取操作在Controller執行Action之前這個管理者要取得上一次的舊數據Action結束之後它還要把新數據給存起來而Controller恰似這麼一個指揮者它把一個能做ITempDataProvider事情的類——SessionStateTempDataProvider交給TempDataProvider使用下面用一個類圖概括一下幾個類的關系
From:http://tw.wingwit.com/Article/program/net/201311/13479.html