2011年12月27日 星期二

[ASP.NET] MVC 3上,Session消失的問題與解決辦法

筆者因工作因素,不得不碰Microsoft MVC 3。雖然開發方便,不過他隱含的一些奇怪的Bug也不少。
身為一個網站開發人員,時常會用到Cookie與Session。奇怪的點就在於MS Visual Studio 2010在開發MVC 3時,有時候會為了某些奇怪因素,重新啟動你開發的Application。
目前普遍知道的重新啟動事件,可能會於以下情形發生:
  1. 從應用程式的 Bin 資料夾中加入、修改或刪除組件。
  2. 從 App_GlobalResources 或 App_LocalResources 資料夾中加入、修改或刪除當地語系化資源。
  3. 加入、修改或刪除應用程式的 Global.asax 檔。
  4. 在 App_Code 目錄中加入、修改或刪除原始程式碼檔。
  5. 加入、修改或刪除設定檔組態。
  6. 在 App_WebReferences 目錄中加入、修改或刪除 Web 服務參考。
  7. 加入、修改或刪除應用程式的 Web.config 檔。
  8. 防毒軟體剛好掃到Webconfig檔
然而,筆者最近發現兩個更奇怪的重新啟動狀況!
  1. Application restarts on directory delete in ASP.net
  2. 檔案上傳後,在該Action內直接return RedirectToAction() ;
開發MVC 3 or ASP.NET網站人員應該都知道,在SessionState為InProc的設定下,任何重新啟動( or Application_start函數的呼叫),將導致Session消失
很不巧地,筆者剛好遇到上述第2種狀況,導致Session消失,Debug 10個小時才抓到此嚴重錯誤。
其實上面第 2種狀況,應該算是「 Bin 資料夾中加入、修改或刪除組件」,從下面程式碼便可知一二:
public ActionResult Access()
{
    // 取出剛剛上傳的檔名,這邊會發現取不到值
    string fileName = TempData["upfile"].ToString();
    /*                 
     *   處理的程式區段   
     */               
    return View();
}

[HttpPost]
public ActionResult Upload(HttpPostedFileBase file)
{
    //檢查是否有檔案上傳,有的話存檔
    if (file !=null && file.ContentLength > 0)
    {
        //放在執行檔目錄中Upload目錄底下
        string savedName = "./Upload/" + file.FileName;
        file.SaveAs(savedName);
        TempData["upfile"]=file.FileName;
    }            

    return RedirectToAction("Access");
}
從上述可知,由於我在檔案上傳時,"增加"了bin資料夾下的組件,導致Application_start()函數再次啟動,使得我的Session就這麼消失了。

為了避免這個錯誤一再重演,建議將Session State設定為StateServer或是SQL Server模式。
將Session State設定為State Server的方法,可看這篇「如何讓 ASP.NET 使用 Session 資料時不要再自動消失」,主要兩個步驟:
1. 啟動 aspnet_state.exe (ASP.NET State Service)
2. 在Web.config中,加入以下這段設定:

<configuration>
  <system.web>
    <sessionState mode="StateServer"
      stateConnectionString="tcpip=localhost:42424"
      cookieless="false"
      timeout="20"/>
  </system.web>
</configuration>

需要注意設定的Web.config檔案的出處。(不知道為什麼,筆者的View資料夾下剛好也有Web.config,若手誤設定到這個,執行會錯誤喔!)
為了追求開發出來的程式不因Application重新啟動而造成不穩定狀況 (例如:筆者遇到的檔案上傳導致無法儲存Session的問題,也是透過部署的方式,立刻解決!)。
我們通常會需要用到部署,至於設定部署功能,請跟著筆者這樣子做:
  1. 請先安裝好IIS  6/7
  2. 若是使用ASP.NET 4.0,ASP.NET 4.0的安裝程式,不會連同IIS一起設定,因此,請按照此方法設定。
  3. 從IIS中,設定好網站目錄 or 虛擬目錄
  4. 設定單鍵發行功能,方法請看如何使用 Visual Studio 2010 的「單鍵發行」功能 (MsDeploy)
