MVC 3 TempData深入研究(跳转Action中没有取TempData的思考)

来源:互联网 发布:多玩魔兽盒子mac版 编辑:程序博客网 时间:2024/06/01 23:42
 

写这篇东西源于一个问题:

问题描述

在一个Action中加入TempData["message"] = this.dialog.GetValue("NoLogin"),转到另一个Action时没有取到TempData["message"] 值。

[csharp] view plaincopyprint?
  1. [RolesFilterAttribute]  
[csharp] view plaincopyprint?
  1. public ActionResult ModifyUser()  
  2.  {  
  3.      AccountInfo userInfo = ViewData["FilterUserInfo"as AccountInfo;  
  4.   
  5.      if (userInfo != null)  
  6.      {  ............................  


在需要的方法上贴触发器:

[csharp] view plaincopyprint?
  1.     /// <summary>   
  2.        /// 用户查询页面(Get)   
  3.        /// </summary>   
  4.        /// <returns></returns>   
  5.        [<span style="color:#333333;">RolesFilterAttribute</span>]  
[csharp] view plaincopyprint?
  1.         [HttpGet]  
  2.         public ActionResult SearchUser()  
  3.         {  
  4.             AccountInfo accountInfo = ViewData["FilterUserInfo"as AccountInfo;  
  5.             Result<SearchResult<List<AccountInfo>>, string> result;  
  6.   
  7.             if (accountInfo == null)  
  8.             {  
  9.   
  10.                 TempData["message"] = this.dialog.GetValue("NoLogin");  
  11.   
  12.                <strong><span style="color:#ff0000;"return RedirectToAction("Error""Common");</span></strong>  
  13.             }  
  14.   
  15.          
  16. ............................其他代码省略                
[csharp] view plaincopyprint?
  1. }  

说明:RolesFilterAttribute 实现接口IAuthorizationFilter ;下面是简单实现代码:

[csharp] view plaincopyprint?
  1. #region IAuthorizationFilter 成员   
  2.   
  3.     void IAuthorizationFilter.OnAuthorization(AuthorizationContext filterContext)  
  4.     {  
  5.         if (filterContext == null)  
  6.         {  
  7.             throw new ArgumentNullException("filterContext");  
  8.         }  
  9.         if (!this.AuthorizeCore(filterContext))  
  10.         {  
  11.             filterContext.Result = new ViewResult() { ViewName = "Error"};  
  12.         }  
  13.     }  
  14.   
  15.     private bool AuthorizeCore(AuthorizationContext filterContext)  
  16.     {  
  17.         AccountInfo account = userSystem.CheckLogin();  
  18.   
  19.         if (account != null)  
  20.         {  
  21.             if ((account.Power & Role) > 0)  
  22.             {  
  23.                 return true;  
  24.             }  
  25.               
  26.             return false;  
  27.         }  
  28.        return false;  
  29.     }  
  30.     #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存放的数据就失效了”。

 

看个例子:

[html] view plaincopyprint?
  1. public class HomeController : Controller  
  2.   {  
  3.       public ActionResult Index()  
  4.       {  
  5.           TempData["D"] = "WT";  
  6.           return Redirect("Index1");  
  7.       }  
  8.   
  9.       public ActionResult Index1()  
  10.       {  
  11.           return Redirect("Index2");  
  12.       }  
  13.   
  14.       public ActionResult Index2()  
  15.       {  
  16.           return Redirect("Index3");  
  17.       }  
  18.   
  19.   
  20.       public ActionResult Index3()  
  21.       {  
  22.           ContentResult result = new ContentResult();  
  23.   
  24.           result.Content = TempData["D"].ToString();  
  25.   
  26.           return result;  
  27.       }  
  28.   }  
[csharp] view plaincopyprint?
  1.    
输入 Home/Index,此时发现页面已经跳转到:Home/Index3,且输出“WT”。似乎说明:说明TempData会保留未使用的值,并不是说"TempData只存放一次数据,到第三个Action时,第一个Action存放的数据就失效了"。
 
实际上,这还是没有能解决我上面说的问题,上面的例子只是说明我们这前的理解是有问题的。 有兴趣就跟我研究一下源码吧。

直奔主题--TempDataDictionary与ITempDataProvider

 
一个一个来呗:
     Controller-->ControllerBase-->TempData-->TempDataDictionary;()
     里面几个重要的方法:
     
[csharp] view plaincopyprint?
  1.        // 保存当前真实数据的字段   
  2.         private Dictionary<stringobject> _data;  
  3.         // 保存初始数据字段,添加操作会在此字段进行  
  4.         private HashSet<string> _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);  
  5.         // 保存保留的字段   
  6.         private HashSet<string> _retainedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);  
[csharp] view plaincopyprint?
  1.    
[csharp] view plaincopyprint?
  1.    
[csharp] view plaincopyprint?
  1. <p>     /// <summary>   
  2.         ///  将所有真实数据保存进保留字段中   
  3.         /// </summary>   
  4.         public void Keep() {  
  5.             _retainedKeys.Clear();  
  6.             _retainedKeys.UnionWith(_data.Keys);  
  7.         }</p><p>        /// <summary>  
  8.         ///  将特定键保存进保留字段中   
  9.         /// </summary>   
  10.         public void Keep(string key) {  
  11.             _retainedKeys.Add(key);  
  12.         }</p><p> </p><p>     /// <summary>  
  13.         /// Load 数据   
  14.         /// 注意:在控制器方法执行前执行   
  15.         /// </summary>   
  16.         /// <param name="controllerContext"></param>  
  17.         /// <param name="tempDataProvider"></param>  
  18.         public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider) {  
  19.             IDictionary<stringobject> providerDictionary = tempDataProvider.LoadTempData(controllerContext);  
  20.             _data = (providerDictionary != null) ? new Dictionary<stringobject>(providerDictionary, StringComparer.OrdinalIgnoreCase) :  
  21.                 new Dictionary<stringobject>(StringComparer.OrdinalIgnoreCase);  
  22.             _initialKeys = new HashSet<string>(_data.Keys, StringComparer.OrdinalIgnoreCase);  
  23.             _retainedKeys.Clear();  
  24.         }</p><p>        public object Peek(string key) {  
  25.             object value;  
  26.             _data.TryGetValue(key, out value);  
  27.             return value;  
  28.         }</p><p>        /// <summary>   
  29.         /// 保存数据(默认保存进Session中)  
  30.         /// 注意:将未使用过的值保存进Session中   
  31.         /// </summary>   
  32.         /// <param name="controllerContext"></param>  
  33.         /// <param name="tempDataProvider"></param>  
  34.         public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider) {  
  35.             string[] keysToKeep = _initialKeys.Union(_retainedKeys, StringComparer.OrdinalIgnoreCase).ToArray();  
  36.             string[] keysToRemove = _data.Keys.Except(keysToKeep, StringComparer.OrdinalIgnoreCase).ToArray();  
  37.             foreach (string key in keysToRemove) {  
  38.                 _data.Remove(key);  
  39.             }  
  40.             tempDataProvider.SaveTempData(controllerContext, _data);  
  41.         }  
  42. </p><p> </p><p>     /// <summary>   
  43.         /// 索引器   
  44.         /// 注意:Get:会在初始字段中,移除掉已经使用过的值   
  45.         ///    Set:会在初始字段中,加入新增的值。  
  46.         /// </summary>   
  47.         /// <param name="key"></param>  
  48.         /// <returns></returns>   
  49.         public object this[string key] {  
  50.             get {  
  51.                 object value;  
  52.                 if (TryGetValue(key, out value)) {  
  53.                     _initialKeys.Remove(key);  
  54.                     return value;  
  55.                 }  
  56.                 return null;  
  57.             }  
  58.             set {  
  59.                 _data[key] = value;  
  60.                 _initialKeys.Add(key);  
  61.             }  
  62.         }</p>  
