Inside ASP.NET 2.0-即時編譯系統

來源:互聯網
上載者:User

 

從ASP.NET 1.1 到2.0, 編譯系統的進化

在筆者撰寫『深入剖析ASP.NET 元件設計』一書時,曾相當深入的探討ASP.NET 1.1 的即時編譯模型, 該章節以圖1 為開端, 一步步的將隱藏於後的設計理念攤開在讀者面前,時至今日,ASP.NET即將邁入2.0 了,這個即時編譯模型做了相當大幅度的變化, 圖2 是對照1.1 與2.0 的即時編譯模型概觀,讀者們可以發現,2.0的即時編譯模型複雜了許多。 圖1

 

圖2

 

 

在1.1 時,當訪問者要求一個檔案時,ISAPIRuntime(IIS 的要求處理物件) 會依照檔案類型來喚起適當的Http Handler ,以.aspx 來說就是PageHandlerFactory, 她也是即時編譯系統的入口, 這段流程在2.0 仍然沒有改變,但後面的動作就完全變樣了,在1.1 時, PageHandlerFactory 會使用PageParser 來解譯.aspx 檔案,再交由PageCompiler 來產生出編譯檔案。在2.0 時,同樣的動作是交由BuildManager 來完成,其會呼叫適當的BuildProvider 來處理要求的檔案, 最後交由適當的Compiler 來產生編譯檔案。不知讀者們是否看出上面這段話所隱含的意義,是的!BuildManager 具備依照不同附檔名使用不同BuildProvider 的能力, 這代表著設計者可能擁有撰寫自訂的BuildProvider 來參與即時編譯流程。讀者們可以在Visual Web Developer 的New Items 選項中看到圖3 的畫面。 圖3

 

其中最引人注意的是ASP.NET 2.0 允許使用者撰寫Generic Handler, 也就是1.1 中的自定Http Handler 程式檔,該Wizard 會產生出程式1 的碼。

程式1

 