另一個可能發生的問題,則是在負載平衡的 Web 伺服陣列環境執行 ASP.NET Web 應用程式時,如果使用 SqlServer 或 StateServer 工作階段模式,工作階段狀態可能會遺失
解決辦法在微軟的「PRB:如果您使用 SqlServer 或 StateServer 工作階段模式,Web 伺服陣列中的工作階段狀態會遺失」,剛好遇到這類問題的使用者,可以看一看。


Reference
  1. [.NET] ASP.NET 狀態管理(State Management):Session
  2. [ASP.NET] Session 遺失 / Session Timeout / Session 設定
  3. 如何讓 ASP.NET 使用 Session 資料時不要再自動消失
  4. IIS虛擬目錄設定
  5. ASP.NET IIS設定工具
  6. ASP.NET 應用程式生命週期概觀
  7. ASP.NET MVC Request Flow (推薦閱讀!)
  8. ASP.NET MVC 3概觀正體中文版 (推薦閱讀!)

10 則留言:

  1. My have a question,
    Do you can write some demo code re-appear this bug.

    ps, Sorry, I use NB no Chinese IME.

    回覆刪除
  2. 感謝留言,我加入範例囉。

    回覆刪除
  3. http://blog.kkbruce.net/2011/12/aspnet-mvc3-tempdatasession.html#.TvqwbtT9NZg

    以上是我驗證的結果,Session不會消失。

    是否是路徑問題,一般直接在根目錄下新增一個Upload或Files來進行上傳下載。

    我有寫過幾篇mvc上傳下載的文章:http://blog.kkbruce.net/p/net-framework.html#mvc3upload,你參考看看。

    回覆刪除
  4. Dear Bruce, 感謝您的測試。
    我上面寫的code,上傳下載檔案是在bin資料夾下執行的。所以,會有Application_restart的情況發生。

    回覆刪除
  5. 非放在 /bin 不可嗎?
    而且你開頭第一句就說「隱含的一些奇怪的Bug也不少。」<--是他打動我的。

    回覆刪除
  6. 喔喔,不好意思!那是我測試的時候沒注意到這個問題。
    起初也認為是Session有問題,
    但是我測試了以下Code:
    public ActionResult Upload(HttpPosedFileBase file)
    {
    file.saveas("./upload/aaa.txt");
    ...
    ViewData['dataname'] = "xxx";

    return View();
    }

    然後在Upload.cshtml中
    @ViewData['dataname'];
    竟然顯示的出來。
    表示只有return ReturnToAction() 啟動時,才會造成Application restart。所以才覺得很奇怪。

    回覆刪除
  7. ASP.NET != ASP.NET MVC
    兩者完全不同產品線,有些內容必須先搞清楚。雖然有相通的地方,但"相異點",更要了解。

    ReturnToAction()會觸發Routing,它會走一次完整的MVC流程,你把資料寫入 /bin 中就已經完成Application restart的條件,你回應的測試Code(ViewData['dataname'] = "xxx";),Upload還在同一段流程內,並無觸發新的MVC流程,當然會顯示資料,這與Session無關。

    1. upload to /bin --> ViewData['dataname'] = "xxx"; --> View

    2. upload to /bin --> tempdata['dataname'] = "xxx"; --> redirect --> 跑MVC流程(Application restart) --> action --> View

    Session是你自己要讓他不見的,關MVC什麼事?

    回覆刪除
  8. 非常謝謝你的指教。
    也許標題與有些內容不該這麼寫。~"~a...
    因為我是剛學MVC不久的初學者,對於有些細節不太了解。
    有時候會以一般Web Design的觀點來解釋,造成你的誤會與費心測試還真不好意思。

    回覆刪除
  9. 交流、交流。
    另外想請問,把上傳檔案放在 /bin 裡有特別的原因嗎?

    回覆刪除
  10. 喔喔。沒特別原因。主要是我自己的壞習慣,
    把一些目前開發的dll以及測試用的檔案都放在這裡面,沒注意到在Inproc模式下,有這個奇怪的問題。

    回覆刪除