天天看點

ASP.NET 頁面的伺服器端

作為一名 ASP.NET 開發人員,您可能非常清楚 ASP.NET 如何處理 .aspx 資源中的代碼,如何對标記進行分析并将其動态轉換成 Visual Basic ® 或 C# 類,等等。但是接下來呢?ASP.NET 生成的檔案儲存在哪裡?如何利用它們滿足頁面請求?從上個月起,我開始關注這一過程。 在本月的内容中,我将對伺服器上所發生的操作進行分析,以便您能夠避免某些常見的問題隐患。 我将讨論 ASP.NET 臨時檔案的存儲以及動态生成的用于為頁面響應提供服務的類的源代碼。此外,我還将建構一個可以與任何 ASP.NET 2.0 或 ASP.NET AJAX(原代号為“Atlas”)應用程式一同使用的資料總管工具,以檢視和調試您的頁面執行的實際代碼。但在此之前,您需要了解幾個事項。(和上月專欄一樣,本部分内容主要以那些沒有文檔記錄的 ASP.NET 工作原理細節為基礎來展開。這些實施細節在未來的 Microsoft ® .NET Framework 版本中可能會發生變化。)

Temporary ASP.NET Files 檔案夾中儲存的是什麼内容? ASP.NET 頁面請求的處理過程需要使用一些臨時檔案。當您在 Web 伺服器上安裝 ASP.NET 2.0 時,所建立的檔案夾層次結構如下:

%WINDOWS%\Microsoft.NET\Framework\v2.0.50727
      

這裡的版本号指的是 ASP.NET 2.0 的零售版。ASP.NET 的每個釋出版本(包括每個過渡性的内部版本)都有一個唯一的版本号,并且會建立不同的檔案夾樹,以便支援不同版本的并行執行。是以,您務必要指定您的應用程式所适用的 ASP.NET 版本,這一點極為重要。在 ASP.NET 1. x 和 ASP.NET 2.0 下運作的應用程式基于實體形式上獨立的檔案夾。在 Microsoft.NET\Framework 檔案夾下,您會找到與已安裝的 ASP.NET 版本數相同數量的 vX. X. XXXX 子檔案夾(請參見 圖 1)。

ASP.NET 頁面的伺服器端

圖 1  ASP.NET 1.0、1.1、2.0 和 3.0 運作庫檔案 (單擊該圖像獲得較大視圖) 在已安裝版本的根檔案夾下,您會看到許多子目錄。CONFIG 檔案夾包含計算機配置檔案,其中包括 machine.config 和用于所有站點的基本 web.config 檔案。名為 ASP.NETWebAdminFiles 的檔案夾包含構成網站管理工具的源檔案,您可從 Visual Studio ® 2005 内部運作該工具。最後,Temporary ASP.NET Files 檔案夾包含為頁面和資源提供服務而建立的所有臨時檔案和程式集。要找到為您的 Web 頁面動态建立的檔案,您需要檢視此檔案夾子樹。請注意,Temporary ASP.NET Files 目錄是存放動态建立的檔案的預設位置,但可以使用 web.config 檔案中的 <compilation> 部分按應用程式對其進行配置:

<compilation tempDirectory="d:\MyTempFiles" />
      

當應用程式第一次在計算機上執行時,在臨時檔案目錄下就會建立一個新的子檔案夾。編譯子檔案夾的名稱與應用程式的 IIS 虛拟目錄的名稱相同。如果您隻是使用 Visual Studio 2005 内嵌的 Web 伺服器測試應用程式,那麼子檔案夾會采用該 Web 應用程式的根檔案夾的名稱。如果您從 Web 伺服器的根檔案夾調用頁面,您将在根子檔案夾下找到它們的臨時檔案(請參見 圖 2)。

ASP.NET 頁面的伺服器端

圖 2  Web 測試伺服器上的 WebApp (單擊該圖像獲得較大視圖) 在應用程式的編譯子檔案夾下,有一組使用散列名稱的目錄。此處顯示了通常可以找到臨時檔案的路徑。(最後兩個目錄包含的是假名稱,但實際顯示的就是這樣的名稱。)

\v2.0.50727\Temporary ASP.NET Files\MyWebApp\3678b103\e60405c7
      

您可以使用以下語句,以程式設計的方式檢索指定應用程式的臨時檔案所在的子檔案夾的路徑:

