如今許多方便好用的功能都已經內建在 .NET 2.0 之上,回頭看看當初 1.1 寫的東西總是很想掉下眼淚,無奈自己已經誤上賊船,為了降低將來維護成本並且增加未來發展的視野性,最近幾天就鐵下心來,籌畫著將私底下仍在經營的網站,升級到 ASP.NET 2.0。
為了讓整個升級過程無痛,我不想讓使用者發覺停機過久,盡量讓網站無法運作的時間縮短在一個「瞬間」,所以我決定利用 ASP.NET 2.0 / CLR 2.0 仍可以執行 1.1 版本的向下相容特性,決定先透過變更 IIS 內的版本設定讓 ASP.NET 1.1 開發的網站先執行在 2.0 的環境底下,等到平穩後,再找個機會以迅雷不及掩耳的速度,將重新編譯過的新版網站上傳到 FTP 蓋過舊的,這個過程理應只有幾個線上的使用者的 Session 失效,重新登入或是重新整理網頁就一切正常。
其實整個過程說來蠻單純的,風險應該不大,但讓我拖了一年半載遲遲沒有動手的原因,主要就是用 ASP.NET 2.0 去執行 ASP.NET 1.1 的向下相容性並沒有很好,實際跑起來還遇到蠻多問題的,一直讓我怯步。
首先,為了讓我的網站能同時支援以不同語言來顯示畫面 ( 目前同時支援 繁中 / 簡中 / 英文 的操作畫面 ),所以我網頁上的顯示文字是根據使用者下拉語言的選單動態產生的。我實做的方式很簡單,就是以一個 XML 檔儲存所有控件顯示的文字訊息。例如我有一個網頁 Index.aspx 上面還有一個顯示訊息的 Label ,其 ID = Label_Welcome,那我在繁中的 XML 語言檔 ( zh-tw.xml ) 就是如下:
<root>
<Index>
<Label_Welcome>歡迎光臨</Label_Welcome>
</Index>
</root>
如果是英文的語言檔 ( en-us.xml ) 就如下:
<root>
<Index>
<Label_Welcome>WELCOME!!</Label_Welcome>
</Index>
</root>
要讀取哪個檔案依下拉選單來決定,然後在每個 page 的 _PreRender() event 中依 [ 頁面類別名稱 + 控件 ID ] 組成的 XPath 去做顯示文字的讀取。但事實上,ASP.NET 在實際執行的時候,使用的不是你原本開發時撰寫的 class,而是它動態產生出來、繼承自你頁面 class 的新 class,而你當初編譯完成的 dll 其實也只是被它拿來當作 Code-Gen 引用參考的對象而已。
ASP.NET 動態產生出來的頁面執行 class 通常命名為以下的規則:[ 你撰寫的頁面 class
名稱 + "_aspx" ],為此,如上述的網頁實際上會產生 Index_aspx 的新類別,所以我在使用
this.GetType().Name 組合 XPath 時,需要做點後製加工。當時的做法是把後面尾巴產生出的 "_aspx" 硬切掉 ( this.GetType().Name.Replace("_aspx", string.empty) )。
但是不知何故,切換到 ASP.NET 2.0 的 runtime 執行,這個做法就失效了,整個頁面一整個空白,完全顯示不出任何文字。
由於 VS 2003 無法 Attach 上 .NET 2.0 的 runtime process 做 debug,我也懶得修改程式吐 OutputDebugString,所有就沉潛了好一陣子置之不理。直到前幾個月突然心血來潮,一研究才發現到了 ASP.NET 2.0,動態產生的 Code-Gen 的命名規則改了,不管你原本的名字的大小寫,它會一律轉為全部小寫!所以我原本的 Index 會變成 index_aspx ( 這是哪個微軟的白痴工程師做出來的向下相容性啊?連這種無關緊要 naming rule 的小地方都不相容... )。去掉尾巴後,index 和 Index 由於大小寫的差異造成 XPath 讀取不到正確的顯示文字。
知道問題所在後,改為直接使用 GetType().BaseType.Name 來解決此問題,寫法也比直接處理字串加工來得簡潔合理。
測試一段時間看來沒太大問題,放上網站後,填申請表請虛擬主機商的工程人員幫我切換到 2.0 runtime,然後就靜悄悄地等幾天,也無法預知他何時會做轉換。所以這就是為何我非得經歷 2.0 跑 1.1 後,再轉換到 pure 2.0 的過程,因為無法預料對方何時幫我轉換設定。
不過看來昨天主機商下午幫我調整過設定,我信箱中收到一堆系統錯誤信件,發現所有線上交易都已經停擺了數小時。
緊急調查下發現系統異常信件中提到:
"Exception Content :
System.Web.HttpUnhandledException: 已發生類型 'System.Web.HttpUnhandledException' 的例外狀況。
---> System.ArgumentException:
無效的回傳或回呼引數。
已在組態中使用 或在網頁中使用 <%@ Page EnableEventValidation="true" %> 啟用事件驗證。
基於安全性理由,這項功能驗證回傳或回呼引數是來自原本呈現它們的伺服器控制項。如果資料為有效並且是必需的,請使用 ClientScriptManager.RegisterForEventValidation 方法註冊回傳或回呼資料,以進行驗證。"
原來之前因為多層選單使用 ASP.NET 的 AutoPostBack 機制,嚴重讓使用者抱怨,故我修改為輸出許多 JScript 在瀏覽器端處理多重下拉選單的更新問題,但到了 2.0 ,這些 JScript 會動態修改原本 ServerControl 輸出時的內容造成和原本的 ViewState 產生差異,導致安全性驗證失敗。好在立刻在該頁面加註 [ EnableEventValidation="false" ] 關閉驗證就解決此問題。
但是事情還沒結束,早上又再次收到一堆系統錯誤訊息,發現日期字串無法識別?
"System.FormatException:字串未被辨認為有效的 DateTime。"
仔細檢查程式碼,才發現之前我在組合日期字串時錯用了斜線符號:[ 2008//06//27 10:00 ],我大概老眼昏花,誤以為這是跳脫字元所使用的反斜線 ( \ ) ,所以都重複鍵入兩次。但這樣的日期解析,在 .NET 1.1 是可以被正確識別的!
好吧,又一個緊急上版修正這個問題。
就這樣結束了嗎?不,這篇文章尚未完成的當下,我信箱內再度出現使用者抱怨的信件,說所有的電子訂單的金額都變成 0 ,要我緊急修復!God!再跳下去查,原來過去我為了增加效能,把部分 ServerControl 的 ViewState 給 Disable ,尤其是 TextBox 。
因為我發現即使關閉 TextBox 的 ViewState ,因為 Submit Button 按下去時,瀏覽器照樣會將使用者輸入的資料回傳, ASP.NET 照樣會在初始化的時候將將該 value 填入控制項內,並不會取不到資料,功效相同卻不需要額外存放 Text Value 以外的其他 Properties。
同樣,到了 2.0 ,這樣的做法式不行的,一切以 ViewState 驗證過的資料為主,於是乎沒有 ViewState 的 TextBox Control 瞬間被釘死。
方才又修正好這個問題,不僅要抱怨一下,微軟的一個升級,在小細節上實在有太多不相容與不一致性了。這次轉換後,短時間不敢再輕易嘗試原本計畫中的下半場步驟 ---> 整個網站重新編譯成原生的 ASP.NET 2.0 版本 ( 現在是在 2.0 Engine 下以相容模式跑 1.1 的編譯產物 ) 。