如何評估並將現有的 ASP.NET 網站上至雲端 - [Azure Web App]

前言
先前有一個客戶想要將現有的公司內部的 ASP.NET 網站佈上 Azure 的 Web App ,他們希望我能夠提供諮詢與協助,本篇文章是將這個網站轉換並放置到 Azure Web App 的經驗分享。

 

現有環境

  • Windows Server 2008 R2 ==> 顧名思義 執行在 IIS 7.5
  • ASP.NET WebForm 網站
  • .NET Framework 4.0
  • MS SQL Server 2008 R2

 

評估現有網站是否可以上雲端
看到上方的「現有環境」,相信讀者應該也注意到了這是一個 ASP.NET WebForm 所開發的網頁應用程式,但事實上移植到 Azure 並沒有差別,因為 Azure WebApp 也是提供 IIS 包含一個 AppPool 的執行個體,好比你在地端的 IIS 上開一個 IIS AppPool 來執行應用程式一樣沒什麼差別!

 

但是在 PaaS 上執行還是有一些限制存在,一般來說,要評估一個現有的網站是否可以上至雲端,我們會透過以下幾點來評估:

  • Web Apps 僅支援連接埠 80 (適用於 HTTP) 和連接埠 443 (適用於 HTTPS 流量)的繫結,系統將會忽略不同的連接埠組態,並將流量路由傳送至 80 或 443
  • Web Apps 預設支援匿名驗證以及應用程式所指定的表單驗證 只有與 Azure Active Directory 和 ADFS 整合,才能使用 Windows 驗證,目前不支援所有其他形式的驗證 (例如基本驗證)
  • Web Apps 不支援全域組件快取GAC,如果您的應用程式會參考部署至 GAC 的組件,就必須部署至 Web Apps 上的應用程式 bin 資料夾
  • Web Apps 不支援 IIS5 以前的相容性模式
  • 應用程式集區 - 在 Web Apps 中,每個網站及其子應用程式都在相同的應用程式集區中執行
  • 如果您的網站上有多個利用多個應用程式集區(AppPool)的子應用程式,請將它們彙總到具有通用設定的單一應用程式集區,或將每個應用程式移轉至個別的 Web 應用程式
  • Web Apps 不允許在平台上註冊 COM 元件,如果您的網站或應用程式使用任何 COM 元件,您必須以 Managed 程式碼予以重新撰寫,並與網站或應用程式一起部署這些元件
  • Web App 為了高可用性 HA (High Availability) 與附載平衡 (Load-Balances),因此,不支援 SessionState,如果您有使用到請考慮將 Session 存放至 SQL Database 或是 Redis Cache 中
  • 在 WebApp 上,若你需要上傳檔案至檔案系統,請務必使用 Storage/Blob 來存放,而不是使用 WebApp (VM) 本身的儲存體,因為儲存體的資料會因為動態配置時被刪除或消失

 

應用程式要上 Azure Web App,除了排除前面的已知的注意事項、與定價資訊(評估)外,需要上 PaaS 的項目部分,我們得要分以下幾個來看:
1. ASP.NET Website
2. (應用程式/網站) 所使用的 MS SQL Server 也需要上 Azure SQL Database
3. ASP.NET Website 的 Session 導向 SQL Database 或 Redis Cache 中
     A. 如果要導向 Redis Cache 中,那麼根據目前網站的使用者數、每一個 Session 工作階段所使用的量來評估的話,最少得使用 C1 標準 定價等級的 Redis Cache
4. 原有的 ASP.NET 網站若有檔案上傳 (FileUpload) 的部分,這得要再開一個 儲存體帳戶 來存放上傳的檔案,因此也得要將這個 Storage 成本估算進來。
5. 如果網站需要走 SSL 也要將憑證的租用費用算進來
6. 多少使用者、平均用量 (頻寬/價格)

 

作業開始
以下開始進行上雲端的作業,首先,評估的項目是,因為可能要將 Session 導向至 Redis Cache 中,但 Microsoft.Web.RedisSessionProvider 如果要使用叢集,官方建議使用 2.0.1 或更高版本,否則會擲回例外狀況,但這樣可能需要使用 .NET 4.6 的執行環境會較理想,但是現有的網站是 .NET 4.0,所以勢必要升級。

 

而升級,我們會面臨到幾個挑戰,如下方我們一個個來談:

1. Entity Framework 4.0 升級到 5.x 以上

在 Entity Framework 4.x 之前的版本使用的是 「擴充功能」裡的 System.Data.Entity.DLL 提供的實體物件功能,但這從 5.x 開始改由  NuGet 提供,所以這個問題稍微棘手一點,因為該舊網站使用的是 Entity Framework 4.0 而且使用的還是 Model Designer from existing Database

 

在我們改使用 NuGet 安裝 Entity Framework 且升級到  6.1.3 之後,馬上編譯就會發生錯誤如下:

