程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> .NET實例教程 >> 深入剖析ASP.NET的編譯原理之二:預編譯(Precompilation)

深入剖析ASP.NET的編譯原理之二:預編譯(Precompilation)

編輯:.NET實例教程
 寫在前面的話:有些東西,還真只有深入剖析了其理論後,才明白微軟的煞費苦心啊!以前只知道怎樣去寫代碼,我想現在重要的是如何去寫代碼,而且要知道自己寫的代碼是如何執行的!個人認為,從事.NET開發也不是像人們想象的那樣,只需要拖控件,然後寫基於事件驅動的代碼就萬事大吉了,其實,越是封狀的東西越多,就越有必要知道微軟在底層是如何為我們封裝的,才更加有利於我們去利用他已經封裝好的東西!本文作者對ASP.Net的編譯原理的確剖析的很透徹,值得學習。轉載地址:http://www.cnblogs.com/artech/archive/2007/05/26/760292.Html

在本篇文章的第一部分:[原創]深入剖析ASP.NET的編譯原理之一:動態編譯(Dynamical Compilation),詳細討論了ASP.Net如何進行動態編譯的,現在我們來談談另外一種重要的編譯方式:預編譯(Precompilation)。

1.為什麼要進行預編譯

ASP.NET 2.0的編譯方式大體可以分成兩種:動態編譯和預編譯,要回答為什麼要進行預編譯,我們先要看看動態編譯有什麼不好的地方。我們回顧一下上一篇介紹的ASP.NET進行動態編譯的簡單的流程:當來自Browser的一個基於aspx的Http request抵達Web server,IIS handle這個request,通過分析注冊在IIS中的Application Mapping,將Request 傳給aspnet_isapi.dll ISAPI extension。ISAPI extension通過HttpRuntime進入Http Runtime Pipeline,HttpRuntime為每個Request創建一個單獨的HttpContext對象,用於保存request的Context信息。在Http Runtime Pipeline中,Http request會被注冊的一系列的Http module處理,比如OutputCache Module,Session Module,Authentication Module,Authorization,ErrorHandler Module等等。在Pipeline的終端,ASP.Net需要根據request創建對應的HttpHandler對象來處理該Request,並將生成結果Response到ClIEnt。對於一個基於ASPx的Http request,對應的Http handler對象一般就是一個System.Web.UI.Page對象。

ASP.NET會先判斷對應的Page type是否存在於被Cache的Assembly中,如果存在,直接創建Page對象,否則ASP.Net會先對該Page的相關的Source code (包括code behind,Html等等) 進行編譯,我們也說過這種編譯是以Directory為單位的,也就是說,處於同一個Directory下的需要編譯的文件會被編譯到同一個Assembly中。編譯生成的Assembly會被Cache,用於後續的Request。

正是因為對資源的首次訪問會導致一次編譯(這樣說不太准確,因為動態編譯是以directory為單位進行的,應該是對某個Directory下的資源進行首次訪問),這樣會嚴重降低Web Application的響應速度。所以我們為了避免這種情況,需要預先對web site進行編譯,所以提高web site的響應是進行預編譯的最重要的原因。

同時動態編譯就因為Web server上放置的是Source code,而且他們是可被修改的。而對於一個開發完畢的Web Application,我們更希望以Binary Assembly的方式進行部署,這樣Server上部署的都是Binary Assembly,不怕被別人篡改而導致系統的崩潰,從知識產權來講,也更利於保護商業秘密。這也是我們為什麼要進行預編譯的另一個原因。

下面我們就來講講如何進行預編譯,以及與編譯背後的原理。同時在這裡我需要特別提出的是,在上一部分講的一些術語和原理,比如Preservation file,FastObjectFactory,同樣適用於預編譯,重復的內容,在這裡就不必再介紹了。同時我也將沿用上一部的Sample。如果想看看相關的內容,請參閱[原創]深入剖析ASP.Net的編譯原理之一:動態編譯(Dynamical Compilation)。

2In Place Pre-compilation V.S. Pre-compilation for Deployment

