淺析ASP.NET編譯器
要深入理解ASP.NET動態(tài)控件,首先就要深入理解整個ASP.NET對頁面的處理過程,由你書寫好一個ASPX文件(可能還有一個code-behind文件)到你在瀏覽器中看到的HTML頁面,這中間到底發(fā)生了什么事。這其中的第一步就是解釋ASPX文件并進行編譯,也就是這篇文章要討論的內容。
由于ASP.NET編譯器本身就是一個大話題,所以我決定在本系列文章把這個題目再細分成幾篇文章來寫。開頭第一篇簡單敘述編譯過程中涉及的各個步驟,讓大家了解ASPX中的聲明性代碼和C#/VB.NET代碼如何合并在一起并編譯成assembly。在這篇文章之后,再深入了解編譯過程中的一些細節(jié),看看一個ASPX中聲明性定義的靜態(tài)控件到底是如何運行起來的。
開始講編譯過程了,首先大家來看兩張圖,這張是ASP.NET 1.x的編譯流程圖:
接下來這張是ASP.NET 2.0的編譯流程圖:
這兩張圖來自官方文檔ASP.NET 2.0 的內部變化,大家要注意到代碼嵌入(code-beside, inline)與代碼隱藏(code-behind)的編譯模式是不同的:代碼嵌入僅進行一次編譯,聲明性代碼與C#/VB.NET代碼都一起編譯到一個類里面;代碼隱藏則將聲明性代碼與C#/VB.NET代碼分開幾次進行翻譯/編譯,這些代碼之間是局部與局部(partial)的關系或是基類與派生類的關系。
圖上引人關注的地方就是代碼隱藏編譯時存在兩次的“繼承自”關系。第一次繼承是很好理解的,用過VS2002/2003的人都記得代碼中明確聲明本頁面的類繼承自Page類,那么第二次繼承又是怎么來的呢?
先把上面的問題放一邊,我們換一種思路來思考,重新想一想我們的C#/VB.NET代碼有什么。如果我們在ASPX中放上了一個TextBox,那么兩邊的代碼都會出現它的定義,ASPX代碼是<asp:TextBox id="myTextBox" runat="server" />,C#代碼是TextBox myTextBox = new TextBox();myTextBox.ID = "myTextBox";。然后我們在此TextBox的后面用HTML寫上<div>Please write down something</div>,那么這段HTML僅在ASPX中存在定義,而不在C#代碼中存在定義。
接下來我們將C#代碼給編譯了,然后用ASP.NET引擎運行它(確實能夠如此運行,但這不是我們當前關心的事),你猜我們能夠看到什么?我們應該能夠看到一個TextBox。至于后面那段文字呢,聰明的你應該馬上想到它沒在C#代碼中被定義的,所以不可能被看到。
現在我們明白到了,有一部分邏輯是僅僅在ASPX中有所定義,我們需要將它們添加到C#編譯結果上。如何添加這部分的邏輯?ASP.NET選擇了繼承機制,從C#編譯結果的那個類繼承,然后在派生類中加入僅在ASPX中定義的邏輯。至于作為聲明性語言的ASPX如何編譯成MSIL,則屬于下一篇文章討論的內容,在這里就不解釋了。
需要說明的是,這兩次編譯中的第一次必須手動進行的,例如在VS2002/2003中執(zhí)行編譯;第二次編譯在運行時進行自動進行。因此改動了ASPX無需重新手動編譯,而改動了C#/VB.NET代碼則需要手動編譯。
ASP.NET 2.0
上面我們解釋ASP.NET 1.1的代碼隱藏編譯時也提到了其中的問題,一個TextBox控件要在兩邊同時聲明,這明顯違反了DRY(Don't Repeat Yourself)原則。ASP.NET 2.0為了解決這個問題而引入了新的機制。
所謂的新機制就是C#代碼中的那個partial關鍵字,大家可能都習慣了它的存在,但有沒有人曾經想過一個這樣的Page繼承類的其他partial在哪里呢?如果你在VS2005中作一次項目內搜索,就會發(fā)現這個類的其它partial是不存在的,這時候你就該去看看官方文檔(例如我上面給出那個)。官方文檔會告訴你,另外一個partial就是ASPX,它們會好像兩個普通的partial文件那樣合并編譯,所以在ASP.NET 2.0中我們僅需要一次合并編譯就解決了所有問題。然后我要告訴你,官方文檔所說的是錯誤的,ASP.NET 2.0的編譯還是好像ASP.NET 1.1那樣,只不過根據ASPX中的控件定義生成對應C#定義的工作由IDE轉交給了ASP.NET編譯器,至于細節(jié)你可以去參考我之前寫的兩篇文章:《ASP.NET 2.0 解決了 Code-Behind 需要控件聲明同步的問題》與《ASP.NET 2.0 的編譯模型并非完全像 MS 說的那樣》。
在ASP.NET編譯器撿起了定義同步這項工作后,整個編譯過程就都在它的職責范圍內了,不再好像ASP.NET 1.x那樣先由C#/VB.NET編譯器負責隱藏代碼的編譯,再由ASP.NET編譯器負責二次編譯。既然ASP.NET編譯器同時負責兩次編譯,那就能夠省去第一次編譯手工進行的麻煩,編譯工作都由它在運行時負責就好了。
現在我們已經對整個編譯過程有了了解,大多數編譯步驟都很容易理解,無非是叫C#/VB.NET編譯器出來做些本職工作,只有一個除外:僅在ASPX中聲明的邏輯是如何被編譯為MSIL的,因為我們將此作為下一步深入理解的目標,并在下一篇文章中討論。
這里有一些簡單的問題或者是小實驗,通過它們可以加深大家對文章的理解,大家可以將答案直接寫在文章評論中。
1. 我在Web應用的根目錄新建了一個用戶控件MyUserControl.ascx,隱藏文件中定義類名稱為MyUserControl,我現在需要在頁面上動態(tài)加載此用戶控件,請問以下哪種方法正確?為什么?(提示:ASCX的編譯方式與ASPX類似)
1). this.Page.Controls.Add(new MyUserControl());
2). this.Page.Controls.Add(this.Page.LoadControl("~/MyUserControl.ascx"));
2. 在討論ASP.NET 1.1編譯的時候,我說到可以直接運行隱藏代碼編譯出來的類,并且說應該能看到一個TextBox。事實上這個TextBox可能也無法看到,不過我手上沒有VS2002/2003,所以沒辦法驗證。大家有興趣的話,可以自己去動手做一下實驗看看那個TextBox到底是否會出現。在實驗之前,讓我先說說如何讓隱藏代碼編譯結果直接運行:
1). 打開MSDN,找到IHttpHandler這個條目,然后看看它的示例代碼,以及如何在web.config中配置一個路徑使用特定的IHttpHandler。
2). 由于Page類本身實現了IHttpHandler,所以隱藏代碼編譯后的Page繼承類也一定是IHttpHandler,在web.config中配置一個使用IHttpHandler的路徑,并指向你要測試的隱藏代碼類。
3). 在瀏覽器中訪問你配置的路徑,你就能夠看到純隱藏代碼編譯后的執(zhí)行結果。
【編輯推薦】