Asp.net不是asp的簡單升級,而是微軟。Net計劃中的一個重要組成部分,它依託。Net的多語言與強大的類庫支援,引進了服務端 HTML控制項與WEB控制項,自動處理控制項的用戶端與服務端的 互動,為開發人員提供了類似Windows下視窗編程的介面,為開發大型網路應用程式功能提 供了良好的編程介面,也能夠極大地提高開發人員的工作效率。
然而,"一次轉換,兩次編譯"過程使得aspx檔案在首次執行(或更 新後首次運行)時顯得略有不足,特別是在擁有大量aspx及codebehind的代碼檔案的應用環境中,把aspx檔案編譯成DLL(在。Net中,被 稱為應用程式集)後再發布,省去"一次轉換、一次編譯"的時間及CPU佔用率,對提高WEB服務的整體效能會有較大的提升。當然,編譯成DLL後,對源代 碼的保密性也有一定程度的提高。
本文通過對Asp.Net的基本處理流程及一個偶然發現的秘密的分析,介紹了在Asp.Net中 如何建立aspx到DLL的映射,如何開發一個可以處理HTTP請求/響應的DLL,以及如何設定"陷阱",把現成的單個aspx檔案與 codebehind的aspx檔案編譯成DLL的過程,文章最後,還介紹了一個在實際操作過程的小技巧。
由於本文要涉及Asp.Net應用程式、命令列編譯、web.config設定檔等概念,為了使讀者能更好地理解本文內容,也為了使本文看上去不顯累贅,先就本文相對應的系統內容作一介紹:
系統內容:Win2000(SP3)+ IIS5 + .Net Framework 1.0(中文版)。
伺服器名稱:由於本文的例子均在本機上測試,伺服器名稱為localhost.
IIS設定:建立虛擬目錄dlltest(真實路徑為w:\wwwroot\dlltest),並把它設為應用程式,在dlltest下建立bin目錄。所有源檔案將放在dlltest目錄下,而所有dll檔案將放在dlltest\bin目錄下。
Asp.Net應用程式設定檔——web.config在dlltest目錄下建立一個web.config檔案,初始時該檔案內容如下:
<?xml version="1.0"?>
<configuration>
<system.web />
</configuration>
命令視窗(DOS視窗)
開啟命令視窗,並用cd命令使目前的目錄為w:\wwwroot\dlltest.
(一)建立aspx到dll的映射
首先讓我們來看看一般情況下aspx檔案是如何被Asp.Net處理的:
當一個HTTP請求(例如"http://webserver/webapp/webpage.aspx") 從用戶端發送到IIS伺服器時,IIS捕獲並分析這個請求,
è當它分析到這個請求是一個aspx頁面時,立即以 "/webapp/webpage.aspx"為參數調用Asp.Net運行環境(aspnet_wp.exe),
èAsp.Net環境啟動後,檢查 "/webapp/webpage.aspx"是否存在,若不存在,則向用戶端返回HTTP 404(File not found)錯誤,
è否則在 Asp.Net 的臨時目錄中尋找相應的dll檔案,若不存在或者該dll比aspx源檔案"舊",則調用csc編譯器(若aspx的服務端指令碼語言是 VB或JScript,則調用相應的vbc編譯器, jsc編譯器)把aspx檔案編譯成dll,
è然後Asp.Net再調用該dll來處理具體的客戶請 求,返回伺服器響應。
從這個處理流程可以看出,一般情況下,Asp.Net運行環境會自動識別、檢查、更新與aspx相對應的 dll.那麼有沒有其它辦法可以強制把對一個aspx檔案的處理"路由"到一個已編譯存在的DLL呢?方法就是在Asp.Net應用程式設定檔 web.config的system.web節的httpHandlers節添加aspx到dll的映射項,文法如下:
<add verb="*" path="aspx檔案名稱" type="類名,dll檔案" />
aspx檔案:需要被"路由"的虛擬名稱,副檔名必須是aspx,否則IIS會先於Asp.Net運行環境處理該檔案。
dll檔案: dll檔案(應用程式集)的名稱,不必輸入".dll".ASP.NET 首先在應用程式的專用 \bin 目錄中搜尋程式集 DLL,然後在系統組件快取中搜尋程式集 DLL.
類名: 由於一個dll可能會有多個名稱空間或多個類,因此必須指明當dll調用時自動載入哪個類。
例如,某一Asp.Net應用程式的web.config檔案如下:
<?xml version="1.0"?>
<configuration>
<system.web>
<httpHandlers>
<add verb="*" path="index.aspx" type="BBS.IndexPage, bbs" />
</httpHandlers>
</system.web>
</configuration>
該設定檔告訴Asp.Net,在用戶端請求本應用程式的index.aspx檔案時,直接調用應用程式bin目錄下的bbs.dll,並自動載入其中的BBS.IndexPage類。
(二)開發能處理HTML頁面的DLL
應該指出的是,並不是所有的應用程式集DLL都能實現HTTP請求/響應模式。還是來看一下Microsoft Asp.Net快速入門教程(http://chs.gotdotnet.com/quickstart/aspplus/)中關於"Http 處理常式和工廠"的描述:
ASP.NET 提供低層級的請求/響應 API,使開發人員能夠使用 .NET 架構類為傳入的 HTTP 要求提供服務。為此,開發人員需創作支援 System.Web.IHTTPHandler 介面和實現 ProcessRequest() 方法的類。當處理 HTTP 要求不需要由進階別的 頁架構抽象化提供的服務時,處理常式通常很有用。處理常式的常用用途包括篩選器和類似 CGI 的應用程式,尤其是那些返回位元據的應用程式。
ASP.NET 收到的每個傳入 HTTP 要求最終由實現 IHTTPHandler 的類的特定執行個體來處理。IHttpHandlerFactory 提供了處理 IHttpHandler 執行個體 URL 請求的實際解析的結構。除了 ASP.NET 提供的預設 IHttpHandlerFactory 類外, 開發人員還可以選擇建立和註冊工廠以支援大量的請求解析和啟用方案。
從這段文字可以看出,當aspx頁面不涉及。net架構提 供的進階介面技術(如資料緩衝、狀態保持、Web表單控制項引用等等)時,且向用戶端輸出的不是複雜的HTML文本,特別是只向用戶端返回位元據( 片,聲音等)時,可以用一個。cs應用程式檔案(本文使用c#語言,如果是用VB或JScript,……)來替代,而該應用程式必須有一個實現 System.Web.IHTTPHandler 介面和並實現 ProcessRequest() 方法的類。一個簡單的例子如下:
1 2 /* 源檔案:ex1.cs 開始 */ 3 using System.Web; 4 namespace DllTest 5 { 6 /*類必須實現IHttpHandler介面。如果程式將訪問工作階段狀態(Session),則必須實現 IRequiresSessionState 介面(不包含任何方法的標記介面)。*/ 7 8 9 public class Ex1Page : IHttpHandler 10 { 11 /*IsReusable屬性告訴。Net架構,本程式是否可以被多個線程同時使用。 12 true對應是;false對應否。*/ 13 14 public bool IsReusable 15 { 16 get { return true; } 17 } 18 19 20 21 /*實現ProcessRequest方法,向用戶端返迴響應資料。 22 本例中向用戶端返回一個簡單的HTML頁*/ 23 24 public void ProcessRequest(HttpContext context) 25 { 26 HttpResponse res = context.Response; 27 28 res.Write("<html><body>"); 29 res.Write("<h1>DllTest - Ex1(例1)</h1><hr>"); 30 res.Write("本頁面直接由DLL處理"); 31 res.Write("</html></body>"); 32 }}} 33/* 源檔案:ex1.cs 結束 */ |
在命令列狀態,用如下的編譯命令把ex1.cs編譯成ex1.dll,並把它存放在bin目錄下。
csc /t:library /out:bin\ex1.dll ex1.cs
可以但要寫全路徑
csc /t:library /out:j:\WebSite\WebTest\bin\ex1.dll
J:\WebSite\WebTest\App_Code\ex1.cs
在設定檔web.config中添加aspx->dll映射,添加後,web.config應該是這樣子的:
<?xml version="1.0"?>
<configuration>
<system.web>
<httpHandlers>
<add verb="*" path="dlltest1.aspx" type="DllTest.ex1Page,ex1" />
</httpHandlers>
</system.web>
</configuration>
現在當瀏覽器訪問http://localhost/dlltest/dlltest1.aspx時,實際上就是調用了ex1.dll中DllTest.Ex1Page類的ProcessRequest方法,在瀏覽中應該可以看到一個簡單的頁面。
(三)把單個aspx檔案編譯成DLL
從 上一節微軟公開描述的"言外之意"來看,微軟是不支援讓開發人員直接把aspx檔案編譯成DLL的。然而,Asp.Net進階介面技術(服務端HTML控 件,WEB控制項等等)都是需要通過aspx檔案才能展現出來的,如果為了DLL的運行效率而放棄aspx的進階特性,則顯然是得不嘗失的。
現在靜下心來分析一下:
csc編譯器只是一個c#語言的編譯器,它只能對符合C#語言規範的檔案進行編譯,而aspx檔案的格式顯然不符合c#語言規範,所以csc編譯器是無法對aspx源檔案進行編譯的。
因 此,要想把aspx檔案編譯成dll檔案,必然要先把aspx檔案轉化成csc編譯器能識別的cs源檔案。那麼用什麼工具來進行轉換呢?雖然我深信這個工 具一定是隱藏在。Net Framework裡面,但在查閱了大量的Asp.Net及。Net的公開文檔及參考手冊,資料之後,仍找不到相關資料。
呵呵,天無絕人之路,一個偶然的機會,還是讓我發現了這個秘密。
來看看源檔案ex2.aspx:
/* 源檔案:ex2.aspx 開始 */ <% @ Page Language="c#" %> <script runat="server"> /*你沒看錯,下一行就是"abcdefg",正是這一行,才讓我有機會寫出本篇文章^_^;在文中,我把這一行稱作"代碼陷阱"*/ abcdefg // 代碼陷阱 void Page_Load(Object src, EventArgs args) { if( !IsPostBack ) NoteLabel.Text = "請輸入您的姓名:"; } void OnNameSubmit(Object src, EventArgs args) { string name = f_Name.Value; NoteLabel.Text = (name=="") ? "姓名不可為空" : name +",您好。歡迎光臨!"; } </script> <html> <body> <form runat="server"> <h1>DllTest - Ex2(例2)</h1> <hr> <asp:label runat="server" id="NoteLabel" style="color:red; font-weight:bold" /> <input runat="server" id="f_Name" size="8"> <button runat="server" onserverclick="OnNameSubmit">確定</button> </form> </body> </html> /* 源檔案:ex2.aspx 結束 */ |
如果把"代碼陷阱"注釋掉或刪掉,那麼ex2.aspx就是一個簡單的Asp.Net檔案,用IE瀏覽此頁面可以發現它能正常工作。
現在讓我們開啟"陷阱",來看看Asp.Net到底返回了什嗎?
返回的是一個"編譯錯誤"的頁面,報告源檔案無法通過編譯。讓我們感興趣的是該頁面最下方的一個名為"顯示完整的編譯源"的超連結,點擊些連結,就能看到這 個由ex2.aspx轉換而來的cs源檔案("完整的編譯源")的完整內容。把這部分"完整的編譯源"去掉前面的行號資訊和其它的一些編譯開關(主要是 #line編譯命令),並關閉那個可愛的"代碼陷阱"(用//把它注釋掉或直接把它delete也行),整理後儲存為ex2_aspx.cs:
/* 源檔案:ex2_aspx.cs 開始 */ /* 從下面的說明可以看出,確實有一個未公開的工具來完成把aspx檔案轉化成cs源檔案 */ //———————————————————— // <autogenerated> // This code was generated by a tool. // Runtime Version:1.0.3705.0 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </autogenerated> //———————————————————— /* 奇怪的是:命名空間居然是ASP而不是ASPX 建議把該名稱改成適合應用程式的名稱,防止命名衝突,例如針對本文,可以改成DllTest 這裡沒改是為了讓大家看清它的原貌 */ namespace ASP { using System; using System.Collections; using System.Collections.Specialized; using System.Configuration; using System.Text; using System.Text.RegularExpressions; using System.Web; using System.Web.Caching; using System.Web.SessionState; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.HtmlControls; /* 1、注意一下類名的構成,如果必要,可以把它改成有意義的名稱,例如針對本文,可以改成Ex2Page 2、注意它的基類。Syste.Web.UI.Page實現了IHttpHandler介面,由於要訪問Session,所以也實現了IRequiresSessionState介面。 */ public class ex2_aspx : System.Web.UI.Page, System.Web.SessionState.IRequiresSessionState { private static int __autoHandlers; protected System.Web.UI.WebControls.Label NoteLabel; protected System.Web.UI.HtmlControls.HtmlInputText f_Name; protected System.Web.UI.HtmlControls.HtmlButton __control3; protected System.Web.UI.HtmlControls.HtmlForm __control2; private static bool __intialized = false; private static System.Collections.ArrayList __fileDependencies; /* 現在可以關掉"陷阱"了 */ // abcdefg void Page_Load(Object src, EventArgs args) { if( !IsPostBack ) NoteLabel.Text = "請輸入您的姓名: "; } void OnNameSubmit(Object src, EventArgs args) { string name = f_Name.Value; NoteLabel.Text = (name=="") ? "姓名不可為空" : name +",您好。歡迎光臨!"; } /* 建構函式 */ public ex2_aspx() { System.Collections.ArrayList dependencies; if ((ASP.ex2_aspx.__intialized == false)) { dependencies = new System.Collections.ArrayList(); /* 應該把下面這行注釋掉,讓DLL成為一個無依賴的獨立檔案 防止在DLL運行時再次去尋找、比較它的"依賴"檔案的新舊 */ //dependencies.Add("W:\\wwwroot\\dlltest\\ex2.aspx"); ASP.ex2_aspx.__fileDependencies= dependencies; ASP.ex2_aspx.__intialized = true; } } protected override int AutoHandlers { get { return ASP.ex2_aspx.__autoHandlers; } set { ASP.ex2_aspx.__autoHandlers = value; } } protected System.Web.HttpApplication ApplicationInstance { get { return ((System.Web.HttpApplication)(this.Context.ApplicationInstance)); } } public override string TemplateSourceDirectory { get { return "/dlltest"; } } private System.Web.UI.Control __BuildControlNoteLabel() { System.Web.UI.WebControls.Label __ctrl; _ctrl = new System.Web.UI.WebControls.Label(); this.NoteLabel = _ctrl; _ctrl.ID = "NoteLabel"; ((System.Web.UI.IAttributeAccessor)(__ctrl))。SetAttribute("style", "color:red; font-weight:bold"); return __ctrl; } private System.Web.UI.Control __BuildControlf_Name() { System.Web.UI.HtmlControls.HtmlInputText __ctrl; _ctrl = new System.Web.UI.HtmlControls.HtmlInputText(); this.f_Name = _ctrl; _ctrl.ID = "f_Name"; _ctrl.Size = 8; return _ctrl; } private System.Web.UI.Control __BuildControl__control3() { System.Web.UI.HtmlControls.HtmlButton __ctrl; _ctrl = new System.Web.UI.HtmlControls.HtmlButton(); this._control3= _ctrl; System.Web.UI.IParserAccessor _parser = ((System.Web.UI.IParserAccessor)(_ctrl)); _parser.AddParsedSubObject(new System.Web.UI.LiteralControl("確定")); _ctrl.ServerClick += new System.EventHandler(this.OnNameSubmit); return _ctrl; } private System.Web.UI.Control __BuildControl__control2() { System.Web.UI.HtmlControls.HtmlForm _ctrl; _ctrl = new System.Web.UI.HtmlControls.HtmlForm(); this._control2= _ctrl; System.Web.UI.IParserAccessor _parser = ((System.Web.UI.IParserAccessor)(_ctrl)); _parser.AddParsedSubObject (new System.Web.UI.LiteralControl("\r\n <h1>DllTest - Ex2(例2)< /h1>\r\n <hr>\r\n ")); this._BuildControlNoteLabel(); _parser.AddParsedSubObject(this.NoteLabel); _parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n ")); this._BuildControlf_Name(); _parser.AddParsedSubObject(this.f_Name); _parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n ")); this._BuildControl_control3(); _parser.AddParsedSubObject(this._control3); _parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n ")); return _ctrl; } private void _BuildControlTree(System.Web.UI.Control _ctrl) { System.Web.UI.IParserAccessor _parser = ((System.Web.UI.IParserAccessor)(_ctrl)); _parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n\r\n<html>\r\n<body>\r\n ")); this._BuildControl_control2(); _parser.AddParsedSubObject(this._control2); _parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n</body>\r\n</html>\r\n")); } protected override void FrameworkInitialize() { this._BuildControlTree(this); this.FileDependencies = ASP.ex2_aspx._fileDependencies; this.EnableViewStateMac = true; } public override int GetTypeHashCode() { return -11574299; } } } /* 源檔案:ex2_aspx.cs 結束 */ |
相信大家在分析了這個檔案之後,會對Asp.Net運行原理有更進一步的認識(與本文無關,不詳述)。
在命令列狀態,用如下的編譯命令把ex2_aspx.cs編譯成ex2.dll,並把它存放在bin目錄下。
csc /t:library /out:bin\ex2.dll ex2_aspx.cs
在設定檔web.config中添加aspx->dll映射,即在system.web節的httpHandlers添加下面一行:<add verb="*" path="dlltest2.aspx" type="ASP.ex2_aspx, ex2" />
現在當瀏覽器訪問http://localhost/dlltest/dlltest2.aspx時,就如同訪問ex2.aspx一樣。當然,現在即使ex2.aspx不存在,或者已經更新過,也不會對頁面訪問有任何影響,除非重建bin\ex2.dll.
(四)把codebehind的aspx檔案編譯成dll
對於把codebehind的aspx檔案編譯成dll,其中把aspx檔案轉化成cs源檔案的原理同上,也是先設定一個"代碼陷阱",然後把"完整的編譯 源"進行適當整理,儲存為cs源檔案。區別是在編譯成dll時的步驟:(為敘述方便,假設介面檔案為ex3.aspx,codebehind檔案為 ex3.aspx.cs,ex3.aspx的"完整編譯源"儲存為ex3_aspx.cs)
第一步:先用如下命令把ex3.aspx.cs編譯成bin\ex3.aspx.cs.dll
csc /t:library /out:bin\ex3.aspx.cs.dll ex3.aspx.cs
第二步:再用如下命令把ex3_aspx.cs編譯成bin\ex3.dll
csc /t:library /r:bin\ex3.aspx.cs.dll /out:bin\ex3.dll ex3_aspx.cs
然後在設定檔web.config中添加aspx->dll映射,即在system.web節的httpHandlers添加下面一行:
<add verb="*" path="dlltest3.aspx" type="ASP.ex3_aspx, ex3" />
現在開啟瀏覽器,訪問http://localhost/dlltest/dlltest3.aspx試試。
(五)一點小技巧
在設定"陷阱"把aspx檔案轉化成cs源檔案時,一般是使用copy、paste方法把"完整的編譯源"儲存在記事本或vs.net或其它asp.net開發環境,再進行整理後儲存為cs源檔案的。
整理,就是把paste進來的行號資訊與"#line"編譯指令去掉。如果是手動地刪掉這些資訊,則會太麻煩,即使是一個簡單的如ex2.aspx的檔案,也會產生約270行的"完整的編譯源".
我所使用的一個小技巧是:在記事本裡,用替換的方法來快速整理。
用"/* 行"來全部替換"行",
用":*/"來全部替換":",
用"// #line 行 "來全部替換"#line",
替換完成之後,再把"代碼陷阱"注釋掉,把主類建構函式裡設定"依賴檔案"的語句全部注釋掉,這樣就算整理完成了。