對於預編譯,又可以分為In Place Pre-compilation(本地預編譯)和Pre-compilation for Deployment(部署預編譯),In Place Pre-compilation很簡單,實際上就是把整個Web site編譯到我們一個臨時的目錄下面,這個臨時目錄也就是我們在介紹動態編譯提到的那個臨時目錄。而且這種編譯的方式,包括生成的文件也和動態編譯完全一樣,唯一不同就是編譯的時間:預先編譯,編譯的范圍:整個Web site。這種編譯就是你常用的在VS中的build。這種編譯方式一般用於開發階段。

以部署為目的的編譯是我們今天討論的重點,下面我們就著重來討論Pre-compilation for Deployment。

注:在ASP.NET中的編譯都是通過一個叫做aspnet_compiler的工具執行的,該工具隨ASP.Net 2.0一起發布,你完全可以利用此工具以命令行的方式的執行編譯,並通過傳遞不同的命令行開關設置不同的編譯選項。該工具被置於VS中,使你可以利用VS進行可視化的編譯。

3Non-updatable Pre-compilation V.S. Updatable Pre-compilation

ASP.NET 2.0為我們提供了幾種不同方式的預編譯和部署。為了弄清楚這些預編譯和部署方式,我們先來回顧一下ASP.NET 1.x下的編譯方式。我們知道在ASP.NET 1.x時代對整個Web site進行編譯,實際上我們只會對所有C#和VB.NET等後台代碼進行編譯,並生成一個單一的Assembly。而Web page的aspx是不會參與編譯的。所以當我們訪問一個Web page的時候,ASP.Net必須對ASPx進行動態編譯。

這一切之所以能夠進行是因為Web page采用的是ASPx + code behind的模式。

<%@ Page Language="C#" AutoEventWireup="false" Codebehind="Default.ASPx.cs"   Inherits="Default" %>

public partial class Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
    }
}

 

從上面我們可以看到aspx和Code behind是一種繼承的關系,aspx繼承和它對應的Code Behind。ASP.Net可以把Code behind和ASPx分開進行編譯,把它們編譯到不同的Assembly中。我們就是上面的Code為例,
我們現在若對該Web site進行編譯的話,Default.aspx.cs會被編譯到一個Assembly中,假設這個Assembly為App_Web.dll. 我們把該Dll和aspx部署到Production Server上。如果我們現在訪問defaut.aspx。ASP.Net
會對aspx進行動態編譯,生成的Assembly可以暫時成為App_Web_aspx.dll。對於Default.ASPx,如果我們如C#代碼來描述的話,應該像下面一樣定義:

public class default_ASPx:Default
{
    
}

這種編譯方式,我自己把它叫做對asXx的動態編譯。在ASP.Net2.0 中也沿用了這種編譯方式。這種編譯方式的主要特征是對Code behind和所有的後台代碼進行預編余,ASPx(確切地說應該是asXx:asax,asmx,asax等)原樣部署。由於這種方式的預編譯,asXx是可以修改的(當然這種修改是有一定限制的,因為code behind已經編譯好了,所以這種修改只可能是和code behind無關的修改),所以又叫做Updatable Pre-compilation。

除了Updatable Pre-compilation之外,ASP.Net還提供另外一種高效的預編譯方式,Non-updatable Pre-compilation,之所以叫做不可修改的預編譯,這是因為:這種編譯方式把asXx、Code behind、後台代碼甚至是部分Resource都進行預編譯,從而避免了運行時對asXx的動態編譯,從而最大程度地提高了整個Web site的響應。在部署的時候,我們除了把生成的Assembly進行部署之外,所有的通過編譯生成的asXx也必須進行部署。 不過需要特別說明的是,此時的asXx文件僅僅是一個占位的文件而已,它裡面不具有任何Html。

4Partial class

在ASP.NET 1.x,由於采用的aspx + code behind的機制,對於任何一個Web page或者其他ASP.Net 基於axXx的對象來說,都是由兩個文件、兩個class組成。兩個文件是指axXx和code behind,兩個class是指Code behind定義的繼承自System.Web.UI.Page的class,和一個繼承自它的由axXx生成的class。

對於使用過ASP.Net 1.x來說,一定會很熟悉這樣一種情況:對於每個在ASPx中通過Html定義的Server Control,在Code behind中必須具有一個對應的protected成員,否則你不能通過編程的方式訪問這個Server control。以不同方式呈現的同一個Server control通過ID關聯起來,如果在Code behind中改了Server control的ID,Server control的Server端的Event handler將會失去原有的作用。

