写这篇东西源于一个问题:
问题描述
在一个Action中加入TempData["message"] = this.dialog.GetValue("NoLogin"),转到另一个Action时没有取到TempData["message"] 值。
复制代码1
2[RolesFilterAttribute]
复制代码1
2
3
4
5
6public ActionResult ModifyUser() { AccountInfo userInfo = ViewData["FilterUserInfo"] as AccountInfo; if (userInfo != null) { ............................
在需要的方法上贴触发器:复制代码1
2
3
4
5/// <summary> /// 用户查询页面(Get) /// </summary> /// <returns></returns> [RolesFilterAttribute]
复制代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16[HttpGet] public ActionResult SearchUser() { AccountInfo accountInfo = ViewData["FilterUserInfo"] as AccountInfo; Result<SearchResult<List<AccountInfo>>, string> result; if (accountInfo == null) { TempData["message"] = this.dialog.GetValue("NoLogin"); return RedirectToAction("Error", "Common"); } ............................其他代码省略
复制代码1
2
3} }
说明:RolesFilterAttribute 实现接口IAuthorizationFilter ;下面是简单实现代码:
复制代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30#region IAuthorizationFilter 成员 void IAuthorizationFilter.OnAuthorization(AuthorizationContext filterContext) { if (filterContext == null) { throw new ArgumentNullException("filterContext"); } if (!this.AuthorizeCore(filterContext)) { filterContext.Result = new ViewResult() { ViewName = "Error"}; } } private bool AuthorizeCore(AuthorizationContext filterContext) { AccountInfo account = userSystem.CheckLogin(); if (account != null) { if ((account.Power & Role) > 0) { return true; } return false; } return false; } #endregion
出现问题:TempData["message"] = this.dialog.GetValue("NoLogin"); 没有提示信息没有到达错误页面。这是为什么呢?我们知道:TempData只存放一次数据,到第三个Action时,第一个Action存放的数据就失效了(TempData的特性就是可以在两个Action之间传递数据,它会保存一份数据到下一个Action,并随着再下一个Action的到来而失效)。现在只是从SeachUser -->Error 方法,应该是能把TempData中的值传过去的啊?
MVC3 TempData 机制
于是去理了一下MVC 3 关于TempData的源码,也查看一点其他文章,有所斩获:我先前的理解是错误的--“TempData只存放一次数据,到第三个Action时,第一个Action存放的数据就失效了”。
看个例子:
复制代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28public class HomeController : Controller { public ActionResult Index() { TempData["D"] = "WT"; return Redirect("Index1"); } public ActionResult Index1() { return Redirect("Index2"); } public ActionResult Index2() { return Redirect("Index3"); } public ActionResult Index3() { ContentResult result = new ContentResult(); result.Content = TempData["D"].ToString(); return result; } }
复制代码1
复制代码1输入 Home/Index,此时发现页面已经跳转到:Home/Index3,且输出“WT”。似乎说明:说明TempData会保留未使用的值,并不是说"TempData只存放一次数据,到第三个Action时,第一个Action存放的数据就失效了"。复制代码1复制代码1实际上,这还是没有能解决我上面说的问题,上面的例子只是说明我们这前的理解是有问题的。 有兴趣就跟我研究一下源码吧。直奔主题--TempDataDictionary与ITempDataProvider
复制代码1复制代码1一个一个来呗:复制代码1Controller-->ControllerBase-->TempData-->TempDataDictionary;()复制代码1里面几个重要的方法:复制代码1复制代码1
2
3
4
5
6// 保存当前真实数据的字段 private Dictionary<string, object> _data; // 保存初始数据字段,添加操作会在此字段进行 private HashSet<string> _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase); // 保存保留的字段 private HashSet<string> _retainedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
复制代码1
复制代码1
复制代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/// <summary> /// 将所有真实数据保存进保留字段中 /// </summary> public void Keep() { _retainedKeys.Clear(); _retainedKeys.UnionWith(_data.Keys); }
/// <summary> /// 将特定键保存进保留字段中 /// </summary> public void Keep(string key) { _retainedKeys.Add(key); }
/// <summary> /// Load 数据 /// 注意:在控制器方法执行前执行 /// </summary> /// <param name="controllerContext"></param> /// <param name="tempDataProvider"></param> 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 object Peek(string key) { object value; _data.TryGetValue(key, out value); return value; }
/// <summary> /// 保存数据(默认保存进Session中) /// 注意:将未使用过的值保存进Session中 /// </summary> /// <param name="controllerContext"></param> /// <param name="tempDataProvider"></param> 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); }
/// <summary> /// 索引器 /// 注意:Get:会在初始字段中,移除掉已经使用过的值 /// Set:会在初始字段中,加入新增的值。 /// </summary> /// <param name="key"></param> /// <returns></returns> 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); } }
复制代码1可以简单总结一下:(MVC默认实现)复制代码1
2
3
旧数据(_retainedKeys ) 新数据(_initialKeys ) 与存储地方同步的数据(_data) 方法使用位置 索引器Get;
不变 移除key 不变 Controller.TempData 索引器Set;
Add 方法;
不变 添加key 添加key Controller.TempData Load 方法 清空 将_data的值(来源于存储位置,如Session),保留于此
是来源Action的Controller.TempData传过来的。
从来源Action的Controller.TempData
(数据是存在Session中),导入值到_data
控制器方法被触发前 Save 方法 不变 不变 筛选出未使用的值,存入Session。 控制器方法被触发后 复制代码1Controller-->ITempDataProvider ;复制代码1
2
3
4public interface ITempDataProvider { IDictionary<string, object> LoadTempData(ControllerContext controllerContext); void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values); }
复制代码1这两个方法是LoadTempData和SaveTempData,我们猜想这两个方法是用来取得TempData容器和保存TempData数据的,因为LoadTempData返回一个IDictionary类型,而SaveTempData没有返回类型,而参数ControllerContext就是针对不同的用户上下文来设计的,标明是对那一个上下文的TempData进行操作。这两个方法是LoadTempData和SaveTempData,我们猜想这两个方法是用来取得TempData容器和保存TempData数据的,因为LoadTempData返回一个IDictionary类型,而SaveTempData没有返回类型,而参数ControllerContext就是针对不同的用户上下文来设计的,标明是对那一个上下文的TempData进行操作。复制代码1这两个方法是LoadTempData和SaveTempData,用来取得TempData容器和保存TempData数据的。复制代码1而参数ControllerContext就是针对不同的用户上下文来设计的,标明是对那一个上下文的TempData进行操作。复制代码1复制代码1好了,我们来看一下一个方法执行前后,TempData发生了什么变化(当然,想到是在Controller里面执行了):复制代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15protected override void ExecuteCore() { // If code in this method needs to be updated, please also check the BeginExecuteCore() and // EndExecuteCore() methods of AsyncController to see if that code also must be updated. PossiblyLoadTempData(); try { string actionName = RouteData.GetRequiredString("action"); if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) { HandleUnknownAction(actionName); } } finally { PossiblySaveTempData(); } }
复制代码1复制代码1再深入一点:复制代码1PossiblyLoadTempData 关键: TempData.Load(ControllerContext, TempDataProvider);复制代码1PossiblySaveTempData 关键: TempData.Save(ControllerContext, TempDataProvider);复制代码1您会发现:TempData.Load和TempData.Save就是 上述表格列出的方法。这是重点:我们知道了这两个方法执行的时机:一个在方法被InvokeAction前,一个在后,如表格所述。复制代码1假如您细心,你会发现:表格中的列出的 旧数据值(retainedKeys) 好像没有改变?如果真不改变,上一个方法中设置的TempData怎么获取到的的?找了很久,发现:复制代码11.如果父ViewContext.TempData有值,将值保存进当前TempData复制代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This property is settable so that unit tests can provide mock implementations.")] public TempDataDictionary TempData { get { if (ControllerContext != null && ControllerContext.IsChildAction) { return ControllerContext.ParentActionViewContext.TempData; } if (_tempDataDictionary == null) { _tempDataDictionary = new TempDataDictionary(); } return _tempDataDictionary; } set { _tempDataDictionary = value; } }
复制代码1复制代码12. TempData调用Keep()就可以拿上父TempData 传过来的值了。复制代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } if (context.IsChildAction) { throw new InvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildAction); } string destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext); context.Controller.TempData.Keep(); if (Permanent) { context.HttpContext.Response.RedirectPermanent(destinationUrl, endResponse: false); } else { context.HttpContext.Response.Redirect(destinationUrl, endResponse: false); } }
复制代码1复制代码1这是在 public class RedirectResult : ActionResult 中的,是在一个Action的执行体。哦,原来是在方法执行的时候,将上一次的TempData值存入旧数据值(retainedKeys)的。查一下代码,您将看见:复制代码12.复制代码1
2
3
4
5
6
7/// <summary> /// 将所有真实数据保存进保留字段中 /// </summary> public void Keep() { _retainedKeys.Clear(); _retainedKeys.UnionWith(_data.Keys); }
复制代码1_data是从请求中取出的值。复制代码1复制代码1整个连起来一想,我上面的问题也得到了解决:RolesFilterAttribute , 当用户没有通过验证时,就不会进行方法执行体(Controller.ExecuteResult),也就不会取得上一次Action中的数据,所以此时没办法获取TempData中的值,因为里面根本就没有值。总体说来:
复制代码1ITempDataProvider只是一个提供临时数据存取的一个约定的接口,它并不提供如何管理“新旧”数据,TempDataDictionary类才是真正管理“新旧”数据的管理者,但是这个“管理者”需要一个存取“新旧”数据的途径,也就是说它告诉ITempDataProvider该存什么该取什么,然后由ITempDataProvider真正的去执行存取操作。复制代码1在Controller,执行中可以加入新的值到TempData中,Action结束之后它还要把没有使用过的数据给存起来。而Controller恰似这么一个“指挥者”,它把一个能做ITempDataProvider事情的类——SessionStateTempDataProvider交给TempDataProvider使用。复制代码1复制代码1关系图如下:复制代码1复制代码1复制代码1完。复制代码1复制代码1复制代码1复制代码1复制代码1复制代码1复制代码1复制代码1复制代码1
最后
以上就是糊涂耳机最近收集整理的关于MVC 3 TempData深入研究(跳转Action中没有取TempData的思考)的全部内容,更多相关MVC内容请搜索靠谱客的其他文章。
发表评论 取消回复