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源码解读之流数据不断接收全生命周期彻底研究和思考