但是在ASP.NET 2.0來說,這種情況發生了改變,在aspx中的Server control在Code behind中卻沒有相應的成員變量,但是我們可以毫無障礙地訪問到每個Server control。這使得我們的code behind更加簡潔,通過避免了Server control在ASPx和code bebind中的不匹配的問題。這一切都得益於.Net Framework 2.0提供的partial class的機制:把同一個class分布於不同文件中進行定義。有了這個概念,我們來看ASP.Net 2.0的code behind機制。

比如我們有這樣的一個Page:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.ASPx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xHtml1-transitional.dtd">
<html XMLns="http://www.w3.org/1999/xHtml" >
<head runat="server">
    <title>Default</title>
    <link href="Style.css" rel="stylesheet" type="text/CSS" />  
</head>
<body>
    <form id="form1" runat="server">  
    <div>          
           <ASP:Button runat="server" ID="btnRefresh" Text="Refresh" OnClick="btnRefresh_Click" />   
    </div>
    </form>
</body>
</Html>

Code behind如下:

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
    }
    protected void btnRefresh_Click(object sender, EventArgs e)
    {
        this.Response.Write("The click event of \"Refresh\" button is fired");
    }
}

而實際上,ASP.Net會為我們創建一個隱藏的.cs文件(這個文件有人把它稱之為Sibling partial class):

public partial class _Default: IRequiresSessionState
{
    protected Button  _ btnRefresh;
}

這個文件會隨著aspx文件的改變而動態變化,所以code behind中的Server control永遠和aspx中的Server control是完全匹配的。所以我們說ASP.Net 2.0的Page是由3個文件、兩個class組成的。

5.編譯的粒度和Assembly的命名

到現在為止,我們所講的ASP.NET的預編譯都是以Directory為單位的,同一個Directory下的所有需要編譯的文件被編譯到同一個Assembly中。ASP.Net還支持以Page為單位的預編譯,也就是每個Page編譯成一個Assembly。

在默認的情況下,ASP.NET預編譯生成的Assembly名稱是隨機生成的,也就是每次生成的Assembly都具有不同的name。所以我們在部署Web site的時候,一般需要把原來的Assembly刪掉,再部署新的Assembly。不過ASP.Net為我們提供了另外一種選擇,使得每次編譯生成的Assembly具有相同的名稱,這樣我們部署的時候就可以直接把新的Assembly 拷貝到Production Server上,自動覆蓋掉同名的Assembly。

6  Sample

我們沿用上一部份是用的Sample,我們通過采用不同的預編譯方式看看程序將如何運行。

    6.1 Non-updatable Pre-compilation

我們采用如上圖所示的默認的發布方式,ASP.Net 將會進行Non-updatable Pre-compilation。浏覽目標文件夾,我們會發現如下的文件結構:

除了多了一個Bin目錄和PrecompiledApp.config之外,整個結構和Source code中的結構完全一樣。通過上面的分析,我們知道這種預編譯方式是將asXx、code behind、後台代碼和Resource一起編譯成Assembly。我們說過對於這樣的預編譯方式,aspx僅僅是一個站位的文件而以,其中HTML已經沒有任何意義了,那麼對於編譯後的aspx中到底是什麼東西呢。我們來一探究竟。打開每個ASPx都是一段如下如下一樣文字,並無任何Html。

This is a marker file generated by the precompilation tool, and should not be deleted!

PrecompiledApp.config裡面具有一段簡短的configuration,表明version和是否可以進行進一步的修改。

<precompiledApp version="2" updatable="false"/>

所有的Assembly被編譯到Bin目錄中,我們來看看到底生成了一些什麼樣的文件在Bin目錄中。

在Bin目錄由兩類文件構成:Assembly和以complIEd作為擴展名的Preservation file。Preservation file的內容和作用在第一部分已經詳細介紹過了,相信大家不會感到陌生。Preservation file在這裡和動態編譯所起的作用一樣。唯一有一點不同的是,他的結構更加簡潔,去掉的Dependence file的列表,因為對於Non-updatable Pre-compilation來說,每個Page的以來的文件都是不可更改的。

<preserve resultType="6" virtualPath="/Artech.ASPNETDeployment/App_Code/" hash="439abe7d" filehash="" flags="140000" assembly="App_Code" />

我們來運行以下程序,和動態編譯情況下的輸出結果比較,看看有什麼不同。我們照例先運行Default Page。

