1. 程式人生 > >MVC 3 TempData深入研究(跳轉Action中沒有取TempData的思考)

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

 

寫這篇東西源於一個問題:

問題描述

在一個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. if (userInfo != null)  
  5.      {  ............................  


在需要的方法上貼觸發器:

[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. if (accountInfo == null)  
  7.             {  
  8.                 TempData["message"] = this.dialog.GetValue("NoLogin");  
  9.                <strong><span style="color:#ff0000;"return RedirectToAction("Error""Common");</span></strong>  
  10.             }  
  11. ............................其他程式碼省略                
[csharp] view plaincopyprint?
  1. }  

說明:RolesFilterAttribute 實現介面IAuthorizationFilter ;下面是簡單實現程式碼:

[csharp] view plaincopyprint?
  1. #region IAuthorizationFilter 成員
  2. void IAuthorizationFilter.OnAuthorization(AuthorizationContext filterContext)  
  3.     {  
  4. if (filterContext == null)  
  5.         {  
  6. thrownew ArgumentNullException("filterContext");  
  7.         }  
  8. if (!this.AuthorizeCore(filterContext))  
  9.         {  
  10.             filterContext.Result = new ViewResult() { ViewName = "Error"};  
  11.         }  
  12.     }  
  13. privatebool AuthorizeCore(AuthorizationContext filterContext)  
  14.     {  
  15.         AccountInfo account = userSystem.CheckLogin();  
  16. if (account != null)  
  17.         {  
  18. if ((account.Power & Role) > 0)  
  19.             {  
  20. returntrue;  
  21.             }  
  22. returnfalse;  
  23.         }  
  24. returnfalse;  
  25.     }  
  26.     #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.       public ActionResult Index1()  
  9.       {  
  10.           return Redirect("Index2");  
  11.       }  
  12.       public ActionResult Index2()  
  13.       {  
  14.           return Redirect("Index3");  
  15.       }  
  16.       public ActionResult Index3()  
  17.       {  
  18.           ContentResult result = new ContentResult();  
  19. result.Content = TempData["D"].ToString();  
  20.           return result;  
  21.       }  
  22.   }  
[csharp] view plaincopyprint?
輸入 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? [csharp] view plaincopyprint? [csharp] view plaincopyprint?
  1. <p>     /// <summary>
  2. ///  將所有真實資料儲存進保留欄位中
  3. /// </summary>
  4. publicvoid Keep() {  
  5.             _retainedKeys.Clear();  
  6.             _retainedKeys.UnionWith(_data.Keys);  
  7.         }</p><p>        /// <summary>
  8. ///  將特定鍵儲存進保留欄位中
  9. /// </summary>
  10. publicvoid 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. publicvoid 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>        publicobject 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. publicvoid 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. publicobjectthis[string key] {  
  50. get {  
  51. object value;  
  52. if (TryGetValue(key, out value)) {  
  53.                     _initialKeys.Remove(key);  
  54. return value;  
  55.                 }  
  56. returnnull;  
  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.TempData
Load 方法清空

將_data的值(來源於儲存位置,如Session),保留於此

是來源Action的Controller.TempData傳過來的。

從來源Action的Controller.TempData

(資料是存在Session中),匯入值到_data

控制器方法被觸發前
Save 方法不變不變篩選出未使用的值,存入Session。控制器方法被觸發後
Controller-->ITempDataProvider ;
[csharp] view plaincopyprint?
  1. publicinterface 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. protectedoverridevoid 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.     PossiblyLoadTempData();  
  5. try {  
  6. string actionName = RouteData.GetRequiredString("action");  
  7. if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) {  
  8.             HandleUnknownAction(actionName);  
  9.         }  
  10.     }  
  11. finally {  
  12.         PossiblySaveTempData();  
  13.     }  
  14. }  
再深入一點:
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. publicoverridevoid ExecuteResult(ControllerContext context) {  
  2. if (context == null) {  
  3. thrownew ArgumentNullException("context");  
  4.           }  
  5. if (context.IsChildAction) {  
  6. thrownew InvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildAction);  
  7.           }  
  8. string destinationUrl = UrlHelper.GenerateContentUrl(Url, context.HttpContext);  
  9.           context.Controller.TempData.Keep();  
  10. if (Permanent) {  
  11.               context.HttpContext.Response.RedirectPermanent(destinationUrl, endResponse: false);  
  12.           }  
  13. else {  
  14.               context.HttpContext.Response.Redirect(destinationUrl, endResponse: false);  
  15.           }  
  16.       }  
   這是在 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使用。
關係圖如下:
完。

相關推薦

MVC 3 TempData深入研究Action沒有TempData思考

  寫這篇東西源於一個問題: 問題描述 在一個Action中加入TempData["message"] = this.dialog.GetValue("NoLogin"),轉到另一個Action時沒有取到TempData["message"] 值。 [csharp]

C語言語句的流氓

別跟我提goto,那孫子除了能在出錯處理討兩口飯吃之外,一無是處! 拓展: goto語句一般的語法規則如下: 從上面的程式碼看到goto的語法很簡單,就是直接跳轉到指定的標籤處,所謂的標籤(如例子中的label)指的是後面帶一個冒號的識別符號。 要注意一

thinkphp5 三種重定向

