如何在ASP.NET應用程式中初始化

來源:互聯網
上載者:User

每個程式都需要初始化的過程,用來讀取配置或者設定一些運行環境(變數),對於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以上版本的整合模式下並不存在。

還有哪些初始化方法?

除了Global.asax中的Application_Start,還有哪些方法可以在ASP.NET程式執行初始化的任務呢?

目前我知道的還有另三種方法:
1. App_Code中的AppInitialize方法。
2. 寫個專用的HttpModule。
3. ASP.NET 4.0的PreApplicationStartMethodAttribute

App_Code中的AppInitialize方法

ASP.NET允許我們在App_Code中的任何一個類型定義一個AppInitialize方法,用它也能執行初始化的任務。

public class Class1{    public static void appInitialize()    {        AppInitializer.Init();            }}

如果我此時再次運行樣本程式(已注釋掉Global.asax中的代碼),會看到以下顯示:

顯然,我們期望的初始化代碼確實被調用了。

這個AppInitialize方法有什麼限制呢?
我們還是來看一下ASP.NET的原始碼吧:

internal class BuildResultMainCodeAssembly : BuildResultCompiledAssembly{    private MethodInfo FindAppInitializeMethod(Type t)    {        return t.GetMethod("AppInitialize",             BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,             null, new Type[0], null);    }

根據代碼我們可以發現AppInitialize方法的特點有:
1. 必須是一個公開的靜態方法:BindingFlags.Public | BindingFlags.Static
2. 方法名不區分大小寫:BindingFlags.IgnoreCase
3. 方法不允許有傳入參數:new Type[0]

HttpModule也能執行初始化的任務

由於HttpModule總是會在ASP.NET管線中被調用,所以,我們還可以用它來完成初始化的操作。

public class InitAppModule : IHttpModule{    public void Init(HttpApplication context)    {         //注意:Init事件可能被多次調用,所以這個方法會被多次調用。        AppInitializer.Init();    }

正如代碼注釋所說的那樣,這種調用代碼是不對的,除非你能接受初始化代碼被多次調用!

所以,我們應該按單例模式的思路來改寫代碼:

private static readonly object s_lock = new object();private static bool s_initOK;public void Init(HttpApplication context){    lock( s_lock ) {        if( s_initOK == false ) {            //保證初始化代碼只執行一次。            AppInitializer.Init();            s_initOK = true;        }    }}

如果你希望代碼簡單一點,還可以這樣實現:

public class InitAppModule : IHttpModule{    static InitAppModule()    {        AppInitializer.Init();    }    public void Init(HttpApplication context)    {        // 留個空方法,        // ASP.NET會調用這個方法,最後能觸發靜態方法的調用。    }
ASP.NET 4.0新增的初始化方法

為了讓一些類庫能自動執行一些初始化,ASP.NET提供了一種新方法,允許為程式集指定一個PreApplicationStartMethodAttribute

為了示範這種用法,我將前面的樣本(VS2008開發)移到一個類庫中(用VS2012開發)並設定類庫的命名空間為InitClassLibrary1。

然後,我添加了一個調用類:

namespace InitClassLibrary1{    public class Class1    {        public static void InitApp()        {            AppInitializer.Init();        }    }}

最後,我們可以在InitClassLibrary1類庫的AssemblyInfo.cs檔案中,增加一個Attribute

[assembly: System.Web.PreApplicationStartMethod(                typeof(InitClassLibrary1.Class1), "InitApp")]

當然了,你也可以直接像下面設定,免得多建立一個類型出來:

[assembly: System.Web.PreApplicationStartMethod(                typeof(InitClassLibrary1.AppInitializer), "Init")]

這樣設定後,再運行網站,你也能發現我們的初始化代碼確實運行了:黃頁仍然在顯示。

各種初始化方法的差別

前面介紹了4種在ASP.NET執行初始化的方法,你或許想知道它們到底有哪些區別呢?

由於它們都能實現初始化的操作,它們的差別也只有執行的時刻不同而已,
我們可以用簡單的方法區分它們的調用位置:看異常的呼叫堆疊資訊。

AppInitialize方法異常時的呼叫堆疊資訊:

HttpModule異常時的呼叫堆疊資訊:

PreApplicationStartMethodAttribute異常時的呼叫堆疊資訊:

Global.asax的Application_Start事件處理器的調用方式則不同,ASP.NET採用了反射調用,
當異常發生只保留了內部異常,我們看不到呼叫堆疊(不信的話,自己去試試)。

沒關係,既然ASP.NET不告訴我們呼叫堆疊資訊,我們自己也可以去取,請看下面的代碼:

void Application_Start(){    System.Diagnostics.StackTrace stack = new System.Diagnostics.StackTrace();    System.IO.File.WriteAllText("h:\\Application_Start_stack.txt", stack.ToString());}

再開啟檔案看一下吧。

說明:Global.asax的Application_Start事件處理器還有幾種等效的方法:

// 這二個方法都可以實現與Application_Start(object sender, EventArgs e)相同的行為。void Application_OnStart(){}void Application_Start(){}

根據以上分析,可以可以得知:
1. AppInitialize和PreApplicationStartMethodAttribute指向的方法被調用的時機發生在ASP.NET建立宿主環境時,屬於比較早的時刻。
2. Application_Start和HttpModule的調用時刻要晚一點。

這個結論有用嗎?
其實我也感覺意義不大,不過分析它僅僅為了滿足我的求知慾和好奇心而已,你是否也有這樣的好奇心呢?
如果你仍然好奇想知道這4種方法的執行時機的先後順序,我也能告訴你:
1. PreApplicationStartMethodAttribute指向的方法。
2. App_Code中的appInitialize方法。
3. Application_Start。
4. HttpModule

再補充一點:在開發環境中,當我們編譯網站時,PreApplicationStartMethodAttribute指向的方法可能會被調用,這處決於類庫的程式集是否發生了修改。

到底該選擇哪種初始化方法?

今天給大家介紹了4種在ASP.NET中執行初始化的方法,或許有些人會想:到底該選擇哪種初始化方法呢?

的確,方法越多越讓人迷惑。
下面的觀點僅代表我個人的建議,你也可以根據自己的喜好來選擇。

1. 優先選擇Application_Start(雖然IIS的傳統模式下需要多寫點代碼),因為任何人找初始化代碼時都會想到那裡,便於其他人維護。

2. AppInitialize方法雖然使用簡單,但它並不適合於WebApplication項目

3. PreApplicationStartMethodAttribute只支援ASP.NET 4.0以上版本,且尤其適合於類庫的內部初始化

4. 當以上方法都不可行時,HttpModule將成為最後的救命稻草,它適合所有ASP.NET版本。

點擊此處下載範例程式碼

如果,您認為閱讀這篇部落格讓您有些收穫,不妨點擊一下右下角的【推薦】按鈕。
如果,您希望更容易地發現我的新部落格,不妨點擊一下右下角的【關注 Fish Li】。
因為,我的寫作熱情也離不開您的肯定支援。

感謝您的閱讀,如果您對我的部落格所講述的內容有興趣,請繼續關注我的後續部落格,我是Fish Li 。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.