輸出的結果印證我們前面的討論:處於同一個目錄下的Default 和Default2被編譯到同一個Assembly中,關注於處理邏輯的code behind的class name為Default和Default2,關注於可視化界面render的aspx對應的class name被加上的_aspx後綴,如果對default_aspx和default2_aspx進行Reflect的話,你會發現他們分別繼承Default和Default2,而後者直接繼承自System.Web.UI.Page。所以default_aspx和default2_ASPx是真正的意義上基於Web page的Http handler。像動態編譯一樣,預編譯生成一個基於Assembly的FastObjectFactory Type,對該對象的描述請參照第一部分。

有了前面的理論基礎,相信大家已經猜到這時候,我浏覽Part I下的Page1和Page2時的輸出是什麼樣子,由於預編譯是以目錄為單位的,我們對Part I下的任何一個page的訪問,都會加載相同的Assembly,所以此時對這兩個Page的訪問會得到一樣的輸出結果:


   6.2 Updatable Pre-compilation

接下來我們來對Web Page進行Updatable Pre-compilation,相關的編譯設置如下:選擇Allow this precompiled site to be updatable。

生成的文件及其結構和進行Non-updatable Pre-compilation,不同的有以下3點:

1.PrecompiledApp.config:updatable被設置為true。

<precompiledApp version="2" updatable="true"/>

2.asXx和我們進行開發時內容一樣,例如ASPx包含的就是Html,我們可以在部署之後對他們進行和code behind無關的修改。

3.Preversation file中有加上了Page對應的dependence file列表。

<?XML version="1.0" encoding="utf-8"?>
<preserve resultType="9" virtualPath="/Artech.ASPNETDeployment/App_GlobalResources/" hash="439abe7d" filehash="ff21249472dbf6cb" flags="140000" assembly="App_GlobalResources" resHash="1cba48dd56e28538" />

我們來運行一下Web site,看看現在的輸出結果又有何不同。首先打開Default Page:


通過上圖,我們發現此時加載了兩個相關的Assembly。我們來分析一下為什麼會這樣。在分析Updatable Pre-compilation時,我們說過:asXx是不會參與編譯的,只有他們的code behind, 所有的後台代碼,資源文件才會參與編譯。對於一個page 來說,page的code behind被編譯到Assembly中,aspx則不會。Aspx在運行時實行動態編譯,所以aspx是可被修改的。在本例中,我們訪問Default Page,ASP.Net先對aspx進行編譯,其對應的class name為default_aspx,由於default_ASPx繼承於Default,並且Default存在於預編譯生成的Assembly中,所以這個Assembly被加載進來。

由於同一個page最終被編譯到兩個不同的Assembly中,所以我們此時訪問Part I中的Page1或者Page2,又會有兩個Assembly被加載進來:


6.3 Page為單位進行預編譯

前面我們進行的都是以directory為單位的預編譯,現在我們縮小編譯的粒度,以Page為單位進行編譯。我們選擇了“Use fixed naming and single page assemblIEs”選項。那麼現在進行的是基於單個page的non-updatable pre-compilation。通時由於采用的是fixed naming的編譯方式,每次進行編譯生成的Assembly的名稱都是一樣的。

現在我們來看看,編譯之後生成的Assembly:

我們看到編譯器為每個Page生成了一個單獨的Assembly。此時運行程序,你看到的又將不同。如果此時你訪問Default Page,你將看到:

是不是和上面不同,Assembly只有Default對應的兩個Type,沒有了處於同一個目錄下的Default2的Type。因為Default2有它獨自的Assembly .所以你該會想到,如果我們現在每訪問一個沒有被訪問過的Page,就會有一個新的Assembly被加載。比如我訪問Part I下的Page1:

 6.4編譯強類型的Assembly

我們知道可以通過一個Public key/Private key pair對Assembly進行簽名,進而把它部署到GAC中,我們來看看如何做。

首先我通過SN.exe生成Public key/Private key pair並保存到一個文件中(比如D:\MyKey.keys),然後進行如下的編譯設置

那麼我們進行編譯就會生成強類型的Assembly。我們可以運行我們的程序來證明:

每個Assembly有具有一樣的PublicKeyToken,因為我們使用的一樣的Public key/Private key pair進行對每個Assembly簽名的。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved