[ASP.NET] 多台 Web 伺服陣列透過 SQL Server 保存共同工作階段狀態 (session state)

ASP.NET 網站預設 session state 只存活在對應 web server 的 IIS 中,也就是說在多台 web server 服務或具有 failover 機制下,當切到另一台 web server 的時候喪失所有 session state 資訊,因此可以考慮將 session state 存放在 SQL Server 中,讓所有 web server 共享相同 session state 資訊。

 

前言


傳統開發方式如 ASP.NET WebForm / MVC 都可以方便存取 session 狀態資料,但預設 session state 只存活在對應 web server 的 IIS 中,也就是說在多台 web server 服務或具有 failover 機制下,當切到另一台 web server 的時候喪失所有 session state 資訊,當然登入資訊也習慣放置在 session 中,因此會造成用戶莫名被登出的情況;這時候我們可以透過 SQL Server 作為 session state 的容器,透過資料庫保存這些狀態,不論哪一台服務的 web server 都可以取得相同 session  資料,以下進行介紹。

 

建立 SQL Server 環境


利用 aspnet_regsql.exe 工具建立 session state 存放的資料庫環境,該工具位於 C:\Windows\Microsoft.NET\Framework\v4.0.30319 資料夾中,因此先切換目錄於此。

aspnet_regsql.exe -S  <DB Instance> -U <user name> -P <password> -ssadd -sstype p

執行後會建立出名為 ASPState 資料庫,並且包含 2 張資料表及相關 Stored Procedure 程式。

 

設定 Web.config 


可以在專案 web.config 加入以下設定,表示以 session state 是以  SQLServer 模式進行。

<system.web>
  ...
  <!--session state from SQL Server-->
  <sessionState mode="SQLServer" timeout="20" sqlConnectionString="server=OO.OOO.OOO.OO;uid=OO;pwd=OOOOO;"></sessionState>
  ...
</system.web>

 

加註 Serializable 標籤


執行後第一個面臨到的問題就是那些原本存放在 session 中的物件,若沒加註 Serializable 標籤就會報錯。

System.Runtime.Serialization.SerializationException: 未將類型 'ResultSsoLoginDto' (於組件 'MyUtility, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' 中) 標記為可序列化。

那是因為現在 session state 都會放置在 DB 中,所以都需要執行序列及反序列化動作,才可以順利存取該物件資料。這個解決方式就在所有須放置到 session 中的物件都加註 Serializable 標籤如下就可以了。

 

前端 JSON 格式逆襲


原本覺得一切都相當美好,但就在進行其他系統串接測試時,發現被註記 Serializable 標籤的物件,在被前端透過 web api 取得該物件時,獲得的 JSON 格式怪怪的造成前端判讀錯誤。

[
   {
      "<Token>k__BackingField":"CA1B21B2-B23C-4327-B99B-26AD34CB95C2",
      "<ExpiredMin>k__BackingField":"20",
      "<CName>k__BackingField":"王XX",
      "<UserAccount>k__BackingField":"0c0312689f607d118001ede12c19147c",
      "<IsForce>k__BackingField":false,
      "<UnLockTime>k__BackingField":"0001-01-01T00:00:00",
      "<PerserilNo>k__BackingField":"2639ef75171256d434ee07ab4c83ba5c",
      "<EmpId>k__BackingField":"1010234",
      "<CmpSerilNo>k__BackingField":"cae6fcca901c04220beeb67362447662",
      "<IsFirstLogin>k__BackingField":false,
      "<Result>k__BackingField":"100",
      "<ResultMsg>k__BackingField":"成功",
      "<ResultValue>k__BackingField":0
   }
]

可以透過以下方法全域調整 JSON 序列化時忽略 Serializable 標籤。

private void Application_Start(object sender, EventArgs e)
{
   // ...

   GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings = new JsonSerializerSettings()
   {
      ContractResolver = new DefaultContractResolver()
      {
         IgnoreSerializableAttribute = true
      }
   };
}

在前端透過 web api 取得該物件時,獲得的 JSON 格式就正確了。

[
   {
      "Token":"9F151070-4A53-48C3-A729-F6DFB35EA462",
      "ExpiredMin":"20",
      "CName":"王XX",
      "UserAccount":"0c0312689f607d118001ede12c19147c",
      "IsForce":false,
      "UnLockTime":"0001-01-01T00:00:00",
      "PerserilNo":"2639ef75171256d434ee07ab4c83ba5c",
      "EmpId":"1010234",
      "CmpSerilNo":"cae6fcca901c04220beeb67362447662",
      "IsFirstLogin":false,
      "Result":"100",
      "ResultMsg":"成功",
      "ResultValue":0
   }
]

 

後端反序列化的逆襲


接著又發現後端透過 httpClient 取得相同 web api 回傳物件時,在反序列化的時候出錯了。

最後直接移除 全域 JSON 序列化忽略 Serializable 標籤 的設定後,只要在原本加註 Serializable 標籤的物件中在加註 JsonObject 即可兼容。

 

參考資訊


如何設定與啟用 ASP.NET 的 SQLServer 工作階段狀態模式

Using Serializable attribute on Model in WebAPI

   


希望此篇文章可以幫助到需要的人

若內容有誤或有其他建議請不吝留言給筆者喔 !