深度解析Controller中的TempData及源码

基础:
首先说一下这个TempData在MVC架构体系中的结构和原理,我们一层层来说:

一、首先是TempDataDictionary(也就是你在Controller中访问到的TempData属性)。
TempDataDictionary是放在ControllerBase中的属性,继承于IDictionary<TKey,TValue>。它在ControllerBase中是非虚、不可重写的属性,意味着,如果你要实现自己的TempData,是必须实现自己
Controller了,Controller都要重新实现。
TempDataDictionary内部有个类型Dictionary<string,object>的_data字段,该_data就是保存TempData键值对的载体,TempDataDictionary使用它自己的Load和Save方法来加载和保存这个_data,也提供了方法如何读写该_data。
每执行一个Action前会执行一次TempDataDictionary.Load,该Action执行完毕后会执行TempDataDictionary.Save。
TempDataDictionary规定了它的核心数据字段_data的载体的类型必须是Dictionary<string,object>,但该数据载体实际是谁来管理、放在哪里,则是由ITempDataProvider提供者来提供的。


二、然后是ITempDataProvider
这个接口只有LoadTempData和SaveTempData两个方法,一个是返回实际数据载体,一个是保存数据。
这个ITempDataProvider是由ControllerBase的CreateTempDataProvider虚方法创建的,默认创建的是new SessionStateTempDataProvider(),
你可以在比如HomeController中override这个CreateTempDataProvider方法以定义你自己实现的ITempDataProvider类(后面再说)。


三、然后是ITempDataProvider的默认实现类SessionStateTempDataProvider
SessionStateTempDataProvider利用HttpContext.Session来保存一个Dictionary<string,object>,因为TempDataDictionary的_data字段只接受这种类型
在LoadTempData时,载入返回上次请求(某个Action请求)之后完毕保存下来的Dictionary,然后从session中移除该Dictionary,如果Session中没有则返回一个空实例new Dictionary<string,object>()。
在SaveTempData时,保存本次未移除的Key。继续下面

四、关于TempData(TempDataDictionary)的读写
TempDataDictionary内部除了_data,还有两个字段,一个是类型为HashSet<string>的初始Key集合,一个是类型为HashSet<string>的保留key集合,它们主要是在对TempData读写时起的作用,它们起什么作用看下面实验过程:
注意:在TempDataDictionaryLoad时,初始key集合保存_data字段中所有的key,而保留key集合长度为0,什么都没有
======================================================================================================================================================================================================
实验:
一、现在我们开始请求一个Action:
1.在Action请求前,Controller会在内部先初始化自己的TempData属性,给它new一个TempDataDictionary,然后Controller调用自己的CreateTempDataProvider创建一个SessionStateTempDataProvider
  随后调用TempData的Load方法,Load这个SessionStateTempDataProviderSessionStateTempDataProvider会初始化类型为Dictionary<string,object>的Session数据,并加载到TempData内部的_data,
  此时_data是个长度为0的Dictionary,至此TempData的初始化完成。
2.我们在Action中写一个TempData["aaa"] = 1,在这种写操作的时候,TempDataDictionary会给_data来Add("aaa",1),然后再给初始Key集合 Add("aaa")
3.然后我调用TempData.TryGetValue,或TempData["aaa"] 这种读操作的时候,TempDataDictionary会将初始Key集合中名为"aaa"的key移除掉。
4.这时我们整个Action请求完毕。
5.在Action请求完毕后(不论是否Action发生了异常),Controller会调用TempData字段的Save方法,Save方法会将保留key集合和另一个key集合(_data.Keys和初始Key集合的并集)选出来成一个非重复集合,
  这个集合是未移除的key,用_data.Keys.Except(这个集合)的结果就是已移除的key集合,然后从_data中移除这些已移除的key。
  然后调用SessionStateTempDataProvider的SaveTempData方法将这个_data保存到Session。


二、然后我们再开始一个新的Action请求
1.和前面一样,这次,SessionStateTempDataProvider会载入上次请求完后保存下来的Dictionary
2.这时调用TempData["aaa"],因为aaa已经在上次访问后被移除掉了,所以会报异常提示找不到key,怎么办?看下面三。

三、整个过程中,我们并没有读写保留key集合,那么这个保留key集合是什么用的呢?
TempData有个方法Keep,在你调用TempData["aaa"]或TryGetValue("aaa")之前调用Keep("aaa"),它会把aaa添加到保留key集合,等到TempData["aaa"]或TempData.TryGetValue("aaa",out xxx)的时候,这个aaa虽然会在初始Key集合中被移除,但是待执行到第6步的时候,这个aaa的键值对数据会保存下来存活至下一次请求,下次请求Action仍又可以访问到它了。
此外TempData有个Peek方法,和TryGetValue类似也是读取值,但是它不会将初始Key集合中名为"aaa"的key移除掉,下次请求Action也可以访问到它。也挺实用。

======================================================================================================================================================================================================

后记:
其实我认为mvc中有很多东西,学的人用的人都没有充分利用它。
关于TempData,我猜mvc设计者之所以要这么做,是担心性能问题,也是怕开发者在session里面数据弄多了,容易搞混淆,每次请求后都及时清理掉。毕竟mvc的效率相对低。Items集合里面本来数据就多。如果能搞清楚mvc设计者为什么要这样设计,我想就可以更好的利用这个TempData了。
如果有多线程,最好就别用这个了。自己写个ITempDataProvider实现类

======================================================================================================================================================================================================
总结:
由此可知,在单个Action请求过程中,TempData某个key的值始终可以访问到,但是如果又进行了一个新的请求,那么这个key就没了,你想保留,就使用keep
注意:如果这个请求的Action是一个ChildAction,那么会载入调用这个ChildAction的父视图的 ControllerContext.ParentViewContext.TempData,而不会重复第1步和第5步的操作了
总的来说,不是很复杂。

如果没懂,去研究下源码,你就会清晰一点

原文地址:https://www.cnblogs.com/yuuki/p/3307485.html