本篇主要讨论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使用下面用一个类图概括一下几个类的关系