浅谈ASP.NET MVC中TempData的实现机制

开发 后端
在这里我们将讨论的是ASP.NET MVC中TempData的实现机制,希望通过本文能对大家有所帮助。

本文将介绍的ASP.NET MVC中的TempData,希望通过这些分析能为大家了解ASP.NET MVC有所帮助。

今天我们讨论的是MVC中一项重要的功能,在其它的一些MVC框架中也很常见它的身影,它就是TempData,下面我们一起来分析一下TempData的原理。

#t#

内容概览Top

本篇主要讨论ASP.NET 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接口,从字面意思上看我们先把它翻译为:暂时数据的提供者所遵从的规则,它约定了两个方法:

  1. public interface ITempDataProvider {  
  2.     IDictionary LoadTempData(ControllerContext controllerContext);  
  3.     void SaveTempData(ControllerContext controllerContext, IDictionary values);  

 

这两个方法是LoadTempData和SaveTempData,我们猜想这两个方法是用来取得TempData容器和保存TempData数据的,因为LoadTempData返回一个IDictionary类型,而SaveTempData没有返回类型,而参数ControllerContext就是针对不同的用户上下文来设计的,标明是对那一个上下文的TempData进行操作。的确是这样的,后面会验证我们的猜想。

 

再来看看TempDataDictionary,我们对这个类的第一印象在哪里呢?是在ControllerBase类中的TempData属性,在普通的Controller中我们打上tempdata,vs帮助我们完成的那个属性其实就是ControllerBase类中的TempData。因此我们明白了,不管是在controller中,还是在view中,所有对TempData的操作都是对TempDataDictionary类型的操作。那ITempDataProvider有是怎么与TempDataDictionary联系的呢?看一下TempDataDictionary的设计便一目了然。

TempDataDictionary的设计Top

public class TempDataDictionary : IDictionary<string, object>, ISerializable

这是TempDataDictionary的签名,我们看到它继承了一个IDictionary<string,object>的字典类型和一个ISerializable的接口。因此我们知道它是可以被序列化和反序列化的,该类有一个常字符串类型的字段和一个Dictionary<string,object>类型的字段:

  1. internal const string _tempDataSerializationKey = "__tempData";  
  2. internal Dictionary<string, object> _data; 

 

在它带参的构造函数中发现了对_tempDataSerializationKey的使用:

 

  1. protected TempDataDictionary(SerializationInfo info, StreamingContext context) {  
  2.     _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);  
  3.     _modifiedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);  
  4.     _data = info.GetValue(_tempDataSerializationKey, typeof(Dictionary<string, object>))  
  5.             as Dictionary<string, object>;  

我们可以看到这是用来从一个流中,反序列化得到一个Dictionary类型的过程。