因為原本的 ObjectContext 定義在 System.Data.Objects 中,且在 System.Data.Entity.DLL 這顆組件中,但 5.x/6.x 是定義在 EntityFramework.DLL 這個組件中,且命名空間更換到 System.Data.Entity.Core.Objects 裡。解決方式也很容易,首先,移除掉 「System.Data.Entity」、「System.Data.Objects」、「System.Data.Objects.DataClasses」這三個命名空間,如下:

這時候按下 快速鍵 Shift+alt+F10 小燈泡就會自動列出可以引用的項目。

剩下的 [assembly: EdmSchemaAttributes()] 使用 IDE 工具幫我們找回應該要 using 的命名空間即可。

 

注意:

您也可以改寫為 DbContext,從 Entity Framework 5.x 開始改以 DbContext 取代 ObjectContext,DbContext 是 ObjectContext 的輕量版,比較適合套用 Repository Pattern 樣式來實作 Data Access Layer,詳細的比較可以參考 Stackoverflow 上的解釋「ObjectContext vs DbContext

 

2. 更新 AjaxControlToolKit

這部分比較沒什麼問題,原先所使用的 AjaxControlToolKit 是 4.1.51116,且不是由 NuGet 安裝,是自行下載參考進來的,這部分還好 NuGet Market 上還可以找到 4.1.51116

 

到這裡,初步可以在開發環境先測試使用 Entity Framework 6.1.3 與 執行原本的 CRUD 是無問題,以及驗證 AjaxControlToolKit 在 .NET Framework 4.6 下所有 WebControls 的執行是否如預期,可搭配一些自動化測試工具,如:Selenium。

 

開始上雲端

如果驗證無誤,由於應用程式本身使用到 Session,前面提到客戶希望此應用程式使用 Redis Cache 來存放 Session,現在我們要開始安裝 Microsoft.Web.RedisSessionStateProvider

接著是調整 Web.Config 中的 <sessionState> 區段,將 mode 調整為 “custom”,並定義 customProvider,如下:

assessKey 以及連線字串中的 password 可由 Azure Portal 中取得。

 

資料庫也要上雲端 (PaaS 服務的 SQL Database)

當然,我們得先建立一個 SQL Database 服務,建立方式筆者就不再熬述,可參考 Microsoft Docs 「

在 Azure 入口網站中建立和查詢單一 Azure SQL Database」 的說明。

 

在 SQL Database 建立完成之後,接著就是將現有地端的 MSSQL Database 匯出來,可使用命令列工具 BACPAC 工具匯出,或是使用 SSMS 工具執行匯出,筆者這裡採用 BACPAC 工具匯出。

匯出方式如下:

不過在執行匯出時,如果你的 Store Procedure 的撰寫方式如果是 [DBName].[dbo].[TableName] 方式是不會過的,原因很減單,因為 SQL Database 本來就不支援跨 DB 的寫法,因為他只是提供單一 Instance 給你。

 

匯入方式如下圖:

 

註:

BACPAC 命令列語法可參照官方說明「SqlPackage.exe 說明

 

在前棉的步驟都完成之後,我們可以將這個 ASP.NET WebForm 的應用程式發佈到 Azure WebApp 上 (發佈到 Azure WebApp 上可參考:Publish the App to Azure WebApp 這類說明官方都非常完整,筆者就不再熬述)

 

開始測試

萬事俱備後,緊張的時刻來了,希望執行時不要掛掉 XD,不過事實上,總是事與願違,一執行隨即收到錯誤訊息…..

根據錯誤訊息可以推斷是有物件 Class 並未標記為 [Serializable] 導致無法序列化並儲存到 Redis Cache 中,於是立馬執行簡單的 Code-Review 作業,搜尋所有程式碼裡面有使用到 Session 的部分,並確認都加上 [Serializable],但重新發佈後在測試還是相同的結果,案情這時候陷入膠著…………

 

於是,唯一作法就是先寫 Log 吧!重新發佈程式後,再由 Log 去查看問題出在什麼地方?

後來在 Log 中發現,在將 SecurityLevel 物件塞入時發生這個 Exception,詳細查看這個 SecurityLevel 類別後發現,他的屬性 Property 裡面居然有一個父類型是 System.Web.UI.Page 類型的物件,疑!這不是 Web Page 的型態,這就搞笑了,倒底是誰把 Web Page 塞到 Session 裡?(怒~翻桌)… 這當然會序列化失敗。

 

調整後,果然測試成功!!!

 

後記

現在越來越多企業對於內部系統上 Azure PaaS 躍躍欲試,筆者手上光是今年就有三家客戶想將現有系統移植上 Web App,以前將 ASP.NET MVC 網站佈署至 Azure 上大部分是玩票性質,直到真的有企業進行現有系統的 Migration 後才會真正遇到一些實際的狀況。

留言

這個網誌中的熱門文章

軟體工程師 - 成長的 10 個階段

常見的程式碼壞味道(Code Smell or Bad Smell)

什麼是 gRPC ?