Dim tempFilesFolder As String = HttpRuntime.CodegenDir
      

ASP.NET 會定期在應用程式發生改變、需要重新編譯時清理編譯檔案夾并删除陳舊的資源,但 Temporary ASP.NET Files 目錄下的子樹的大小可能會顯著地增加,在測試計算機上更是如此。 作為管理者,您應密切關注 Temporary ASP.NET Files 下的目錄,并確定所有目錄都是與目前活動的應用程式有關的。如果您無意間删除了一個處于活動狀态的應用程式的子樹,不必驚慌。您将丢失所有預編譯的頁面和資源并會将應用程式重置到其最初的編譯狀态;但下一個請求将觸發對每個頁面或一批頁面(具體取決于配置)執行新的編譯過程,是以最終不會丢失任何資訊或頁面,隻不過使用者在處理下一個請求時将感覺到首次命中延遲。現在,我們來看某一應用程式的編譯檔案夾的内容。

保留檔案 對于應用程式中的每個頁面,頁面編譯程序會生成一個下述名稱的檔案:

[page].aspx.[folder-hash].compiled
      

[page] 占位符代表 .aspx 資源的名稱。[folder-hash] 占位符是一個散列值,它使檔案名保持唯一,避免與原本屬于其他檔案夾的同名檔案混淆。這種檔案稱為保留檔案,因為它們包含有重要的資訊,這些資訊可幫助 ASP.NET 運作庫快速檢索程式集以及檢索将用于為頁面請求提供服務的 HTTP 處理程式的類型名稱。此外,保留檔案還包含一個檔案散列值,用于檢測自從上次通路後檔案的内容是否發生了改變。 構成某一應用程式的所有 .aspx 頁面在同一個臨時檔案夾中進行編譯,即使它們名稱相同且位于不同的檔案夾中也是如此處理。這一點如何實作?假設您的應用程式包含兩個名為 test.aspx 的頁面,位于不同的檔案夾 - Folder1 和 Folder2 中。兩個頁面将在同一臨時檔案夾中進行編譯,但可以通過它們的散列值對其進行區分,由于散列值是根據路徑資訊而不隻是檔案名計算出來的,是以它們的散列值是不同的。 因而最終,兩個 test.aspx 頁面的保留檔案名隻在檔案夾散列值部分有所不同:

Test.aspx.cdcab7d2.compiled
Test.aspx.9d86a5d7.compiled
      

散列值的内部存儲緩存使 ASP.NET 運作庫可以識别任何指定頁面 URL 的散列值并快速找到相應的保留檔案。如果沒有找到保留檔案,ASP.NET 會動态編譯頁面。當您部署沒有預編譯的應用程式時就會發生這種情況。另一方面,當您對一個站點進行預編譯時,每個組成頁面的保留檔案被建立并放置在 Bin 檔案夾中。 保留檔案為純 XML 檔案。 圖 3 顯示了一個示例保留檔案的内容。

ASP.NET 頁面的伺服器端

  Figure 3 示例保留檔案

<?xml version="1.0" encoding="utf-8"?>
<preserve resultType="3" virtualPath="/WebSite2/Default.aspx" 
          hash="bce9c4d05" 
          filehash="2831dc3af5add65c" 
          flags="110000" 
          assembly="App_Web_ap-sequk" 
          type="ASP.default_aspx">
    <filedeps>
        <filedep name="/WebSite2/Default.aspx" />
        <filedep name="/WebSite2/Default.aspx.vb" />
    </filedeps>
</preserve>

      

圖 4 具體列出了檔案的屬性。<fileDeps> 部分列出了目前頁面所依賴的檔案。對任何依存關系所做的任何改動都将導緻頁面重新編譯。FileHash 值代表依存關系狀态的快照,而 Hash 代表目前頁面檔案狀态的快照。值得注意的是,當您停止或重新啟動 Web 應用程式時,完全基于檔案更改通知來檢測檔案動态更改的機制會失敗。按照散列值儲存頁面和依存關系的狀态,使您可以随時檢測到更改。

ASP.NET 頁面的伺服器端

  Figure 4 保留檔案中的屬性