scrip names 成功 三種 line hist server -s 需要 頁面跳轉 在應用開發中,經常會遇到一些帶有提示信息的跳轉頁面,例如操作成功或者操作錯誤頁面,並且自動跳轉到另外一個目標頁面。系統的\think\Controller類內置了兩個跳轉方法succ

xilinx 高速收發器Serdes深入研究-Comma碼()

  一、為什麼要用Serdes 傳統的源同步傳輸,時鐘和資料分離。在速率比較低時(<1000M),沒有問題。 在速率越來越高時,這樣會有問題 由於傳輸線的時延不一致和抖動存在,接收端不能正確的取樣資料,對不準眼

每天一點點之vue框架開發 - vue-router路由進階路由別名、、設置默認路由

跳轉 創建 mage 分享圖片 const oot ons dir info 路由別名 別名的作用:防止文件路徑泄露 使用之前顯示如下: 使用別名後就只會顯示到域名,後面的文件是不會顯示的,這就起到保護的作用了 在main.js中的路由中添加name來創建別

3.Vue中點選button至新的路由

1.params傳參: this.$router.push({name:'parasetEdit',params:{pk_refinfo:'test',value:'test1'}}); 目標頁面接收引數: this.$route.params.pk_refinfo 2.query傳參:

學生管理系統頁面設計

新增頁面。 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4

【Win10】探索 Windows 10 10586 之 JumpList列表

原文: 【Win10】探索 Windows 10 10586 之 JumpList(跳轉列表) Windows 10 10586 出來了也挺久的了,應該大部分都從 10240 升級到這個版本了。在 10586 中,微軟添加了 200 多個新的 API,具體 API 的變動,大家可以點選下面這個連結來看:

微信一鍵啟用會員卡外鏈

1.建立會員卡的同時配置(use_custom_code:false)// 1.填入了自動啟用auto_activate欄位,啟用連結activate_url和一鍵開卡介面設定都會失效;// 2.若同時傳入了activate_url,則一鍵開卡介面設定會失效;// 3.建議開

微信無法下載APP的解決方案微信自動瀏覽器打開下載鏈接

信任 現在 自動跳轉 頁面 div 分享圖片 鏈接地址 cdn 不讓 現在微信分享的功能很多,從分享的鏈接下載apk安卓包是很正常的,但是微信不讓下載apk包,只能通過瀏覽器來下載,但是這要給用戶一個提示吧,不然用戶不知道 下面我們來實現,用戶通過微信點擊跳轉瀏覽器來下

ROS Learning-032 提高篇-010 LaunchLaunch 深入研究 --- 啟動檔案程式設計ROS 的 XML語法簡介

ROS 提高篇 之 Launch 深入研究 - 01 — 啟動檔案的程式設計 — ROS 的 XML語法簡介 我使用的虛擬機器軟體:VMware Workstation 11 使用的Ubuntu系統:Ubuntu 14.04.4 LTS ROS 版

netty原始碼深入研究從客戶端入手第四篇讀寫超時詳解

怎麼設定讀寫超時的監聽函式呢,首先從文件開始,或者看看官方有沒有例子,一般任何平臺的官方都會或多或少的提供例子。 官方文件有這樣一個類new IdleStateHandler(readerIdleTimeSeconds, writerIdleTimeSeconds, all

mvc ajax 請求 session失效,到登入頁

 public class BaseController : Controller    {        ///        /// 登入驗證/掉線攔截        /// protected override void OnActionExecuting(ActionExecutingContext

Linux 下 Sublime Text 3 安裝 Godef 進行程式碼/

GoSublime 外掛中的跳轉使用的是 GsDoc,只能跳轉包名點出來的成員和函式(例如 fmt.Println),很不方便後來發現了 Godef 這個外掛,可以實現任意物件跳轉(瞬間爽的飛起,此外,我還添加了跳轉回來的方法)以下記錄下安裝過程和遇到的問題1. 安裝 god

Spring深入研究

Spring 建立Spring配置檔案 Spring配置檔案 名字位置不固定 放在src目錄下面,命名applicationContext.xml schema約束 <?xml

netty原始碼深入研究從客戶端入手第二篇詳解讀訊息的管道處理流程

上一篇講到netty和伺服器建立連線的所有過程,接著上一篇的結尾,看程式碼 private static void doConnect( final SocketAddress remoteAddress, final SocketAddress

Spring MVC 設定訪問錯誤路徑自動到指定頁面

在dispatcher-servlet.xml中配置如下servlet後,對於*.do結尾的url請求,將轉發給org.springframework.web.servlet.DispatcherServlet類去處理。 問題:對於不存在的頁面或者不符合匹配條件的url,瀏

Intent傳送簡訊到傳送介面

    在main.xml中: <LinearLayout     xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="fill_parent"

mvc設計模式實現簡單登入

MVC模式 MVC最初應用於桌面程式中,M指資料模型,V指使用者介面,C指控制器,是Xerox PARC在20世紀80年代為程式語言“Smalltalk-80”發明的一種軟體設計模式,至今已被廣泛使用。基於JavaEE的Web應用開發,經歷了Model1和Model2的不同

Android studio 點選按鈕開啟介面 介面

1,建立layout(activity_test.xml)在src/main/res/layout滑鼠右鍵new->LayoutResource File然後輸入一個file name,比如:activity_test點ok鍵完成建立2,建立activity(TestA