每個程式都需要初始化的過程,用來讀取配置或者設定一些運行環境(變數),對於ASP.NET程式來說,又該在哪裡執行初始化的任務呢?
我想應該絕大多數人都知道在Global.asax中執行初始化的過程,然而有些細節是我們需要關注的。
本文用例
在這篇部落格的範例程式碼中,AppInitializer包含了網站的初始化的實現代碼:
- public static class AppInitializer
- {
- public static ConnectionStringSettings MyNorthwindConnectionSetting { get; private set; }
-
- public static void Init()
- {
- // 讀取連接字串。
- LoadConnectionString();
-
- // 設定SQLSERVER緩衝依賴通知。
- SetSqlDependency();
-
- // 其它的初始化操作。
- OthersInit();
- }
-
- static void LoadConnectionString()
- {
- ConnectionStringSettings setting = ConfigurationManager.ConnectionStrings["MyNorthwind"];
- if( setting == null )
- throw new ConfigurationException("沒有配置MyNorthwind連接字串。");
-
- if( string.IsNullOrEmpty(setting.ConnectionString) )
- throw new ConfigurationException("沒有為MyNorthwind連接字串指定內容。");
-
- if( string.IsNullOrEmpty(setting.ProviderName) )
- throw new ConfigurationException("沒有為MyNorthwind連接字串指定ProviderName 。");
-
- // 儲存讀取到的連接字串,供程式使用。
- MyNorthwindConnectionSetting = setting;
- }
-
- static void SetSqlDependency()
- {
- // 判斷SQLSERVER版本是否為 2005以上版本,
- // 是否開啟Service Broker的檢查代碼就不列出了。
-
- SqlDependency.Start(MyNorthwindConnectionSetting.ConnectionString);
- }
-
- static void OthersInit()
- {
- // 其它的初始化操作。
-
- // 例如:
- // 1. 載入必要的快取資料。
- // 2. 檢查上傳目錄是不存在。
- // 3. ...................
- }
- }
這段代碼的意圖很清楚,一定要確保正確的配置了資料庫連接字串,否則以異常的形式報告出來。
樣本程式還有一個頁面,Default.aspx
- <body>
- <form id="form1" runat="server">
- <div>
- <h1>User Login</h1>
- </div>
-
- <p style="line-height: 150%;">
- UserName: <asp:TextBox ID="txtUserName" runat="server" Width="200px" Text="Fish Li"></asp:TextBox><br />
- Password: <asp:TextBox ID="txtPassword" runat="server" Width="200px" TextMode="Password"></asp:TextBox><br />
- <asp:Button ID="btnLogin" runat="server" Text="登入" OnClick="btnLogin_Click" />
- </p>
- </form>
- </body>
其實就是一個登入頁面,後台代碼為:
- protected void btnLogin_Click(object sender, EventArgs e)
- {
- bool ok = false;
-
- using( SqlConnection connection
- = new SqlConnection(AppInitializer.MyNorthwindConnectionSetting.ConnectionString) ) {
-
- connection.Open();
-
- // 其它的資料庫操作。
-
- ok = true;
- }
-
- if( ok )
- Response.Redirect("Default2.aspx");
- }
你沒有想到的Global.asax怪事!
或許有些人會這樣寫他們的初始化代碼:
- void Application_Start(object sender, EventArgs e)
- {
- //在應用程式啟動時啟動並執行代碼
- try {
- AppInitializer.Init();
- }
- catch( Exception ex ) {
- LogException(ex);
-
- // .....................
- }
- }
這段代碼有什麼問題呢?
其實問題的線索在於:為什麼要加try....catch語句,是因為知道可能會發生異常嗎?
如果真有異常情況發生,這樣處理後,後續的請求是不是會發生各種想像不到的錯誤?
顯然這裡不能吃掉異常,要不然後面的請求肯定會有問題,因為它們依賴的設定沒有正確的初始化。
好吧,那我去掉 try.....catch語句,這樣總該行了吧:
- void Application_Start(object sender, EventArgs e)
- {
- //在應用程式啟動時啟動並執行代碼
-
- AppInitializer.Init();
- }
還是看來一下真實的運行情況吧。
噢,抱歉,我還真忘記了配置連接字串,這個異常提示太給力了。
現在就加上連接字串嗎?
別急,想像一下,如果這個網站是一個真實的線上網站,會是什麼情況呢?
答案有二種:
1. 另一個使用者也發起了一次請求。
2. 目前使用者看到錯誤頁面後,重新重新整理了一次當前頁面。
現在我用Opera來扮演第二個瀏覽使用者吧,還是開啟同樣的網址。
太奇怪了,第二個使用者居然能開啟頁面,好吧,讓他登入試試。
結果第二個使用者看到的錯誤情況和第一個使用者完全不同。
如果此時第一個使用者重新整理他的瀏覽器,發現頁面又可以顯示了,然而登入時,會看到與第二個使用者一樣的異常資訊。
這個範例程式碼實在太簡單了,我想維護人員根據NullReferenceException這個線索找下去,很快就能找到答案。如果初始化代碼再複雜一些,比如SetSqlDependency()中出現異常呢,那麼程式仍然能夠正常運行,但是我們期望的緩衝依賴可能就沒有效果了,最終可能會產生效能問題,排查的難度就會大多了。
記得以前做項目時,就遇到過這種情況,當時感到很奇怪,為什麼重新整理一下就沒黃頁了,不過後面的錯誤就很折騰人了,最終也讓我總結了這個教訓。所以我建議:如果在初始化階段出現了異常,乾脆就別讓程式繼續運行了,每個請求都直接顯示黃頁,直到排除故障為止。
如何保證初始化異常一直顯示?
當初始化發生異常時,如何保證初始化異常一直顯示呢?
方法其實並不難,我們需要修改一下代碼:
- private static Exception s_initException;
-
- void Application_Start(object sender, EventArgs e)
- {
- try {
- AppInitializer.Init();
- }
- catch( Exception ex ) {
- // 記下初始化的異常。
- s_initException = ex;
- }
- }
-
- protected void Application_BeginRequest(object sender, EventArgs e)
- {
- // 如果存在初始化異常,就拋出來。
- // 直到開發人員發現這個異常,並已解決了異常為止。
- if( s_initException != null )
- throw s_initException;
- }
現在不管有多少個使用者來訪問,或者第一個訪問者重新整理瀏覽器多少次,都會看到同樣的異常資訊:
說明:Global.asax的這個問題在IIS7以上版本的整合模式下並不存在。