屬性 描述
Assembly 包含已編譯頁面的程式集的名稱。
FileHash 散列值,用于檢查頁面所依賴的所有源檔案中的更改。
Flags 整數值,用于儲存内部操作狀态,如建構一個用于監視所依賴的檔案中的更改的散列值。
Hash 用于檢查目前頁面是否發生了任何改動的散列值。
ResultType 訓示為其建立此保留檔案的生成結果類型。可能的值來自名為 BuildResultTypeCode 的非公共枚舉對象。
Type 代表已編譯頁面的類型的名稱。
VirtualPath 正在編譯的頁面的虛拟路徑。

類型 (Type) 屬性設定動态建立的類(将用于為請求提供服務)的名稱。預設情況下,類型名稱是 ASP.[page]_aspx,其中 [page] 代表頁面檔案的名稱。但是請注意,您可以通過設定您的 .aspx 檔案的 @Page 指令中的 ClassName 屬性來更改此名稱。根命名空間不會更改,是以類型名稱可以是 ASP.[ClassName]。 程式集 (Assembly) 屬性訓示動态建立的程式集的名稱,該程式集包含用于為請求提供服務的頁面類。此類程式集的名稱和内容取決于 web.config 檔案的 <compilation> 部分中的設定。 預設情況下,應用程式頁面以批處理模式編譯,這意味着 ASP.NET 會嘗試在一個程式集中容納盡可能多的未編譯頁面。使用 maxBatchSize 和 maxBatchGeneratedFileSize 屬性可以限制一個程式集中封裝的頁面數量以及程式集的總大小。預設情況下,每個批處理編譯将擁有不超過 1000 個頁面,并且所有程式集都不大于 1MB。一般來說,當第一次編譯大量頁面時,您不應讓使用者等待太長時間。同時,您不應該在記憶體中加載大型程式集而隻是為一個小頁面來提供服務,或者為每個頁面啟動編譯。 maxBatchSize 和 maxBatchGeneratedFileSize 屬性可幫助您在首次命中延遲和記憶體使用之間找到良好的平衡。 如果您選擇站點預編譯(請參閱本雜志 2006 年 1 月 Fritz Onion 的  Extreme ASP.NET 專欄),那麼您不必擔心首次命中延遲,但您仍應考慮最佳的批處理參數,以避免 Web 伺服器的記憶體過載。 當批處理開啟時,應用程式中的前 1000 個頁面(實際數量取決于 maxBatchSize)被編譯為名為 App_Web_[random] 的程式集,其中 [random] 是由八個字元組成的随機序列。 如果關閉批處理,則每個頁面将産生各自的程式集。程式集的名稱如下:

App_Web_[page].aspx.[folder-hash].[random].dll
      

要關閉批處理,可向 web.config 檔案添加以下内容:

<compilation batch="false" />
      

如果您對一個示例應用程式的編譯檔案夾進行檢視,您會找到名稱中包含 CBMResult 的附帶保留檔案,還有一個具有相同名稱的 .ccu 檔案,如下所示:

test.aspx.cdcab7d2.compiled
test.aspx.cdcab7d2_CBMResult.ccu
test.aspx.cdcab7d2_CBMResult.compiled
      

清單中的第一個檔案是保留檔案。那麼其他兩個作何用途? CCU 代表代碼編譯單元 (Code Compile Unit),是指用于生成動态頁面類的源代碼而建立的 CodeDOM 樹。 CCU 檔案是二進制檔案,包含經序列化的頁面 CodeDOM 樹。CBMResult 檔案是保留檔案,用于檢查 CCU 是否最新、其所在的位置以及它基于哪些檔案。 CBMResult 檔案由與 ClientBuildManager 類通信的子產品(例如,Visual Studio 2005 設計器和 IntelliSense ®)來使用。這些子產品查詢頁面的結構來擷取語句結束資訊。CCU 檔案會保留準備為這些請求提供服務的頁面的最新 CodeDOM 結構副本。

頁面類動态源代碼 正如上面提到的,.aspx 資源被解析為 Visual Basic 或 C# 類。該類繼承自 System.Web.UI.Page,或者很可能繼承自某個從 System.Web.UI.Page 繼承而來的類。事實上,在大多數常見情形下,動态頁面類具有以下原型:

Namespace ASP
    Public Class test_aspx 
        Inherits Test : Implements System.Web.IHttpHandler
        ...
    End Class
End Namespace
      

