﻿# ASP.NET MVC 5 如何精準控制不同 Controller 下的 Session 運作機制

很多人並不知道 ASP.NET 的 Session 其實是[有毒的](https://blog.darkthread.net/blog/session-alternative/)！所以我個人會盡一切可能避免使用到 Session 機制，以免頁面遭到鎖定(Lock)狀況，影響使用者操作體驗。這篇文章我打算分享如何在 .NET Framework 的 ASP.NET MVC 5 精準控制不同 Controller 下的 Session 運作機制，避免使用者在多頁籤瀏覽的情況下遭到 Session 鎖定而無法瀏覽網頁。

### 針對全站關閉 Session 機制

首先，我們在 `Web.config` 的 `<system.web>` 區段內，就算你看不到 `<sessionState mode="InProc" />` 設定，但 .NET Framework 從某個版本開始預設就是啟用 sessionState 機制的，但你也可以透過以下設定明確關閉全站的 Session 機制：

```xml
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.8" />
    <httpRuntime targetFramework="4.8" />

    <sessionState mode="Off" />

  </system.web>
</configuration>
```

不過，關閉 Session 之後，我們在 ASP.NET MVC 5 的 `Session` 與 `TempData` 物件就再也無法使用，只要執行到有用到這兩個物件的 Action 都會導致網站發生例外狀況！

![Object reference not set to an instance of an object.](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/7bc2b3948415d66e795c-215513932-45e7133e-7905-4c72-b944-4a562fe401ca.jpg)

![The SessionStateTempDataProvider class requires session state to be enabled.](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/1de59b936776458ccdee-215514207-b4e3976b-7a4a-4ac8-94f6-52908d2e4ab6.jpg)

### 針對特定 Controller 關閉 Session 機制

如果你現有的網站已經使用了大量的 Session 機制，但有一些需要長時間執行的頁面（例如匯出報表檔案），那就可以特別獨立一個 `Controller` 出來，特別對這個 `Controller` 關閉 Session 機制，如此一來就不會影響使用者瀏覽其他頁面的操作體驗。

只要在 `Controller` 類別上套用 `[SessionState]` Filter 並傳入 `SessionStateBehavior.Disabled` 就可以停用整個 `Controller` 的 Session 使用：

```csharp
[SessionState(System.Web.SessionState.SessionStateBehavior.Disabled)]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}
```

不過你依然要注意，這個 `Controller` 中所有 `Action` 都不能使用到 `Session` 與 `TempData` 物件！

### 針對特定 Controller 啟用 Session 的 ReadOnly (唯讀) 機制

如果你真的一定需要用到 Session 機制，但又不想讓使用者被 Session 鎖定，此時你可以把特定 `Controller` 設定為「唯讀」狀態，你可以讀取到 Session 資料，但又同時不會進入 Session 的鎖定狀態！👍

以下是在 `Controller` 中設定為 `SessionStateBehavior.ReadOnly` 唯讀模式的方法：

```csharp
[SessionState(System.Web.SessionState.SessionStateBehavior.ReadOnly)]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}
```

### 調整 TempData 使用 Cookies 來儲存暫存資料

由於 ASP.NET MVC 5 的 `TempData` 預設採用 Session 來實作，所以如果你真的一定要使用 `TempData` 但又不想使用有毒的 `Session` 的話，那就可以自行實作 [ITempDataProvider 介面](https://learn.microsoft.com/zh-tw/dotnet/api/system.web.mvc.itempdataprovider?view=aspnet-mvc-5.2&WT.mc_id=DT-MVP-4015686)，改用 `Cookies` 來儲存資料！

> 注意: 從 ASP.NET Core 1.1 開始就有內建一個 [CookieTempDataProvider](https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.ViewFeatures/src/CookieTempDataProvider.cs) 實作，但在 ASP.NET MVC 5 就要自行實作這個類別！

我有發現一個有趣的地方是，在 ASP.NET MVC 5 原始碼專案中並沒有發現 `CookieTempDataProvider` 的影子，但在 `mono` 計畫中 (一個跨平台的 .NET Framework 實作)，卻有實作出一個 [CookieTempDataProvider](https://github.com/mono/aspnetwebstack/blob/master/src/Microsoft.Web.Mvc/CookieTempDataProvider.cs) 類別，你可以從 GitHub 看到[完整的實作原始碼](https://github.com/mono/aspnetwebstack/blob/master/src/Microsoft.Web.Mvc/CookieTempDataProvider.cs)！

你可以自行將該 `CookieTempDataProvider` 類別複製到你現有的 ASP.NET MVC 5 專案中，並在 `Controller` 或自訂的 `BaseController` 中覆寫 `Initialize()` 方法，替換掉預設的 `TempDataProvider` 屬性即可！如下範例：

```csharp
public abstract class BaseController : Controller
{
    protected override void Initialize(System.Web.Routing.RequestContext requestContext)
    {
        base.Initialize(requestContext);

        TempDataProvider = new CookieTempDataProvider(requestContext.HttpContext);
    }
}
```

### 相關連結

-   [示範 ASP.NET MVC 5 如何控制 Session 與 TempData 機制運作的原始碼](https://github.com/doggy8088/NoSessionMvc/commit/056b3f3866fc2da265d6e88d9bb4a38570e3cb52)
-   [aspnetwebstack/CookieTempDataProvider.cs at master · mono/aspnetwebstack · GitHub](https://github.com/mono/aspnetwebstack/blob/master/src/Microsoft.Web.Mvc/CookieTempDataProvider.cs)
-   Using an alternative ITempDataProvider implementation in ASP.NET MVC | Jonathan George's Blog
