惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Help Net Security
Help Net Security
G
Google Developers Blog
雷峰网
雷峰网
WordPress大学
WordPress大学
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Engineering at Meta
Engineering at Meta
Security Latest
Security Latest
T
Threat Research - Cisco Blogs
AWS News Blog
AWS News Blog
F
Full Disclosure
C
Cybersecurity and Infrastructure Security Agency CISA
T
The Exploit Database - CXSecurity.com
J
Java Code Geeks
U
Unit 42
C
Cyber Attacks, Cyber Crime and Cyber Security
V
V2EX
C
Cisco Blogs
博客园 - 司徒正美
Project Zero
Project Zero
L
LINUX DO - 热门话题
阮一峰的网络日志
阮一峰的网络日志
Blog — PlanetScale
Blog — PlanetScale
Scott Helme
Scott Helme
A
About on SuperTechFans
Hugging Face - Blog
Hugging Face - Blog
S
Securelist
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
S
Schneier on Security
G
GRAHAM CLULEY
酷 壳 – CoolShell
酷 壳 – CoolShell
Cyberwarzone
Cyberwarzone
MongoDB | Blog
MongoDB | Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 叶小钗
T
Threatpost
Recorded Future
Recorded Future
C
CXSECURITY Database RSS Feed - CXSecurity.com
宝玉的分享
宝玉的分享
N
News and Events Feed by Topic
人人都是产品经理
人人都是产品经理
The Register - Security
The Register - Security
S
Security Archives - TechRepublic
博客园 - Franky
N
News | PayPal Newsroom
Simon Willison's Weblog
Simon Willison's Weblog
S
SegmentFault 最新的问题
W
WeLiveSecurity
A
Arctic Wolf
B
Blog

博客园 - 黃偉榮

Web Project的檔案共用小技巧 IoC的中繼器:CommonServiceLocator UTF8Encoding與BOM Temporary Post Used For Theme Detection (d4b0aefa-c88e-4957-bba7-b367d1bfa042 - 3bfe001a-32de-4114-a6b4-4005b770f6d7) 寫CodedUI時如何尋找控制項的小技巧 Visual Studio 2010 Feature Packs 2之Silverlight自動化測試 Moles - Isolation framework for .NET(假.Net)介紹 [小技巧]Entity Framework強型別Include C#仿Oracle Decode,將ValueType對應成String - 黃偉榮 - 博客园 Visual Studio 單元測試的3種Initialize與Cleanup jQuery套件-檢查頁面的欄位是否有變更 用EventLogReader查詢特殊EventLog jQuery自製Plugin-Bind事件函式時檢查有沒有Bind過 Visual Stuiod 自訂檔案比較合并工具 [小技巧]自動化測試時NLog的訊息輸出到測試結果中 小技巧:專案切換32與64位元組件 Linq小技巧:日期處理 Unit Test小技巧 : DateTime的Stub 解決TFS Build Asp.Net Mvc開啟MvcBuildViews後無法載入組件問題
ASP.NET MVC TempData使用心得
黃偉榮 · 2010-10-02 · via 博客园 - 黃偉榮

在看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的存留期測試

HomeController.cs 片段
public ActionResult Index()
{
    this.ViewData["Data"] = "Index";
    return View();
}

public ActionResult List()
{
    //什麼Data都沒有輸出
    return View();
}
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。

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呼叫一直都是使用預設值。

原始碼分析

載入與儲存時機

System.Web.Mvc.Controller.cs 片段
protected override void ExecuteCore() {
    //載入TempData
    PossiblyLoadTempData();
    try {
    //呼叫Action
    ...........
    }
    finally {
    //儲存TempData
        PossiblySaveTempData();
}

TempData的一些操作

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結束後就消失,不過專案成員們都覺得太多此一舉了,而作罷。

//自訂的TempDataProvider,沒辦法用設定改變(至少我沒找到),只能用繼承來覆寫CreateTempDataProvider作到統一使用
public class MyControllerBase : Controller
{
    protected override ITempDataProvider CreateTempDataProvider()
    {
        return new HttpContextItemsTempDataProvider();
    }
}

//使用
public class HomeControllerBase : MyControllerBase
{     
}

參考資料