在此例中,Test 類在頁面的代碼檔案類中定義,它包括您在頁面的附帶類檔案中寫入的任何事件處理程式和幫助器例程。在您使用 Visual Studio 2005 時可能已經注意到,此代碼檔案類缺少頁面成員的定義。對于您在 .aspx 源檔案中找到的每個 runat=server 标記,在代碼檔案中應定義有相應類型的成員。ASP.NET 運作庫系統會生成 Test 分部類,包含所有這些成員以及兩個額外的屬性 - Profile 和 ApplicationInstance。 圖 5 顯示了參與為某一 .aspx 資源的請求提供服務的類集。

ASP.NET 頁面的伺服器端

  Figure 5 用于為 .aspx 請求提供服務的運作庫類

'Generated by ASP.NET to add member definitions
Partial Public Class Test
    Implements System.Web.SessionState.IRequiresSessionState

    Protected WithEvents TextBox1 As System.Web.UI.WebControls.TextBox
    Protected WithEvents Button1 As System.Web.UI.WebControls.Button
    Protected WithEvents Msg As System.Web.UI.WebControls.Label
    Protected WithEvents form1 As System.Web.UI.HtmlControls.HtmlForm
    
    Protected ReadOnly Property Profile() _
            As System.Web.Profile.DefaultProfile
        Get
            Return CType(Me.Context.Profile, _
                System.Web.Profile.DefaultProfile)
        End Get
    End Property
    
    Protected ReadOnly Property ApplicationInstance() _
            As System.Web.HttpApplication
        Get
            Return CType(Me.Context.ApplicationInstance, _
                System.Web.HttpApplication)
        End Get
    End Property
End Class