可以简单总结一下:(MVC默认实现)
 旧数据(_retainedKeys )新数据(_initialKeys )与存储地方同步的数据(_data)方法使用位置

索引器Get;

不变移除key不变Controller.TempData

索引器Set;

Add 方法;

不变添加key添加keyController.TempDataLoad 方法清空

将_data的值(来源于存储位置,如Session),保留于此

是来源Action的Controller.TempData传过来的。

从来源Action的Controller.TempData

(数据是存在Session中),导入值到_data

控制器方法被触发前Save 方法不变不变筛选出未使用的值,存入Session。控制器方法被触发后
Controller-->ITempDataProvider ;
[csharp] view plaincopyprint?
  1. public interface ITempDataProvider {  
  2.        IDictionary<stringobject> LoadTempData(ControllerContext controllerContext);  
  3.        void SaveTempData(ControllerContext controllerContext, IDictionary<stringobject> values);  
  4.    }  
这两个方法是LoadTempData和SaveTempData,我们猜想这两个方法是用来取得TempData容器和保存TempData数据的,因为LoadTempData返回一个IDictionary类型,而SaveTempData没有返回类型,而参数ControllerContext就是针对不同的用户上下文来设计的,标明是对那一个上下文的TempData进行操作。这两个方法是LoadTempData和SaveTempData,我们猜想这两个方法是用来取得TempData容器和保存TempData数据的,因为LoadTempData返回一个IDictionary类型,而SaveTempData没有返回类型,而参数ControllerContext就是针对不同的用户上下文来设计的,标明是对那一个上下文的TempData进行操作。
这两个方法是LoadTempData和SaveTempData,用来取得TempData容器和保存TempData数据的。
而参数ControllerContext就是针对不同的用户上下文来设计的,标明是对那一个上下文的TempData进行操作。
 
