MVC 3 TempData深入研究(跳轉Action中沒有取TempData的思考)
寫這篇東西源於一個問題:
問題描述
在一個Action中加入TempData["message"] = this.dialog.GetValue("NoLogin"),轉到另一個Action時沒有取到TempData["message"] 值。
[csharp] view plaincopyprint?- [RolesFilterAttribute]
- public ActionResult ModifyUser()
- {
- AccountInfo userInfo = ViewData[
- if (userInfo != null)
- { ............................
在需要的方法上貼觸發器:
- /// <summary>
- /// 使用者查詢頁面(Get)
- /// </summary>
- /// <returns></returns>
- [<span style="color:#333333;">RolesFilterAttribute</span>]
- [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");
- <strong><span style="color:#ff0000;"> return RedirectToAction("Error", "Common");</span></strong>
- }
- ............................其他程式碼省略
- }
說明:RolesFilterAttribute 實現介面IAuthorizationFilter ;下面是簡單實現程式碼:
[csharp] view plaincopyprint?- #region IAuthorizationFilter 成員
- void IAuthorizationFilter.OnAuthorization(AuthorizationContext filterContext)
- {
- if (filterContext == null)
- {
- thrownew ArgumentNullException("filterContext");
- }
- if (!this.AuthorizeCore(filterContext))
- {
- filterContext.Result = new ViewResult() { ViewName = "Error"};
- }
- }
- privatebool AuthorizeCore(AuthorizationContext filterContext)
- {
- AccountInfo account = userSystem.CheckLogin();
- if (account != null)
- {
- if ((account.Power & Role) > 0)
- {
- returntrue;
- }
- returnfalse;
- }
- returnfalse;
- }
- #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?- public 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;
- }
- }
輸入 Home/Index,此時發現頁面已經跳轉到:Home/Index3,且輸出“WT”。似乎說明:說明TempData會保留未使用的值,並不是說"TempData只存放一次資料,到第三個Action時,第一個Action存放的資料就失效了"。
實際上,這還是沒有能解決我上面說的問題,上面的例子只是說明我們這前的理解是有問題的。 有興趣就跟我研究一下原始碼吧。
直奔主題--TempDataDictionary與ITempDataProvider
一個一個來唄:
Controller-->ControllerBase-->TempData-->TempDataDictionary;()
裡面幾個重要的方法:[csharp] view plaincopyprint?
- // 儲存當前真實資料的欄位
- private Dictionary<string, object> _data;
- // 儲存初始資料欄位,新增操作會在此欄位進行
- private HashSet<string> _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
- // 儲存保留的欄位
- private HashSet<string> _retainedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
- <p> /// <summary>
- /// 將所有真實資料儲存進保留欄位中
- /// </summary>
- publicvoid Keep() {
- _retainedKeys.Clear();
- _retainedKeys.UnionWith(_data.Keys);
- }</p><p> /// <summary>
- /// 將特定鍵儲存進保留欄位中
- /// </summary>
- publicvoid Keep(string key) {
- _retainedKeys.Add(key);
- }</p><p> </p><p> /// <summary>
- /// Load 資料
- /// 注意:在控制器方法執行前執行
- /// </summary>
- /// <param name="controllerContext"></param>
- /// <param name="tempDataProvider"></param>
- publicvoid 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();
- }</p><p> publicobject Peek(string key) {
- object value;
- _data.TryGetValue(key, out value);
- return value;
- }</p><p> /// <summary>
- /// 儲存資料(預設儲存進Session中)
- /// 注意:將未使用過的值儲存進Session中
- /// </summary>
- /// <param name="controllerContext"></param>
- /// <param name="tempDataProvider"></param>
- publicvoid 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);
- }
- </p><p> </p><p> /// <summary>
- /// 索引器
- /// 注意:Get:會在初始欄位中,移除掉已經使用過的值
- /// Set:會在初始欄位中,加入新增的值。
- /// </summary>
- /// <param name="key"></param>
- /// <returns></returns>
- publicobjectthis[string key] {
- get {
- object value;
- if (TryGetValue(key, out value)) {
- _initialKeys.Remove(key);
- return value;
- }
- returnnull;
- }
- set {
- _data[key] = value;
- _initialKeys.Add(key);
- }
- }</p>
可以簡單總結一下:(MVC預設實現)
舊資料(_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。 | 控制器方法被觸發後 |
Controller-->ITempDataProvider ;[csharp] view plaincopyprint?
- publicinterface ITempDataProvider {
- IDictionary<string, object> LoadTempData(ControllerContext controllerContext);
- void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values);
- }
這兩個方法是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?
- protectedoverridevoid 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();
- }
- }
再深入一點:
PossiblyLoadTempData 關鍵: TempData.Load(ControllerContext, TempDataProvider);
PossiblySaveTempData 關鍵: TempData.Save(ControllerContext, TempDataProvider);
您會發現:TempData.Load和TempData.Save就是 上述表格列出的方法。這是重點:我們知道了這兩個方法執行的時機:一個在方法被InvokeAction前,一個在後,如表格所述。
假如您細心,你會發現:表格中的列出的 舊資料值(retainedKeys) 好像沒有改變?如果真不改變,上一個方法中設定的TempData怎麼獲取到的的?找了很久,發現:
1.如果父ViewContext.TempData有值,將值儲存進當前TempData[csharp] view plaincopyprint?
- [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;
- }
- }
2. TempData呼叫Keep()就可以拿上父TempData 傳過來的值了。[csharp] view plaincopyprint?
- publicoverridevoid ExecuteResult(ControllerContext context) {
- if (context == null) {
- thrownew ArgumentNullException("context");
- }
- if (context.IsChildAction) {
- thrownew 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);
- }
- }
這是在 public class RedirectResult : ActionResult 中的,是在一個Action的執行體。哦,原來是在方法執行的時候,將上一次的TempData值存入舊資料值(retainedKeys)的。查一下程式碼,您將看見:
2.[html] view plaincopyprint?
- /// <summary>
- /// 將所有真實資料儲存進保留欄位中
- /// </summary>
- public void Keep() {
- _retainedKeys.Clear();
- _retainedKeys.UnionWith(_data.Keys);
- }
_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 Launch)Launch 深入研究 --- (啟動檔案程式設計)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