另一点,在controller中,我们可以这样使用TempData的:

  1. TempData["msg"] = new Object();  
  2. Object obj = TempData["msg"as object

在了解它的索引器之前我们先看看它的几个字段和方法,TempDataDictionary类重要的字段有三个:

  1. internal Dictionary<string, object> _data;  
  2. private HashSet<string> _initialKeys;  
  3. private HashSet<string> _modifiedKeys; 

_data用来存放真正的数据,_initialKeys用来存放原先数据的key,_modifiedKeys用来存放修改过或新添加的数据key。为什么要这样呢?回想一下TempData的特性,TempData只存放一次数据,到第三个Action时,第一个Action存放的数据就失效了,所以,_initialKeys被设计来存放那些数据是原来的,_modifiedKeys被设计来存放那些数据是修改过的或是新添加上的,这样就区分了“旧”数据和“新”数据,那下一步就是把“旧”的删除,把“新”的记录了。

我们再到索引器看看,因为我们对TempData的操作是从索引器开始的,下面是索引器的代码:

  1. public object this[string key] {  
  2.     get {  
  3.         object value;  
  4.         if (TryGetValue(key, out value)) {  
  5.             return value;  
  6.         }  
  7.         return null;  
  8.     }  
  9.     set {  
  10.         _data[key] = value;  
  11.         _modifiedKeys.Add(key);  
  12.     }  

 

当我们TempData["msg"]=new Object();时不仅向_data中添加了数据,同时_modifiedKeys也保存了“新”数据的key。那什么时候“新”数据被保存“旧”数据被删除,真正的执行呢?这个过程是在Load和Save方法中发生的。下面看它们的具体实现:

 

  1. public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider) {  
  2.     IDictionary<stringobject> providerDictionary = tempDataProvider.LoadTempData(  
  3.             controllerContext);  
  4.     _data = (providerDictionary != null) ? new Dictionary<stringobject>(providerDictionary,  
  5.             StringComparer.OrdinalIgnoreCase) : new Dictionary<stringobject>  
  6.             (StringComparer.OrdinalIgnoreCase);  
  7.     _initialKeys = new HashSet<string>(_data.Keys);  
  8.     _modifiedKeys.Clear();  
  9. }  
  10. public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider) {  
  11.     if (_modifiedKeys.Count > 0) {  
  12.         // Apply change tracking.  
  13.         foreach (string x in _initialKeys) {  
  14.             if (!_modifiedKeys.Contains(x)) {  
  15.                 _data.Remove(x);  
  16.             }  
  17.         }  
  18.         // Store the dictionary  
  19.         tempDataProvider.SaveTempData(controllerContext, _data);  
  20.     }  

我们看到TempDataDictionary的Load方法首先是调用了ITempDataProvider的LoadTempData方法来获取tempdata容器,然后让_initialKeys等于_data.Keys,相当于保存了“旧”数据的key,然后清空_modifiedKeys,相当于目前没有“新”数据。而Save方法则是检查_modifiedKeys.Count是否大于0,就相当于检查是否有“新”数据,有则调用ITempDataProveder的SaveTempData方法保存掉“新”数据。这里也验证了我们先前的猜想是正确的。

说到这里,我们似乎还没有发现没有一个地方调用TempDataDictionary的Load和Save方法,也就是说“新”“旧”数据一直在都在_data中,似乎“旧”的数据没有真正删除,“新”数据也一直没有一个安定的家。

我们说对TempData中数据的“刷新”操作(刷新操作即把“旧”数据删除,把“新”数据保存)应该发生在执行Action的时候,那在什么地方我们执行了Action呢,是在IController的Execute方法中,IController<=ControllerBase<=Controller,顺着这样的继承顺序,我们找到Controller类的ExecuteCore方法,这里是执行Action的地方,下面我们看看ExecuteCore方法的实现:

  1. protected override void ExecuteCore() {  
  2.     TempData.Load(ControllerContext, TempDataProvider);  
  3.     try {  
  4.         string actionName = RouteData.GetRequiredString("action");  
  5.         if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {  
  6.             HandleUnknownAction(actionName);  
  7.         }  
  8.     }  
  9.     finally {  
  10.         TempData.Save(ControllerContext, TempDataProvider);  
  11.     }  

我们看到在这里,Action执行之前TempData.Load,Action执行之后TempData.Save。这就实现了TempData的“刷新”操作。

SessionStateTempDataProvider与ITempDataProviderTop

到这里,我们发现似乎还不知道到底数据是怎么被保存的,我们只知道ITempDataProvider提供了一个保存数据和获取容器的这么一个约定,那么具体的实现肯定是继承了ITempDataProvider接口的类来做,SessionStateTempDataProvider就是这么一个类。

我们知道是在Controller类中的ExecuteCore方法中执行了“刷新”操作,我们还知道TempDataDictionary的Load和Save方法需要一个ITempDataProvider的方法,那么我们可以推断肯定要去Controller类中寻找ITempDataProvider的实现。如我们所料:

  1. public ITempDataProvider TempDataProvider {  
  2.     get {  
  3.         if (_tempDataProvider == null) {  
  4.             _tempDataProvider = new SessionStateTempDataProvider();  
  5.         }  
  6.         return _tempDataProvider;  
  7.     }  
  8.     set {  
  9.         _tempDataProvider = value;  
  10.     }  

这里使用了属性注入,强硬的注入了一个SessionStateTempDataProvider对象。那么具体是怎样实现存储的就要去看一下SessionStateTempDataProvider类了。

SessionStateTempDataProvider有一个常字符串字段:

  1. internal const string TempDataSessionStateKey = "__ControllerTempData"

下面是LoadTempData方法:

  1. public virtual IDictionary LoadTempData(ControllerContext controllerContext) {  
  2.     HttpContextBase httpContext = controllerContext.HttpContext;  
  3.     if (httpContext.Session == null) {  
  4.         throw new InvalidOperationException(  
  5.                 MVCResources.SessionStateTempDataProvider_SessionStateDisabled);  
  6.     }  
  7.     Dictionary<stringobject> tempDataDictionary = httpContext.Session[TempDataSessionStateKey]  
  8.                                                         as Dictionary<stringobject>;  
  9.     if (tempDataDictionary != null) {  
  10.         // If we got it from Session, remove it so that no other request gets it  
  11.         httpContext.Session.Remove(TempDataSessionStateKey);  
  12.         return tempDataDictionary;  
  13.     }  
  14.     else {  
  15.         return new Dictionary<stringobject>(StringComparer.OrdinalIgnoreCase);  
  16.     }  

上面的代码很简单,原来它把Dictionary类型的数据存进了Session["__ControllerTempData"]里,读的时候也只是简单的类型转换一下就返回了。

下面是SaveTempData方法:

  1. public virtual void SaveTempData(ControllerContext controllerContext, IDictionary values) {  
  2.     HttpContextBase httpContext = controllerContext.HttpContext;  
  3.     if (httpContext.Session == null) {  
  4.         throw new InvalidOperationException(  
  5.                 MVCResources.SessionStateTempDataProvider_SessionStateDisabled);  
  6.     }  
  7.     httpContext.Session[TempDataSessionStateKey] = values;  

SaveTempData方法也很简单。

总结Top

ITempDataProvider只是一个提供临时数据存取的一个约定的接口,它并不提供如何管理“新旧”数据,TempDataDictionary类才是真正管理“新旧”数据的管理者,但是这个“管理者”需要一个存取“新旧”数据的途径,也就是说它告诉ITempDataProvider该存什么该取什么,然后由ITempDataProvider真正的去执行存取操作。在Controller执行Action之前,这个“管理者”要取得上一次的“旧”数据,Action结束之后它还要把“新”数据给存起来。而Controller恰似这么一个“指挥者”,它把一个能做ITempDataProvider事情的类——SessionStateTempDataProvider交给TempDataProvider使用。下面用一个类图概括一下几个类的关系。

结构图 

原文标题:揭秘ASP.NET mvc TempData机制

链接:http://www.cnblogs.com/niuchenglei/archive/2009/12/19/1627988.html

责任编辑:彭凡 来源: 博客园
相关推荐

2009-04-08 09:58:07

ASP.NET MVCTempData框架

2009-12-07 09:23:05

ASP.NET MVC

2009-07-23 15:44:39

ASP.NET MVC

2009-07-22 13:24:24

ASP.NET MVC

2009-07-07 10:14:57

基于URL权限控制

2009-06-15 10:57:51

FluentHtmlASP.NET MVC

2011-01-28 09:45:29

ASP.NET MVC

2009-07-20 12:42:04

MvcContrib.ASP.NET MVC

2009-07-20 15:44:32

ASP.NET MVC

2009-07-27 13:01:28

TreeViewASP.NET

2009-07-28 14:47:18

ASP.NET MVC

2009-07-22 13:16:04

MvcAjaxPaneASP.NET MVC

2011-06-08 11:36:16

ASP.NETrender

2009-07-24 13:20:44

MVC框架ASP.NET

2009-09-10 09:50:47

ASP.NET MVC

2009-07-22 16:02:39

ASP.NET MVCPagedList

2009-09-24 09:26:22

ASP.NET MVC

2009-07-22 16:11:43

ASP.NET AJA

2009-07-20 15:30:11

ASP.NET应用

2009-07-24 10:52:42

ASP.NET ISA
点赞
收藏

51CTO技术栈公众号