<%@ WebHandler Language="C#" Class="Handler" %> 
using System.Web; 
public class Handler : IHttpHandler { 

public void ProcessRequest (HttpContext context) { 
context.Response.ContentType = "text/plain"; 
context.Response.Write("Hello World"); 


public bool IsReusable { 
get { 
return false; 


}

你是否看到了一個介於ASP.NET Script 與一般程式檔的怪異程式碼呢? 在存檔後執行時會看到圖4 的結果。

圖4

 

問題來了, 以往撰寫這種自訂的Http Handler 時,設計師必須預先將程式碼編譯好, 放置於網站目錄下, 這個Handler 才能正常運作, 但現在並未執行這個編譯動作啊?那是誰為我們編譯這個檔案,又是如何做的呢? 答案與1.1 時相同,就是SimpleHandlerFactory, 但後面的動作就不同了, 以往的SimpleHandlerFactory 只是載入對應的Assembly 來啟動Http Handler, 在2.0 時此動作換成了BuildManager, 她會尋找.ashx 對應的Build Provider,也就是WebHandlerBuildProvider 來運行即時編譯模型。以上的討論說明一件事,Handler Factory 的大部份工作已經下放給BuildManager 了, 而目的就是提供一個更強大的即時編譯模型,不只是可以編譯.aspx、.ascx, 還可以編譯各式各樣的檔案,例如.masterpage 也是這個模型中的一員, 這帶來了一個難以想象的極大優點,設計師以後將具備自定Script 檔案的能力,只要有需求,設計師可以自行定義一種Script 語言, 再提供對應的BuildProvider 物件,BuildManager 將很樂意的為你完成即時編譯動作,而且優點還不只於此,BuildManager支援先行編譯模型, 也就是說設計師只要提供Script 檔案與BuildProvider 後, 就能享受即時編譯與先行編譯兩種模型。舉一個較實務的例子, 一個設計師希望提供某種較簡單的Script 語言供使用者應用, 那麼該設計師只需提供一個MyScriptBuildProvider, 將其與特定的附檔名對應之後, 再利用CodeDom 來產生真正的程式碼就可以了,接下來的動作BuildManager 將很樂意的幫你完成。

Reloaded! Page Compiler-Time

既然2.0 已經改變即時編譯模型了,那麼就讓我們從新瞭解這個編譯系統究竟是如何

動作的,在1.1 中,即時編譯系統最令人注意的是PageParser 物件,此物件會讀入.aspx

檔案,將其解譯成一群Control Builder 物件交由PageCompiler 物件來產生原始程式碼後

編譯,這段過程在2.0 中依然沒變,不同的是PageParser 在2.0 中已經不是由

PageHandlerFactory 來呼叫了,圖5 是2.0 中Web Page 的編譯時間期概觀。

圖5,一段不算短的旅行景點, 在BuildManager 接到編譯命令時, 會先將目錄中的幾個外部檔案編譯好, 這些檔案就是Resource 、Web Reference 、Code 、Profile 、Global.asax,Resource 指的是資源檔,Web Reference 通常是引用Web Services 時用的檔案,Code 是位於Code 目錄下的程式檔,Profile 則是位於web.config 中的Profile 區段定義,Global Asax 則是大家所熟悉的Global.asax 檔案。接下來是本節的重頭戲了,Compiling Web Files!這個動作將會編譯網站中的.aspx 或是其它擁有相對應BuildProvider 物件的檔案,例如.ashx與.masterpage 等等。回到Page 的編譯周期上,.aspx 所對應的BuildProvider 是PageBuilderProvider 物件,此物件會使用PageParser 來解譯.as px,再利用PageCodeDomTreeGenerator 來產生出原始碼,最後交由適當的Compiler 編譯。

Manager、Provide r、Generator

承上節,BuildManager與BuildProvider 及其CodeGenerator 擁有不可分的關係,圖6 是目前2.0 所提供的一部份BuildProvider 物件,讀者們可以在其中發現許多熟悉的物件名稱,她們就是對應到目前你能在ASP.NET 中撰寫的檔案。

圖6

 

 

有趣的是Page Theme 居然也擁有一個PageThemeBuilderProvider, 這代表著什麼呢?筆者原來以為Theme 只是一個簡單的文字檔,當Page 套用某一個Theme 時,只是由該文字檔中讀取定義來套用至控制項上,但結果不然, 由PageThemeBuilderProvider 的出現來看,Theme 是一個編譯後的檔案,PageThemeBuilderProvider 會編譯所有的Theme 檔案, 也就是.skin,事實上,所有於內的控制項定義都會被編譯成控制項實體,當Page 需要套用某個Theme 至控制項時, 只是將控制項的屬性複製過來罷了, 沒有解譯動作, 速度自然快上不少。基本上,所有可編譯型的BuildProvider 物件都會提供兩個物件,一個是Parser,用來解譯檔案用, 另一個是CodeDomTreeGenerator, 用來將Control Builder 物件群轉換為可編譯的原始碼, 以PageTheme 來說, 就7 所示。 圖7

 

當然,這並不是說每一個BuildProvider 都得提供這些東西,契約層僅到達BuildProvider 就停止了, 只要該BuildProvider 能傳回一個實體,BuildManager 並不管其內部是如何達到的。

先行編譯系統

截至目前為止, 我們一直在即時編譯系統上打轉, 並未談到另一個系統, 那就是先行編譯系統, 事實上這個系統只是即時編譯系統的一種呈現型式, 當BuildManager 啟動時,會先判別要求的目錄中是否擁有.compiled 的檔案, 存在的話就將其視為先行編譯模式, 載入.compiled 檔案中所定義的Assembly, 等會!先行編譯後的檔案連真正的.aspx 都不存在了,BuildManager 如何做接下來的動作了,又是如何與BuildProviders 互動呢?哦, 沒有!在先行編譯情況下,BuildManager 根本就不會用到BuildProvider, 這跟即時編譯系統的二次動作一樣, 當即時編譯系統完成後, 會將結果存放到暫存目錄中, 順帶著也會放一份到Cache 中, 待下次收到要求時, 就直接取用了, 先行編譯系統只是跳過了第一次那一段動作而已, 這代表著, 自定的BuildProvider 不用做特別的動作, 就可以享受到先行編譯系統的優點。

 

Custom Build Provider

由於目前ASP.NET 2.0 仍處於Beta 版本,有關於Build Provider 的資訊少之又少,不過我還是在檔案中找到程式2 的說明。程式2

 

<configuration>
<system.web> 
<compilation> 
……. 
<buildProviders> 
<buildProvider extension=".mafx" type="BuildProviderType,        BuildProviderAssembly" /> 
</buildProviders>
 </compilation>   
</system.web> 
</configuration> 

 

粗體字的部份就是定義自定Build Provider 的地方,這可以證明在ASP.NET 2.0 中,設計師是被允許撰寫Build Provider 的,不過除了這個檔案外,我再也找不到更深入的資訊了,而很不幸的, 這個檔案有一個錯誤, 其中的buildProvider 定義是不被接受的, 實際上的文法應該如程式3。程式3

 

<compilation debug="true">
<buildProviders> 
<add extension=".ppp"
type="TestBuildProvider.MyCSharpBuilder, TestBuildProvider"
appliesTo="Code"/> 
</buildProviders> 
</compilation>

 

extension 是定義此BuildProvider 對應至何種副檔名,type 指的是BuildProvider 的Assembly 與Type, 最後的appliesTo 是代表著使用於何種模式, 有四個選擇, 一是Code, 代表程式檔, 二是Resource, 就是資源檔, 三是Web, 代表著網頁檔案(.aspx、.ascx…), 四是All, 代表任何類型檔案。要撰寫BuildProvider,讀者必須先準備Visual Studio 2005 Beta 或是Visual C# Express,這些工具才有提供Class Library 的Wizard ,否則就得使用Command Line 方式編譯範例了,程式4 是我們的第一個BuildProvider 範例。程式4

 

#region Using directives 
using System; 
using System.Collections.Generic; 
using System.Text; 
using System.IO; 
using System.CodeDom; 
using System.CodeDom.Compiler; 
using System.Web.Compilation; 
#endregion 

namespace TestBuildProvider 

    public class MyCSharpBuilder:BuildProvider
    { 
    
        public override void GenerateCode(AssemblyBuilder assemblyBuilder) 
        { 
            TextReader reader = base.OpenReader(); 
            string scriptString = reader.ReadLine(); 
            CodeCompileUnit unit = new CodeCompileUnit();             
            unit.Namespaces.Add(new CodeNamespace("TEST"));
            CodeTypeDeclaration class1 = new CodeTypeDeclaration("HelloClass");  
            class1.IsClass = true; 
            CodeMemberMethod method1 = new CodeMemberMethod();             
            method1.Name = "SayHello";  
            method1.ReturnType = new CodeTypeReference("System.String");
            method1.Statements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(scriptString)));     
            method1.Attributes = MemberAttributes.Public; 
            class1.Members.Add(method1);  
            unit.Namespaces[0].Types.Add(class1);         
            assemblyBuilder.AddCodeCompileUnit(this, unit); 
        }
    } 

 

讓我稍微解釋一下這個範例, 程式中以CodeDom 產生出一個HelloClass 類別,在其中加入一個方法:SayHello ,為其定義一個字串型別的傳回值,特別注意的是此值是由OpenReader 所傳回的TextReader 中讀回來的,OpenReader 會以TextReader 來開啟目前處理的檔案, 此例中就是class1.ppp( 後詳)。編譯後將其複製到網站目錄中的bin 目錄下,假設你的網站目錄下並沒有bin 目錄,那就自行建一個吧。接著修改web.config 加入程式5 的定義。程式5

 

<compilation debug="true">
<buildProviders> 
<add extension=".ppp" type="TestBuildProvider.MyCSharpBuilder, TestBuildProvider"
appliesTo="Code"/> 
</buildProviders> 
</compilation>

 

完成之後建立一個檔案class1.ppp,內容如程式6。程式6

 

hello i am buildprovider,this message is define in class1.ppp.

 

將這個檔案放在網站目錄下的Code 目錄中,沒有Code 目錄的話就建一個吧。現在讓我們來試試這個BuildProvdier 能否正常運作吧, 在Default.aspx 中放入一個Button,撰寫其事件函式,如程式7。程式7

 

Type FindType() 