'Your code file
Partial Class Test : Inherits System.Web.UI.Page
    Protected Sub Button1_Click(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles Button1.Click
    End Sub
End Class

'Created to build the expected markup for the .aspx resource
Namespace ASP
    Public Class test_aspx
        Inherits Global.Test
        Implements System.Web.IHttpHandler
        ...
    End Class
End Namespace

      

圖 5 中的類跨兩個不同的源檔案。第一個包含分部類,用于完善代碼檔案中的類和由此派生出的用于為請求提供服務的實際頁面類。第二個檔案是您在項目中建立的代碼檔案的副本。這些檔案根據程式集名稱而命名。名稱的結構如下:[assembly]. X.vb。(如果您使用 C#,則為 .cs)X 為從 0 開始的遞增索引值,可確定檔案名唯一。 如果您檢視示例 test.aspx 頁面的編譯檔案夾的内容,您會發現建立了第三個檔案,如下例中所示:

Namespace __ASP
   Friend Class FastObjectFactory_app_web_test_aspx_cdcab7d2_xg83msu0
      Private Sub New()
          MyBase.New
      End Sub
        
      Shared Function Create_ASP_test_aspx() As Object
          Return New ASP.test_aspx
      End Function
  End Class
End Namespace 
      

類名稱是以字元串 FastObjectFactory 為字首的頁面程式集的名稱。該類具有一個名為 Create_ XXX 的共享函數(如果以 C# 編寫則為靜态函數),其中的  XXX 是要執行個體化的頁面類的名稱。顧名思義,這是一個幫助器類,ASP.NET 運作庫利用其來加速頁面執行個體的建立 - 這是一個非常常見的操作。與編譯一個頁面相比,建立這種類所花費的時間非常短。另一方面,使用工廠類比使用 Activator.CreateInstance 間接建立對象要快得多。 根據批處理編譯設定,工廠類的内容會有所變化。在預設情況下,當批處理開啟時,工廠類包含與批處理頁面相同數量的 Create_ XXX 函數。工廠類的名稱與批處理程式集的名稱相同:

' Used to serve test.aspx
Shared Function Create_ASP_test_aspx() As Object
   Return New ASP.test_aspx
End Function

' Used to serve default.aspx
Shared Function Create_ASP_default_aspx() As Object
   Return New ASP.default_aspx
End Function
      

如果批處理關閉,則工廠類與單個頁面程式集的名稱相同,并且隻包含一個共享函數 - 具體頁面的 facotry。在這種情況下,應用程式中的每個頁面将有自己的工廠類。

運作庫公共 API 借助上面讨論的資訊,探究編譯檔案夾的内容就不是非常困難了。 但通過一個工具來幫助您快速找到您所需的資訊還是非常友善。 我待會兒将設計一個用來導航動态生成的 ASP.NET 應用程式源代碼的資料總管工具,但首先我們來看一看 .NET Framework 2.0 中的一些運作庫 API。特别是,以下兩個類可能是您更希望了解的:HttpRuntime 和 ClientBuildManager。 HttpRuntime 類具有大量共享屬性,可傳回關于包括目前應用程式的 Bin 檔案夾、ASP.NET 安裝路徑、編譯檔案夾和目前 AppDomain ID 在内的各種系統路徑的資訊。您還可以使用以下代碼輕松擷取目前 AppDomain 中加載的程式集清單:

Dim listOfAssemblies() As Assembly
listOfAssemblies = AppDomain.CurrentDomain.GetAssemblies()
      

此代碼并非特定于 ASP.NET,但當從 ASP.NET 應用程式内部調用時,它将傳回包含 AppDomain 中的程式集的數組,其中包括為您的頁面生成的所有程式集。 ClientBuildManager 類沒有多少資訊一類的屬性,CodeGenDir 屬性除外,該屬性傳回與 HttpRuntime 的 CodeGenDir 屬性相同的資訊。但 ClientBuildManager 具有許多讀取配置資訊(如支援的浏覽器)的方法和預編譯應用程式的方法。Get 是該類中的一個方法,它傳回一列應用程式的目錄(在這些目錄中監視那些會引發關閉 AppDomain 應用程式的重要更改)。這些目錄是:App_Browsers、App_Code、App_GlobalResources、App_WebReferences 和 Bin。

建構資料總管工具 對于調試,能夠快速通路正在運作的頁面的源代碼和其他運作時資訊往往非常有用。任何提供這種功能的工具都必須與所有 ASP.NET 應用程式相容,并且隻要求進行有限的配置或根本無需配置。Nikhil Kothari 出色的 Web Development Helper 工具如果能夠提供 ASP.NET 運作庫資訊,那就非常完美了。該工具作為浏覽器幫助對象 (BHO) 來實作,BHO 是一種用于 Microsoft Internet Explorer ® 使用者界面的基于 COM 的插件。BHO 對于我在本專欄中建構的監視工具将是非常好的宿主環境,但可惜我偷了些懶,并沒有這樣做。是以我将我的工具編寫為位于頁面和浏覽器之間的一個 HTTP 子產品,它可查找查詢字元串,如果是顯式調用就可發揮作用。在 ASP.NET 應用程式中安裝 HTTP 子產品隻需在 web.config 中增加一行語句,而且可以非常容易地開啟和關閉安裝:

<httpModules>
   <add name="AspExplorerModule" type="Samples.AspExplorerModule" />
</httpModules>
      

圖 6 顯示了 Explorer HTTP 子產品的大部分代碼。該子產品注冊使用 PostMapRequestHandler 應用程式事件并與頁面類挂接。PostMapRequestHandler 事件會在 ASP.NET 運作庫确定了為請求提供服務所需的 HTTP 處理程式對象時觸發。如果請求的查詢字元串中包含 source=true 參數,并且處理程式是從 System.Web.UI.Page 繼承的一個類,那麼子產品将開始工作。

ASP.NET 頁面的伺服器端

  Figure 6 ASP Explorer HTTP 子產品

Namespace Samples
    Public Class AspExplorerModule : Implements IHttpModule
        Private _app As HttpApplication

        Public Sub Dispose() Implements IHttpModule.Dispose
        End Sub

        Public Sub Init(ByVal context As HttpApplication) _
                Implements IHttpModule.Init
            If context Is Nothing Then
                Throw New ArgumentNullException("[context] argument")
            End If

            ' Cache the HTTP application reference
            _app = context

            ' Map the app event to hook up the page
            AddHandler context.PostMapRequestHandler, _
                New EventHandler(AddressOf OnPostMapRequestHandler)
        End Sub

        Private Sub OnPostMapRequestHandler( _
                ByVal source As Object, ByVal e As EventArgs)
            ' Get the just mapped HTTP handler and cast to Page
            Dim theHandler As IHttpHandler = _app.Context.Handler
            Dim thePage As Page = TryCast(theHandler, Page)

            ' If OK, register an event handler for PreRender
            If thePage IsNot Nothing AndAlso IsSourceRequest() Then
                HookUpPage(thePage)
            End If
        End Sub

        Private Function IsSourceRequest() As Boolean
            Return String.Equals( _
                app.Request.QueryString.Item("source"), "true", _
                StringComparison.OrdinalIgnoreCase)
        End Function

        Sub HookUpPage(ByVal thePage As Page)
            AddHandler thePage.PreRenderComplete, _
                New EventHandler(AddressOf PreRenderComplete)
        End Sub

        Private Sub PreRenderComplete( _
                ByVal sender As Object, ByVal e As EventArgs)
            Dim thePage As Page = DirectCast(sender, Page)
            thePage.SetRenderMethodDelegate( _
                New RenderMethod(AddressOf RenderSource))
        End Sub

        Private Sub RenderSource( _
                ByVal output As HtmlTextWriter, _
                ByVal container As Control)
            Dim thePage As Page = DirectCast(container, Page)

            Dim source As String
            source = GetWebPageInfo(thePage)
            output.Write(source)
        End Sub

        Private Function GetWebPageInfo(ByVal thePage As Page) As String
            ...
        End Function

    End Class
End Namespace

      

ASP Explorer 子產品會與頁面類挂接,并為 PreRenderComplete 事件注冊其自己的處理程式。這樣的設計使得 HTTP 子產品不會改變請求的運作時處理,也不會幹預頁面的編譯。當查詢字元串指定了 source 參數并将其設定為 true 時,子產品就會發揮作用。如 圖 6 中所示,子產品所要做的就是使用不太常見的“頁面”類方法 SetRenderMethodDelegate 為頁面注冊呈現委派 (rendering delegate)。當為頁面指定了呈現委派時,所封裝的方法會替代标準呈現處理。換言之,一旦安裝了該子產品,如果使用 test.aspx 進行調用,您将看到頁面的标準輸出;如果您使用 test.aspx?source=true 進行調用,您将看到子產品可收集的所有與頁面有關的運作時資訊。 ASP Explorer 源代碼定義了一個類,以映射目前頁的保留檔案的内容。它會讀取保留檔案,并複制 圖 7 所示的類中的所有資訊。SourceFiles 屬性是設計用于包含頁面使用的所有源檔案的一個集合。此集合包含從編譯檔案夾獲得的保留檔案中所沒有的資訊。特别是其中包括與某個頁面相關的 .vb 或 .cs 格式的所有源檔案,這些檔案名以動态頁面程式集的名稱開頭。GetWebPageInfo 方法(請參見 圖 6)捕獲所有資訊并為 source 模式的請求建構輸出内容。頁面輸出包括運作時資訊和動态頁面類的源代碼。 圖 8 顯示了實際運作中的 ASP Explorer。

ASP.NET 頁面的伺服器端

  Figure 7 用于從保留檔案加載資訊的幫助器類

Public Class PreservationInfo
    Private _file As String
    Private node As XmlNode

    Public Sub New(ByVal file As String)
        _file = file
        Load()
    End Sub

    Private Sub Load()
        Dim doc As New XmlDocument
        doc.Load(_file)
        node = doc.DocumentElement

        If node Is Nothing OrElse _
                Not String.Equals(node.Name, "preserve") Then Return

        Dim buf As String

        ' ResultType
        buf = node.Attributes("resultType").Value
        Int32.TryParse(buf, ResultType)

        ' VirtualPath
        VirtualPath = node.Attributes("virtualPath").Value

        ' Hash
        Hash = node.Attributes("hash").Value

        ' FileHash
        FileHash = node.Attributes("filehash").Value

        ' Flags
        buf = node.Attributes("flags").Value
        Int32.TryParse(buf, Flags)

        ' Assembly
        AssemblyName = node.Attributes("assembly").Value

        ' Type
        TypeName = node.Attributes("type").Value

        ' Dependencies
        Dim list As XmlNodeList = node.SelectNodes("filedeps/filedep")
        FileDeps = Array.CreateInstance(GetType(String), list.Count)

        For i As Integer = 0 To list.Count - 1
            FileDeps(i) = list.Item(i).Attributes("name").Value
        Next

        ' Source files
        Dim searchPath As String = String.Format("{0}.*", AssemblyName)
        Dim files() As String = _
            Directory.GetFiles(HttpRuntime.CodegenDir, searchPath)

        SourceFiles = Array.CreateInstance(GetType(String), files.Length)
        For i As Integer = 0 To files.Length - 1
            Dim fileInfo As New FileInfo(files(i))
            If fileInfo.Extension = ".vb" Or _
                   fileInfo.Extension = ".cs" Then
                SourceFiles(i) = files(i)
            End If
        Next
    End Sub

    Public ResultType As Integer
    Public VirtualPath As String
    Public Hash As String
    Public FileHash As String
    Public Flags As Long
    Public AssemblyName As String
    Public TypeName As String
    Public FileDeps() As String
    Public SourceFiles() As String
End Class

      
ASP.NET 頁面的伺服器端

圖 8  實際運作中的 ASP Explorer 子產品 (單擊該圖像獲得較大視圖)

示例頁面分析 既然有了可使用的工具,那麼讓我們簡要檢視一下 ASP.NET 為每個 .aspx 檔案生成的代碼的結構。值得注意的是,如果沒有 ASP.NET 運作庫提供的分析和編譯工具,您就必須親自編寫代碼來運作 ASP.NET 頁面! 動态頁面類( 圖 5 中的 test_aspx 類)改寫了 System.Web.UI.Page 類中的幾個方法:FrameworkInitialize、ProcessRequest 和 GetTypeHashCode。ProcessRequest 沒有什麼變化,它隻是調用它的基類方法。GetTypeHashCode 傳回頁面的散列代碼,該代碼可唯一辨別頁面的控件層次結構。當對頁面進行編譯時,會動态計算散列值,并将其作為常量插入到源檔案。 最值得關注的是對 FrameworkInitialize 的改寫。該方法控制頁面的控件樹的建立,并調入一個名為 __BuildControlTree 的私有方法。此方法使用與 .aspx 源檔案中的 runat=server 标記相對應的控件的新執行個體來填充頁面類的 Control 集合。__BuildControlTree 會分析所有伺服器端标記并為每個标記建構一個對象。

<asp:textbox runat="server" id="TextBox1" text="Type here" />
      

以下是為上述标記擷取的典型代碼:

Private Function __BuildControlTextBox1() As TextBox
    Dim __ctrl As New TextBox()
    Me.TextBox1 = __ctrl
    __ctrl.ApplyStyleSheetSkin(Me)
    __ctrl.ID = "TextBox1"
    __ctrl.Text = "Type here"
    Return __ctrl
End Function
      

如果控件有事件處理程式或資料綁定表達式,會怎樣?讓我們首先來考慮帶“單擊”事件處理程式的按鈕。您需要增加一行語句:

__ AddHandler __ctrl.Click, AddressOf Me.Button1_Click
      

對于資料綁定表達式 <%# ... %>,除了使用了 DataBinding 事件,生成的代碼與之類似:

AddHandler __ctrl.DataBinding, AddressOf Me.DataBindingMsg 
      

與處理程式相關的代碼取決于綁定的控件的屬性和要綁定的代碼。對于 Label 控件的 Text 屬性,代碼類似于:

Public Sub DataBindingMsg(ByVal sender As Object, ByVal e As EventArgs) 
   Dim target As Label = DirectCast(sender, Label)
   target.Text = Convert.ToString(..., _  
       CultureInfo.CurrentCulture);
End Sub
      

傳遞給 Convert.ToString 的表達式就是 <%# ... %> 表達式中的代碼。強制類型轉換還取決于所涉及的類型。 如果存在母版頁和主題,那麼源檔案的數量和依存關系清單就會增大,但借助 ASP Explorer 工具,您可以随意對其進行跟蹤。

總結 ASP.NET 對其擁有的資源類型執行按需動态代碼編譯。此功能大大促進了 Web 應用程式的快速疊代開發,但需要 ASP.NET 才能将檔案寫到磁盤。編譯檔案夾是一個重要的檔案夾,ASP.NET 的許多神奇之處都在此展現。您可以隻是出于興趣對此檔案夾研究一番,有時卻可以利用它來診斷和調試棘手的問題。 當然,這裡讨論的大部分功能是 ASP.NET 内部的功能,是以來說,這些功能在未來版本中可能會未經提醒即進行更改。 但截至目前為止,ASP.NET 2.0 的工作原理就是如本文所述的這樣。 順便提一下,可以将 ASP Explorer 工具與 ASP.NET AJAX 應用程式一起使用,這一點也請放心。 該工具的運作效果非常好。 原文連接配接: http://msdn.microsoft.com/zh-cn/magazine/cc163496.aspx#S2

繼續閱讀