asp.net mvc源码分析-Controllerl篇 TempData数据存储
2012-11-08 23:53
567 查看
本些列文章是以asp.net mvc源代码为例按照asp.net mvc执行顺序一一分析和解释。上篇文章asp.net mvc源码分析-Controllerl篇 如何创建Controller实例 讲到了如何创建Controller,在创建后就调用 controller.Execute(RequestContext);
在ControllerBase的Execute方法很简单
VerifyExecuteCalledOnce(); // 确保一个controller实例只调用一次,
Initialize(requestContext);//初始化 ControllerContext = new ControllerContext(requestContext, this);
using (ScopeStorage.CreateTransientScope()) {
ExecuteCore();//这个才是真正的执行
}
本系列文章主要是分析源代码,分析里面的逻辑和实现细节,所以我们还是来看看VerifyExecuteCalledOnce这个方法吧。
internal void VerifyExecuteCalledOnce() {
if (!_executeWasCalledGate.TryEnter()) {
string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBase_CannotHandleMultipleRequests, GetType());
throw new InvalidOperationException(message);
}
}
internal sealed class SingleEntryGate {
private const int NOT_ENTERED = 0;
private const int ENTERED = 1;
private int _status;
// returns true if this is the first call to TryEnter(), false otherwise
public bool TryEnter() {
int oldStatus = Interlocked.Exchange(ref _status, ENTERED);
return (oldStatus == NOT_ENTERED);
}
}
当大家 看了TryEnter方法以后是不是觉得他们实现的很巧妙啊。保证一个类的一个实例方法只执行一次的一种实现方式。
而ExecuteCore这个方法在抽象类Controller中实现,Controller是ControllerBase的子类,
protected override void ExecuteCore() {
PossiblyLoadTempData();
try {
string actionName = RouteData.GetRequiredString("action");
if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
HandleUnknownAction(actionName);
}
}
finally {
PossiblySaveTempData();
}
}
其中 ActionInvoker.InvokeAction(ControllerContext, actionName)是真正的调用Action,我们放到后面来讲,这节我们来看看PossiblyLoadTempData、PossiblySaveTempData这个2个方法。在每次action调用前加载,调用后保存。
internal void PossiblyLoadTempData() {
if (!ControllerContext.IsChildAction) {
TempData.Load(ControllerContext, TempDataProvider);
}
}
internal void PossiblySaveTempData() {
if (!ControllerContext.IsChildAction) {
TempData.Save(ControllerContext, TempDataProvider);
}
}
这 2个方法实现是不特别简单啊,那么TempData属性实现是否简单了?
public TempDataDictionary TempData {
get {
if (ControllerContext != null && ControllerContext.IsChildAction) {
return ControllerContext.ParentActionViewContext.TempData;
}
if (_tempDataDictionary == null) {
_tempDataDictionary = new TempDataDictionary();
}
return _tempDataDictionary;
}
set {
_tempDataDictionary = value;
}
}
这里 需要注意一下的是如果当前Action是一个子Action则返回父辈Action的Controller的TempData。
一提到 TempData ,我们还知道ViewData、ViewBag也是保存数据的,它们之间有何区别了?
TempData 是TempDataDictionary类的实例 public class TempDataDictionary : IDictionary<string, object>
ViewData是ViewDataDictionary类的实例 public class ViewDataDictionary : IDictionary<string, object>
ViewBag是DynamicViewDataDictionary类的实例 internal sealed class DynamicViewDataDictionary : DynamicObject
一般 对它们的区别网上都是如下的内容:
TempData:保存在Session中,Controller每次执行请求的时候,会从Session中先获取TempData,而后清除Session,获取完TempData数据,虽然保存在内部字典对象中,但是其集合中的每个条目访问一次后就从字典表中删除。具体代码层面,TempData获取过程是通过SessionStateTempDataProvider.LoadTempData方法从ControllerContext的Session中读取数据,而后清除Session,故TempData只能跨Controller传递一次。
ViewData:生命周期和View相同,仅对当前View有效。
ViewBag:和ViewData生命周期相同,也是对但前View有效,不同的是ViewBag的类型不再是字典的键值对结构,而是dynamic动态类型,属于MVC3里面新增的部分。
这里的TempData解释是对的吗?
我们 这里主要讲讲TempData,其他2个很简单,TempDataDictionary类主要代码如下:
仔细看以上代码,我们会发现Load只是初始化一个默认的字典,没什么特别的,而Save就有所特别,它在每次保存的时候都移除此次添加的key,说白了又回到初始状态了。反正我是没明白微软为什么要这个做。不保存不就行了吗?
在来让我们看看TempDataProvider
public ITempDataProvider TempDataProvider {
get {
if (_tempDataProvider == null) {
_tempDataProvider = CreateTempDataProvider();
}
return _tempDataProvider;
}
set {
_tempDataProvider = value;
}
}
protected virtual ITempDataProvider CreateTempDataProvider() {
return new SessionStateTempDataProvider();
}
微软代码就这样,看看这 如果我们要设置自己的TempDataProvider 可以通过TempDataProvider 属性来设置,也可以通过重写CreateTempDataProvider方法来实现,总是提供给用户多个选择。
我们还是来看看默认的SessionStateTempDataProvider
先说LoadTempData方法吧,第一次访问tempDataDictionary应该是空的没有任何数据,直接new一个字典。然后就是SaveTempData了,按照前面的理解,这个时候的字典应该没有数据了,一旦有它就是脏数据。
bool isDirty = (values != null && values.Count > 0);
所以 真正的保存执行代码是
if (session[TempDataSessionStateKey] != null) {
session.Remove(TempDataSessionStateKey);
}
移除 session,以至于第二次又重新实例一个字典。但是有一种情况很特殊也是经常遇到的,也是TempData存在的原因。
我们以一段代码来说明吧:
为什么了会这样了,原因很简单,虽然我们调用@{Html.RenderAction("Index","Test");}时候会去执行 PossiblyLoadTempData()、 PossiblySaveTempData()这2个方法,但是他们有一个过滤条件 if (!ControllerContext.IsChildAction) {} 这个条件不满足,所以实际上就没有调用TempData.Load和TempData.Save方法。IsChildAction这个属性究竟是怎么定义的了。
public virtual bool IsChildAction {
get {
RouteData routeData = RouteData;
if (routeData == null) {
return false;
}
return routeData.DataTokens.ContainsKey(PARENT_ACTION_VIEWCONTEXT);
}
}
而RenderAction实际上市调用
internal static void ActionHelper(HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues, TextWriter textWriter)
各参数如下:
actionName:Index
controllerName:Test
routeValues:null
textWriter:htmlHelper.ViewContext.Writer
在这个方法里面有一句 RouteData routeData = CreateRouteData(vpd.Route, routeValues, vpd.DataTokens, htmlHelper.ViewContext);
该方法的代码如下:
private static RouteData CreateRouteData(RouteBase route, RouteValueDictionary routeValues, RouteValueDictionary dataTokens, ViewContext parentViewContext) {
RouteData routeData = new RouteData();
foreach (KeyValuePair<string, object> kvp in routeValues) {
routeData.Values.Add(kvp.Key, kvp.Value);
}
foreach (KeyValuePair<string, object> kvp in dataTokens) {
routeData.DataTokens.Add(kvp.Key, kvp.Value);
}
routeData.Route = route;
routeData.DataTokens[ControllerContext.PARENT_ACTION_VIEWCONTEXT] = parentViewContext;
return routeData;
}
我想看到这里大家都应该明白了吧,TempData也可次访问。应该是说MVC在请求周期结束的时候有动作去删除此类的Session,而不是访问一次就被删除。MS命名为TempData,意思应该是说TempData是个Session,但是它又和普通的Session不同。它会在请求之后被删除,所以是临时的Data
在ControllerBase的Execute方法很简单
VerifyExecuteCalledOnce(); // 确保一个controller实例只调用一次,
Initialize(requestContext);//初始化 ControllerContext = new ControllerContext(requestContext, this);
using (ScopeStorage.CreateTransientScope()) {
ExecuteCore();//这个才是真正的执行
}
本系列文章主要是分析源代码,分析里面的逻辑和实现细节,所以我们还是来看看VerifyExecuteCalledOnce这个方法吧。
internal void VerifyExecuteCalledOnce() {
if (!_executeWasCalledGate.TryEnter()) {
string message = String.Format(CultureInfo.CurrentCulture, MvcResources.ControllerBase_CannotHandleMultipleRequests, GetType());
throw new InvalidOperationException(message);
}
}
internal sealed class SingleEntryGate {
private const int NOT_ENTERED = 0;
private const int ENTERED = 1;
private int _status;
// returns true if this is the first call to TryEnter(), false otherwise
public bool TryEnter() {
int oldStatus = Interlocked.Exchange(ref _status, ENTERED);
return (oldStatus == NOT_ENTERED);
}
}
当大家 看了TryEnter方法以后是不是觉得他们实现的很巧妙啊。保证一个类的一个实例方法只执行一次的一种实现方式。
而ExecuteCore这个方法在抽象类Controller中实现,Controller是ControllerBase的子类,
protected override void ExecuteCore() {
PossiblyLoadTempData();
try {
string actionName = RouteData.GetRequiredString("action");
if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {
HandleUnknownAction(actionName);
}
}
finally {
PossiblySaveTempData();
}
}
其中 ActionInvoker.InvokeAction(ControllerContext, actionName)是真正的调用Action,我们放到后面来讲,这节我们来看看PossiblyLoadTempData、PossiblySaveTempData这个2个方法。在每次action调用前加载,调用后保存。
internal void PossiblyLoadTempData() {
if (!ControllerContext.IsChildAction) {
TempData.Load(ControllerContext, TempDataProvider);
}
}
internal void PossiblySaveTempData() {
if (!ControllerContext.IsChildAction) {
TempData.Save(ControllerContext, TempDataProvider);
}
}
这 2个方法实现是不特别简单啊,那么TempData属性实现是否简单了?
public TempDataDictionary TempData {
get {
if (ControllerContext != null && ControllerContext.IsChildAction) {
return ControllerContext.ParentActionViewContext.TempData;
}
if (_tempDataDictionary == null) {
_tempDataDictionary = new TempDataDictionary();
}
return _tempDataDictionary;
}
set {
_tempDataDictionary = value;
}
}
这里 需要注意一下的是如果当前Action是一个子Action则返回父辈Action的Controller的TempData。
一提到 TempData ,我们还知道ViewData、ViewBag也是保存数据的,它们之间有何区别了?
TempData 是TempDataDictionary类的实例 public class TempDataDictionary : IDictionary<string, object>
ViewData是ViewDataDictionary类的实例 public class ViewDataDictionary : IDictionary<string, object>
ViewBag是DynamicViewDataDictionary类的实例 internal sealed class DynamicViewDataDictionary : DynamicObject
一般 对它们的区别网上都是如下的内容:
TempData:保存在Session中,Controller每次执行请求的时候,会从Session中先获取TempData,而后清除Session,获取完TempData数据,虽然保存在内部字典对象中,但是其集合中的每个条目访问一次后就从字典表中删除。具体代码层面,TempData获取过程是通过SessionStateTempDataProvider.LoadTempData方法从ControllerContext的Session中读取数据,而后清除Session,故TempData只能跨Controller传递一次。
ViewData:生命周期和View相同,仅对当前View有效。
ViewBag:和ViewData生命周期相同,也是对但前View有效,不同的是ViewBag的类型不再是字典的键值对结构,而是dynamic动态类型,属于MVC3里面新增的部分。
这里的TempData解释是对的吗?
我们 这里主要讲讲TempData,其他2个很简单,TempDataDictionary类主要代码如下:
public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider) { IDictionary<string, object> providerDictionary = tempDataProvider.LoadTempData(controllerContext); _data = (providerDictionary != null) ? new Dictionary<string, object>(providerDictionary, StringComparer.OrdinalIgnoreCase) : new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); _initialKeys = new HashSet<string>(_data.Keys, StringComparer.OrdinalIgnoreCase); _retainedKeys.Clear(); } public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider) { string[] keysToKeep = _initialKeys.Union(_retainedKeys, StringComparer.OrdinalIgnoreCase).ToArray(); string[] keysToRemove = _data.Keys.Except(keysToKeep, StringComparer.OrdinalIgnoreCase).ToArray(); foreach (string key in keysToRemove) { _data.Remove(key); } tempDataProvider.SaveTempData(controllerContext, _data); } public object this[string key] { get { object value; if (TryGetValue(key, out value)) { _initialKeys.Remove(key); return value; } return null; } set { _data[key] = value; _initialKeys.Add(key); } } public void Add(string key, object value) { _data.Add(key, value); _initialKeys.Add(key); }
仔细看以上代码,我们会发现Load只是初始化一个默认的字典,没什么特别的,而Save就有所特别,它在每次保存的时候都移除此次添加的key,说白了又回到初始状态了。反正我是没明白微软为什么要这个做。不保存不就行了吗?
在来让我们看看TempDataProvider
public ITempDataProvider TempDataProvider {
get {
if (_tempDataProvider == null) {
_tempDataProvider = CreateTempDataProvider();
}
return _tempDataProvider;
}
set {
_tempDataProvider = value;
}
}
protected virtual ITempDataProvider CreateTempDataProvider() {
return new SessionStateTempDataProvider();
}
微软代码就这样,看看这 如果我们要设置自己的TempDataProvider 可以通过TempDataProvider 属性来设置,也可以通过重写CreateTempDataProvider方法来实现,总是提供给用户多个选择。
我们还是来看看默认的SessionStateTempDataProvider
public class SessionStateTempDataProvider : ITempDataProvider { internal const string TempDataSessionStateKey = "__ControllerTempData"; public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext) { HttpSessionStateBase session = controllerContext.HttpContext.Session; if (session != null) { Dictionary<string, object> tempDataDictionary = session[TempDataSessionStateKey] as Dictionary<string, object>; if (tempDataDictionary != null) { // If we got it from Session, remove it so that no other request gets it session.Remove(TempDataSessionStateKey); return tempDataDictionary; } } return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); } public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } HttpSessionStateBase session = controllerContext.HttpContext.Session; bool isDirty = (values != null && values.Count > 0); if (session == null) { if (isDirty) { throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled); } } else { if (isDirty) { session[TempDataSessionStateKey] = values; } else { // Since the default implementation of Remove() (from SessionStateItemCollection) dirties the // collection, we shouldn't call it unless we really do need to remove the existing key. if (session[TempDataSessionStateKey] != null) { session.Remove(TempDataSessionStateKey); } } } } }
先说LoadTempData方法吧,第一次访问tempDataDictionary应该是空的没有任何数据,直接new一个字典。然后就是SaveTempData了,按照前面的理解,这个时候的字典应该没有数据了,一旦有它就是脏数据。
bool isDirty = (values != null && values.Count > 0);
所以 真正的保存执行代码是
if (session[TempDataSessionStateKey] != null) {
session.Remove(TempDataSessionStateKey);
}
移除 session,以至于第二次又重新实例一个字典。但是有一种情况很特殊也是经常遇到的,也是TempData存在的原因。
我们以一段代码来说明吧:
为什么了会这样了,原因很简单,虽然我们调用@{Html.RenderAction("Index","Test");}时候会去执行 PossiblyLoadTempData()、 PossiblySaveTempData()这2个方法,但是他们有一个过滤条件 if (!ControllerContext.IsChildAction) {} 这个条件不满足,所以实际上就没有调用TempData.Load和TempData.Save方法。IsChildAction这个属性究竟是怎么定义的了。
public virtual bool IsChildAction {
get {
RouteData routeData = RouteData;
if (routeData == null) {
return false;
}
return routeData.DataTokens.ContainsKey(PARENT_ACTION_VIEWCONTEXT);
}
}
而RenderAction实际上市调用
internal static void ActionHelper(HtmlHelper htmlHelper, string actionName, string controllerName, RouteValueDictionary routeValues, TextWriter textWriter)
各参数如下:
actionName:Index
controllerName:Test
routeValues:null
textWriter:htmlHelper.ViewContext.Writer
在这个方法里面有一句 RouteData routeData = CreateRouteData(vpd.Route, routeValues, vpd.DataTokens, htmlHelper.ViewContext);
该方法的代码如下:
private static RouteData CreateRouteData(RouteBase route, RouteValueDictionary routeValues, RouteValueDictionary dataTokens, ViewContext parentViewContext) {
RouteData routeData = new RouteData();
foreach (KeyValuePair<string, object> kvp in routeValues) {
routeData.Values.Add(kvp.Key, kvp.Value);
}
foreach (KeyValuePair<string, object> kvp in dataTokens) {
routeData.DataTokens.Add(kvp.Key, kvp.Value);
}
routeData.Route = route;
routeData.DataTokens[ControllerContext.PARENT_ACTION_VIEWCONTEXT] = parentViewContext;
return routeData;
}
我想看到这里大家都应该明白了吧,TempData也可次访问。应该是说MVC在请求周期结束的时候有动作去删除此类的Session,而不是访问一次就被删除。MS命名为TempData,意思应该是说TempData是个Session,但是它又和普通的Session不同。它会在请求之后被删除,所以是临时的Data
相关文章推荐
- asp.net mvc源码分析-Controllerl篇 TempData数据存储
- asp.net mvc 之旅 —— 第五站 从源码中分析asp.net mvc 中的TempData
- asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证
- asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证
- asp.net mvc源码分析-Controllerl篇 如何创建Controller实例
- ASP.NET MVC 跨页数据呈现(TempData)
- asp.net mvc源码分析-Route的GetRouteData
- asp.net mvc源码分析-Route的GetRouteData
- asp.net mvc源码分析-Controllerl篇 ControllerDescriptor
- 关于asp.net mvc中的TempData对象跨视图传递数据
- asp.net mvc源码分析-Controllerl篇 如何创建Controller实例
- asp.net mvc源码分析-Controllerl篇 ControllerDescriptor
- asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证
- ASP.NET MVC 3中ViewBag, ViewData和 TempData 推荐
- Asp.net MVC源码分析--Asp.net MVC与Ninject.MVC集成分析
- Asp.net MVC 示例项目"Suteki.Shop"分析之---数据验证
- ASP.NET MVC 入门6、TempData
- asp.net mvc源码分析-BeginForm方法 和ClientValidationEnabled 属性
- ASP.NET MVC 使用TempData
- ASP.NET MVC 源码分析——巧用Aggregate和委托构造递归链