淺析ASP.NET編譯器
要深入理解ASP.NET動(dòng)態(tài)控件,首先就要深入理解整個(gè)ASP.NET對(duì)頁(yè)面的處理過(guò)程,由你書(shū)寫(xiě)好一個(gè)ASPX文件(可能還有一個(gè)code-behind文件)到你在瀏覽器中看到的HTML頁(yè)面,這中間到底發(fā)生了什么事。這其中的第一步就是解釋ASPX文件并進(jìn)行編譯,也就是這篇文章要討論的內(nèi)容。
由于ASP.NET編譯器本身就是一個(gè)大話題,所以我決定在本系列文章把這個(gè)題目再細(xì)分成幾篇文章來(lái)寫(xiě)。開(kāi)頭第一篇簡(jiǎn)單敘述編譯過(guò)程中涉及的各個(gè)步驟,讓大家了解ASPX中的聲明性代碼和C#/VB.NET代碼如何合并在一起并編譯成assembly。在這篇文章之后,再深入了解編譯過(guò)程中的一些細(xì)節(jié),看看一個(gè)ASPX中聲明性定義的靜態(tài)控件到底是如何運(yùn)行起來(lái)的。
開(kāi)始講編譯過(guò)程了,首先大家來(lái)看兩張圖,這張是ASP.NET 1.x的編譯流程圖:

接下來(lái)這張是ASP.NET 2.0的編譯流程圖:

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

