好了,我们来看一下一个方法执行前后,TempData发生了什么变化(当然,想到是在Controller里面执行了):
[csharp] view plaincopyprint?
  1. protected override void ExecuteCore() {  
  2.     // If code in this method needs to be updated, please also check the BeginExecuteCore() and  
  3.     // EndExecuteCore() methods of AsyncController to see if that code also must be updated.  
  4.   
  5.     PossiblyLoadTempData();  
  6.     try {  
  7.         string actionName = RouteData.GetRequiredString("action");  
  8.         if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {  
  9.             HandleUnknownAction(actionName);  
  10.         }  
  11.     }  
  12.     finally {  
  13.         PossiblySaveTempData();  
  14.     }  
  15. }  
 
再深入一点:
PossiblyLoadTempData 关键: TempData.Load(ControllerContext, TempDataProvider);
PossiblySaveTempData 关键: TempData.Save(ControllerContext, TempDataProvider);
您会发现:TempData.LoadTempData.Save就是 上述表格列出的方法。这是重点:我们知道了这两个方法执行的时机:一个在方法被InvokeAction前,一个在后,如表格所述。
假如您细心,你会发现:表格中的列出的 旧数据值(retainedKeys) 好像没有改变?如果真不改变,上一个方法中设置的TempData怎么获取到的的?找了很久,发现:
1.如果父ViewContext.TempData有值,将值保存进当前TempData
[csharp] view plaincopyprint?
  1. [SuppressMessage("Microsoft.Usage""CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This property is settable so that unit tests can provide mock implementations.")]  
  2.       public TempDataDictionary TempData {  
  3.           get {  
  4.               if (ControllerContext != null && ControllerContext.IsChildAction) {  
  5.                   return ControllerContext.ParentActionViewContext.TempData;  
  6.               }  
  7.               if (_tempDataDictionary == null) {  
  8.                   _tempDataDictionary = new TempDataDictionary();  
  9.               }  
  10.               return _tempDataDictionary;  
  11.           }  
  12.           set {  
  13.               _tempDataDictionary = value;  
  14.           }  
  15.       }  
 
2. TempData调用Keep()就可以拿上父TempData 传过来的值了。
[csharp] view plaincopyprint?
  1. public override void ExecuteResult(ControllerContext context) {  
  2.           if (context == null) {  
  3.               throw new ArgumentNullException("context");  
  4.           }  
  5.           if (context.IsChildAction) {  
  6.               throw new InvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildAction);  
  7.           }  
  8.   
  9.           string destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);  
  10.           context.Controller.TempData.Keep();  
  11.   
  12.           if (Permanent) {  
  13.               context.HttpContext.Response.RedirectPermanent(destinationUrl, endResponse: false);  
  14.           }  
  15.           else {  
  16.               context.HttpContext.Response.Redirect(destinationUrl, endResponse: false);  
  17.           }  
  18.       }  
 
   这是在 public class RedirectResult : ActionResult 中的,是在一个Action的执行体。哦,原来是在方法执行的时候,将上一次的TempData值存入旧数据值(retainedKeys)的。查一下代码,您将看见:
2.
[html] view plaincopyprint?
  1.     /// <summary>  
  2.        ///  将所有真实数据保存进保留字段中  
  3.        /// </summary>  
  4.        public void Keep() {  
  5.            _retainedKeys.Clear();  
  6.            _retainedKeys.UnionWith(_data.Keys);  
  7.        }  
 _data是从请求中取出的值。
 
   整个连起来一想,我上面的问题也得到了解决:RolesFilterAttribute , 当用户没有通过验证时,就不会进行方法执行体(Controller.ExecuteResult),也就不会取得上一次Action中的数据,所以此时没办法获取TempData中的值,因为里面根本就没有值。

总体说来:

  ITempDataProvider只是一个提供临时数据存取的一个约定的接口,它并不提供如何管理“新旧”数据,TempDataDictionary类才是真正管理“新旧”数据的管理者,但是这个“管理者”需要一个存取“新旧”数据的途径,也就是说它告诉ITempDataProvider该存什么该取什么,然后由ITempDataProvider真正的去执行存取操作。
  在Controller执行中可以加入新的值到TempData中,Action结束之后它还要把没有使用过的数据给存起来。而Controller恰似这么一个“指挥者”,它把一个能做ITempDataProvider事情的类——SessionStateTempDataProvider交给TempDataProvider使用。
 
关系图如下:
 
完。
原创粉丝点击