    Assembly[] assems = AppDomain.CurrentDomain.GetAssemblies(); 
    foreach(Assembly assem in assems) { 
        Type t = assem.GetType("TEST.HelloClass"); 
        if (t != null) return t;
     }
    return null; 

void Button2_Click(object sender, EventArgs e) 

    Type t = FindType(); 
    if (t != null)  
      { 
     object obj = Activator.CreateInstance(t);   
     string s = (string)obj.GetType().InvokeMember("SayHello",  BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Public, null, obj, new object[]{});     
     Button2.Text = s; 
      }  

 

FindType 函式是為了找尋HelloClass 這個Type 而寫的,在網站執行時雖然會載入BuildProvider 所產生的Assemblys, 但是在這裡並無法知道class1.ppp 所產生出來的Assembly 實際名稱,自然也就無法取到HelloClass 了,所以用FindType 來搜尋所有的Assemblys 來取得HelloClass。連Assembly 都無法確定了,當然也無法用一般的方式來建立物件及來電者法了,所以就只剩下Reflection 可以用了,此處利用Reflection 來建立HelloClass 物件實體,接著呼叫其SayHello 來取回class1.ppp 中的文字。

另一個編譯子系統:Expression Builder

Build Provider 是一個蠻不錯的設計,設計師可以撰寫自訂的Provider 來延伸ASP.NET 的編譯系統, 但有時候設計師只是需要一個簡單的動態決議系統, 而不是一個以檔案為基礎的編譯動作,例如程式8 中所示。 程式8

 

ConnectionString="<%$ ConnectionStrings:AppConnectionString1 %>"

 

這是ASP.NET 內建的一項簡易設計,<%$ 後的字串在編譯時間期時會被解譯成程式9 的碼。

程式9

 

source1.ConnectionString = ConnectionStringsExpressionBuilder.GetConnectionString("AppConnectionString1"); 

 

藉由此設計, 設計師可以將組態檔中的值指給某個屬性, 達到以外部檔案改變應用程式行為的目的, 也可以減少程式重編譯的次數, 那這是如何達到的呢? 如果我們要自定這種功能,又該如何做呢?答案已經在程式9 中出現了, 那就是ExpressionBuilder 物件。ASP.NET 2.0 允許設計師如撰寫Build Provider 一般撰寫自訂的ExpressionBuilder 物件, 程式10 是一個簡單到不行的範例。 程式10

 

#region Using directives 
using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Web.Compilation; 

using System.Reflection; 
using System.ComponentModel;
 using System.CodeDom; 
 #endregion 

namespace TestExpressionBuilder 

