黃偉榮的學習筆記

軟體的世界變化萬千,小小的我只能在這洪流奮發向上以求立足。
随笔 - 100, 文章 - 0, 评论 - 212, 阅读 - 17万
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

ASP.NET MVC TempData使用心得

Posted on   黃偉榮  阅读(1521)  评论(1编辑  收藏  举报

在看TempData的說明時,有人說用一次就刪除,有人說一個Request就結束,在道聽途說下,有一次我的Code就出了Bug,一直死在TempData,最後看Source Code才發現,我對TempData的認知出了錯誤。

 

原理

在ASP.NET MVC中資料傳遞主要有ViewData與TempData,ViewData主要是Controller傳遞Data給View,存留期只有一個Action,要跨Action要使用TempData,而TempData依TempDataProvider的不同,會有不同的存留期,預設的TempDataProvider是SessionStateTempDataProvider,你沒有看錯,預設是用Session來存放TempData,Session不是使用者存放資料,而且存留時間預設在20分鐘的嗎?

所以SessionStateTempDataProvider有做一些手段,Controller起來時,從Session載入TempData,然後刪除Session,所以在Action時是不會看到TempData的Session,在讀取TempData時,會記錄用了那些Key,在Controller結束時,會把沒有過的TempData在存回Session中,所以一直沒有讀取的TempData是會存在到Session消失的

Note:

ViewData的存留期測試

1
2
3
4
5
6
7
8
9
10
11
12
HomeController.cs 片段
public ActionResult Index()
{
    this.ViewData["Data"] = "Index";
    return View();
}
 
public ActionResult List()
{
    //什麼Data都沒有輸出
    return View();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Index.aspx 片段
<div>
    Partial:
    <%
        //ViewData是使用Index,不會執行List的Action
        Html.RenderPartial("List");
    %>
         
</div>
<div>
    Action:
    <%
        //ViewData是使用List,會執行List的Action
        Html.RenderAction("List");
    %>
</div>
 
List.ascx 片段
<%:this.ViewData["Data"] %>

 

結果

 image

執行Partial或RanderPartial是在同一個Action中直接呼叫View,共用同一個ViewData。

執行Action或RanderAction會呼叫另一個Action,那一個Action再呼叫View,使用不用的ViewData。

如果要在不同的Action中傳遞資料,要使用TempData。

 

錯誤重現

下列這段Code,猜猜有什麼Bug。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public ActionResult Index()
{
    this.TempData["UseDefault"] = "true";
    return View();
}
 
public ActionResult List()
{
    //在Index的View,會使用RanderAction呼叫List,但那一個區塊是會用Ajax重載
    if (this.TempData.ContainsKey("UseDefault"))
    {
        //從Index的View,使用RanderAction呼叫,使用預設值
        ..........
    }
    else
    {
        //從Ajax呼叫
        ...........
    }
 
    return View();
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

答案是

呼叫this.TempData.ContainsKey("UseDefult")一直都是True,因為ContainsKey不是使用,所以TempData["UseDefult"]會一直保留在Session,直到Session消失前都是true,所以從Ajax呼叫一直都是使用預設值。

 

原始碼分析

載入與儲存時機

1
2
3
4
5
6
7
8
9
10
11
12
System.Web.Mvc.Controller.cs 片段
protected override void ExecuteCore() {
    //載入TempData
    PossiblyLoadTempData();
    try {
    //呼叫Action
    ...........
    }
    finally {
    //儲存TempData
        PossiblySaveTempData();
}

 

TempData的一些操作

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
System.Web.Mvc.TempDataDictionary.cs 片段
//_data 是放Keys + Values
//_initialKeys 是放Keys,使用時移除Key
//_retainedKeys 是放有呼叫,Keep的Keys
public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider) {
    //載入放在Provider的資料
    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) {
    //keysToKeep = _initialKeys + _retainedKeys
    string[] keysToKeep = _initialKeys.Union(_retainedKeys, StringComparer.OrdinalIgnoreCase).ToArray();
    //keysToRemove = _data - keysToKeep
    string[] keysToRemove = _data.Keys.Except(keysToKeep, StringComparer.OrdinalIgnoreCase).ToArray();
 
    //刪除使用過且不保留的Keys
    foreach (string key in keysToRemove) {
        _data.Remove(key);
    }
 
    //將沒有使用的TempData存起來
    tempDataProvider.SaveTempData(controllerContext, _data);
}      
 
public object this[string key] {
    get {
        object value;
        if (TryGetValue(key, out value)) {
            //讀取時刪除Key,在Save時用來比較
            _initialKeys.Remove(key);
            return value;
        }
        return null;
    }
    set {
        _data[key] = value;
        _initialKeys.Add(key);
    }
}
 
public void Keep(string key)
{
    //保留Key
    _retainedKeys.Add(key);
}

Note:

我曾經想過寫一個Provider,資料是存放在HttpContext.Items,因為我習慣Temp的資料,在一個Request結束後就消失,不過專案成員們都覺得太多此一舉了,而作罷。

1
2
3
4
5
6
7
8
9
10
11
12
13
//自訂的TempDataProvider,沒辦法用設定改變(至少我沒找到),只能用繼承來覆寫CreateTempDataProvider作到統一使用
public class MyControllerBase : Controller
{
    protected override ITempDataProvider CreateTempDataProvider()
    {
        return new HttpContextItemsTempDataProvider();
    }
}
 
//使用
public class HomeControllerBase : MyControllerBase
{    
}


 

參考資料

编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
历史上的今天:
2007-10-02 檔案 "C:\Program Files\Microsoft SQL Server\MSSQL.2\MSSQL\DATA\master.mdf" 是壓縮檔,但不在唯讀資料庫或檔案群組中。必須解壓縮該檔案 的解決方法
点击右上角即可分享
微信分享提示