MVC源码解读ViewDataViewBagTempData的区别

Posted 1Note

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MVC源码解读ViewDataViewBagTempData的区别相关的知识,希望对你有一定的参考价值。

asp.net MVC框架提供了4种控制器向视图传值的方法:分别是ViewData,ViewBag,TempData,Model(Model暂不讨论,因为也没啥说的)


平时使用的过程中虽然了解了差异但是不知其所以然,所以这两天用反编译器看了源码,算是知道了个大概。


ViewData和ViewBag


这两货为什么要放在一起来说呢?因为这两个其实底层的实现都是ViewData。ViewBag是对ViewData的封装,牺牲了性能换来了更好的可读性。而且由于ViewBag是dynamic类型,所以使用的时候不需要进行类型转换。

//ControllerBase类里面的ViewBag属性public dynamic ViewBag{ get { if (_dynamicViewDataDictionary == null) {        //重点看这句代码 _dynamicViewDataDictionary = new DynamicViewDataDictionary(() => ViewData); } return _dynamicViewDataDictionary; }}
private DynamicViewDataDictionary _dynamicViewDataDictionary;
可以看到ViewBag的本质就是一个DynamicViewDataDictionary类型,而且在构造函数里面传入了一个委托。我们来看看DynamicViewDataDictionary这个类
internal sealed class DynamicViewDataDictionary : DynamicObject{  //私有委托字段,返回值是一个ViewDataDictionary类型 private readonly Func<ViewDataDictionary> _viewDataThunk; //调用委托函数将返回值赋予给ViewData字段 private ViewDataDictionary ViewData => _viewDataThunk();   //DynamicViewDataDictionary的构造函数,需要传入一个委托。 public DynamicViewDataDictionary(Func<ViewDataDictionary> viewDataThunk) { _viewDataThunk = viewDataThunk; }    //下面3个方法都是通过ViewData属性来进行操作的   public override IEnumerable<string> GetDynamicMemberNames() { return ViewData.Keys; }
public override bool TryGetMember(GetMemberBinder binder, out object result) { result = ViewData[binder.Name]; return true; }
public override bool TrySetMember(SetMemberBinder binder, object value) { ViewData[binder.Name] = value; return true; }}


结论:

ViewBag对数据存储的本质是其底层封装了一个ViewDataDictionary对象。关键是这个ViewDataDictionary对象就是ViewData。



TempData


TempData的数据是存储在session里面的,所以可以跨控制器传值。但是有个特点:存储的数据使用一次之后就会被删除。

/// <summary>Invokes the action in the current controller context.</summary>protected override void ExecuteCore(){ PossiblyLoadTempData(); //注意这里 try { string actionName = GetActionName(RouteData); if (!ActionInvoker.InvokeAction(base.ControllerContext, actionName)) { HandleUnknownAction(actionName); } } finally { PossiblySaveTempData(); }}
internal void PossiblyLoadTempData(){ if (!base.ControllerContext.IsChildAction) { base.TempData.Load(base.ControllerContext, TempDataProvider); }}

可以看到,在调用action方法之前先调用了PossiblyLoadTempData(),调用完action之后又调用了PossiblySaveTempData()。


先来研究PossiblyLoadTempData(),这个方法又调用了TempData.Load。

看一下Load方法

public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider){ IDictionary<string, object> dictionary = tempDataProvider.LoadTempData(controllerContext); _data = ((dictionary != null) ? new Dictionary<string, object>(dictionary, StringComparer.OrdinalIgnoreCase) : new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)); _initialKeys = new HashSet<string>(_data.Keys, StringComparer.OrdinalIgnoreCase); _retainedKeys.Clear();}

看一下LoadTempData

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 dictionary; } } return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);}

可以看到LoadTempData到session里面取完数据保存到字典返回之后就把session的数据清空了。

字典返回的数据存到了TempData的_data字段里面。同时将字典的所有key值存到了_initialKeys 字段里面并且清空_retainedKeys字段。


看一下TempDataDictionary

public class TempDataDictionary : IDictionary<string, object>, ICollection<KeyValuePair<string, object>>, IEnumerable<KeyValuePair<string, object>>, IEnumerable{ public object this[string key] { get { if (TryGetValue(key, out object value)) { _initialKeys.Remove(key); return value; } return null; } set { _data[key] = value; _initialKeys.Add(key); } }}

字典每次取完值之后,就会在_initialKeys将该值的key移除掉(TempData的特性就是取完值就删了,此处有伏笔)


再来看PossiblySaveTempData(),直接贴代码了

internal void PossiblySaveTempData(){ if (!base.ControllerContext.IsChildAction) { base.TempData.Save(base.ControllerContext, TempDataProvider); }}//看一下Save方法public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider){ _data.RemoveFromDictionary(delegate(KeyValuePair<string, object> entry, TempDataDictionary tempData) { string key = entry.Key; return !tempData._initialKeys.Contains(key) && !tempData._retainedKeys.Contains(key); }, this); tempDataProvider.SaveTempData(controllerContext, _data);}
//看一下RemoveFromDictionary方法public static void RemoveFromDictionary<TKey, TValue, TState>(this IDictionary<TKey, TValue> dictionary, Func<KeyValuePair<TKey, TValue>, TState, bool> removeCondition, TState state){ int num = 0; TKey[] array = new TKey[dictionary.Count]; foreach (KeyValuePair<TKey, TValue> item in dictionary) { if (removeCondition(item, state)) { array[num] = item.Key; num++; } } for (int i = 0; i < num; i++) { dictionary.Remove(array[i]); }}

如果_initialKeys_retainedKeys里面都不包含某个key时,那么这个key就会在字典里面被删除。

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"] = values; } else if (session["__ControllerTempData"] != null) { session.Remove("__ControllerTempData"); }}

然后将字典的值保存到session里面。


结论:ExecuteCore执行,在调用action方法之前,先到session里面去取数据存到Tempdata里面(同时将所有的key值存到_initialKeys)然后清空session数据。当我们从Tempdata取值时,该值的key便会在_initialKeys里面移除。调用完action方法之后遍历Tempdata的key值,如果key既不在_initialKeys也不在_retainedKeys,则会将该key值的键值对从Tempdata移除(这就是TempData数据只有一次性的原因)。之后再将Tempdata的数据存回session。


如果我们想重复使用TempData的数据,只需要将key添加到_initialKeys或者_retainedKeys里面即可。


TempDataDictionary类提供了Keep方法

 public void Keep(string key) { _retainedKeys.Add(key); }


以上是关于MVC源码解读ViewDataViewBagTempData的区别的主要内容,如果未能解决你的问题,请参考以下文章

(版本定制)第10课:Spark Streaming源码解读之流数据不断接收全生命周期彻底研究和思考

源码分析spring-mvc启动流程

详解Spring mvc工作原理及源码分析

Spark Streaming源码解读之流数据不断接收全生命周期彻底研究和思考

从Web开发者的视角来解读MVC架构

PHP深入解读MVC框架