    public class MyExpressionBuilder:ExpressionBuilder { 
        public override System.CodeDom.CodeExpression GetCodeExpression(System.Web.UI.BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context) 
        { 
         return new CodePrimitiveExpression(entry.Expression); 
        } 
        
        public MyExpressionBuilder() 
        {
        } 
    } 

 

程式中覆載了GetCodeExpression 函式, 此函式會在解譯時期被呼叫, 此時必須傳回一個CodeExpression 物件,解譯器藉此產生出類似程式9 的程式碼。要套用這個ExpressionBuilder,web.config 中必須包含程式11 的設定。 程式11

 

<compilation debug="true"> 
<expressionBuilders> 
<add expressionPrefix="MyExpression"
type="TestExpressionBuilder.MyExpressionBuilder, TestExpressionBuilder"/> 
</expressionBuilders> 
</compilation>

 

expressionPrefix 屬性代表著ExpressionBuilder 所能解析的Expression 的開頭驗證字碼, 只有符合這個字串的Expression 才會交給MyExpressionBuilder 來處理,程式12 是測試碼。程式12

 

<asp:Button ID="Button2" Runat="server"
Text="<%$ MyExpression:i am expxression builder %>" />

 

不過可惜的是, 目前的VWD 似乎完全不認識自訂的ExpressionBuilder, 因此無法在設

 

計時期顯示出正確的結果。

後記

 .提醒讀者,NET Framework 2.0 仍處於Beta 階段,這也代表著目前所談的技術都是未定數,雖然Build Provider 與Expression Builder 技術帶給設計師無限的想象空間, 但除了Microsoft 之外沒人能確定,最終版本會不會仍然開放這些功能給設計師使用。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.