asp.net
在 ASP.NET 中由於可直接使用或替換 DLL 檔,大部分人較不關心動態共用原始碼的問題,由於 ASP.NET 在執行時可動態編譯最新原始碼供網站執行,是否一定要採用 DLL 來執行網頁對於管理者來說,沒有絕對的準則,但對系統或程式設計師來說,動態共用原始碼意味相同原始碼可供各專案同時共用,當在開發或維護某一專案時,針對共用原始碼的變動,亦等同於針對所有專案一起更新,此外亦可降低相同功能類似原始開發維護時間,有助於開發力的提升及專案小組共同開發。
例如早期專案存取資料庫可能以 OLEDB 開發,而到 .Net framework 1.1 納入 ODBC 後,在開發新專案時,於共用程式碼可加入 ODBC 支援,則原先專案亦同時獲得 ODBC 支援效益。
在 ASP.NET 中動態共用原始碼有三種方法:
使用 Web 使用者控制項 (*.ascx)
使用程式碼宣告區塊
伺服器端Include指示詞語法
在線上手冊或相關 ASP.NET 叢書中多半都介紹到 Web 使用者控制項,因此本文不重複介紹,而方法 2、方法 3 可參考的資料極少,可參考文末其他參考資訊所列網址。在上述方法中,Web 使用者控制項是屬於使用者介面共用,程式碼區塊或 Include 指示詞則屬於類別、模組或 HTML 等語法共用,在用途及分類上亦有所不同。
首先建立一個測試範例,將原始碼切割為四個檔案部份,如圖 1,程式碼如下:
圖 1
Test.aspx
<%@ Page Language="vb" AutoEventWireup="false" %>
<script language=vb runat=server src="testClass.vb" /%>
<!-- #include virtual = "Include.aspx" --%>
testClass.vb
Public Class testClass
Public Function Test() As String
Return TypeName(Me) & ": testClass.vb Function"
End Function
End Class
Include.aspx
<script language=vb runat=server src="testFun.vb" />
<script language=vb runat=server id="modInclude">
Function Test1()
Response.Write(TypeName(Me) & ": Include.aspx Function Test1" & "<br>")
End Function
</script>
<%
Dim cTest As New testClass
Test1()
Test2()
Response.Write(cTest.Test() & "<br>" & vbNewLine)
%>
testFun.vb
Public Function Test2()
Response.Write(TypeName(Me) & ": testFun.vb Function Test2" & "<br>")
End Function
Test.aspx 是實際的網頁,其他三個檔都是示範動態共用的引入檔,引入外部原始碼之語法有兩種:
使用程式碼宣告區塊
<script language="codelanguage" runat="server" Src="pathname" />
在本區塊內的原始碼,可建立物件類別、列舉等,並可與 Visual Basic .Net 共用原始碼,例如本例中 testClass.vb。在 Web Form 内不能建立新的模組類別,故如 testFun.vb 不能另外使用 Module…End Module。在物件類別中,由於屬於內含類別,故檔案內不能直接存在任何屬於網頁部分的原始碼,若要使用 Visual Studio .Net IDE 環境(以下簡稱 VS.NET)編輯原始碼,建議在使用程式碼宣告區塊時以物件類別為主,以避免 VS.NET 提示錯誤。
伺服器端 Include 指示詞語法
<!-- #include file | virtual = filename -->
此種方式如同原先 ASP 引入檔案方式,由於屬於 ASPX 原始檔的一部份,因此在檔案內可使用屬於網頁部分的相關設定,亦可再引入其他原始碼。若要使用 VS.NET 編輯原始碼,建議副檔名取為 .aspx,則可使用 VS.NET 提示錯誤、自動感知及連結線上說明功能。
在測試範例時,為了確保原始碼為動態引入,請不要使用 VS.NET 中執行的功能,才不會自動依照應用程式建立 DLL 檔參照,導致不易分辨函數或物件功能是否由原始碼引入,因此請直接由 Internet Explorer(以下簡稱 IE)網址列直接輸入對應網址 http://localhost/Include/test.aspx。此外,由於並未透過 VS.NET 進行編譯,因此無法繼承 DLL 檔內之物件,若使用 VS.NET 創建本範例時,請手動移除 Global.asax 及每一個 Web Form (*.aspx) 原始碼中,Inherits 的屬性,才不致發生問題。
在此範例中,將網頁主要顯示內容的程式碼分隔放置在 Include.aspx 內,並呼叫各函數或建立物件,在 IE 上顯示結果如圖 2,一個簡易的動態共用原始碼就此成形,若各部分程式碼有修正,只要重新整理 IE,即可依修正後的程式碼顯示網頁,在網頁系統維護與專案開發將可獲得不小的彈性。
圖 2
更進一步
在內含類別中可能會想要共用其他部分的的變數或函數,此時會發現不能直接在內含類別中使用,想要使用的方法有三種:
以 Shared 關鍵字宣告變數或函數
將主物件當作變數傳遞
宣告一個主物件引用
在原先範例中加入 Shared.aspx 來測試,因此 Test.aspx 也小幅修改來進行測試,修改部分程式碼加上底線來區別。修改過後的程式碼執行成果如圖 3 所示。
Test.aspx
<%@ Page Language="vb" AutoEventWireup="false" ClassName="myTest" %>
<script language=vb runat=server src="testClass.vb" />
<!-- #include virtual = "Shared.aspx" -->
<!-- #include virtual = "Include.aspx" -->
Shared.aspx
<script language=vb runat=server id="modShared">
Public Shared shdMe As Object
Public Shared shdString As String = ""
Public Class cTestObj
Public Function Test(Byval commonObj As Object)
Return commonObj.PublicFun(SharedFun("cTestObj"))
End Function
End Class
Public Class cTestShd
Public Function Test()
Return shdMe.PublicFun(SharedFun("cTestShd"))
End Function
End Class
Public Class cTestName
Public Function Test()
Dim webTest As New myTest
Return webTest.PublicFun(SharedFun("cTestName"))
End Function
End Class
Public Function PublicFun(Byval strOutput As String)
shdString &= "-[Public]"
Return strOutput & "-[Public]"
End Function
Public Shared Function SharedFun(Byval addString As String)
shdString &= addString & "-[Shared]"
Return addString & "-[Shared]"
End Function
</script>
<%
shdMe = Me
Dim clsObj As New cTestObj
Response.Write(clsObj.Test(Me) & "<br>")
Dim clsShd As New cTestShd
Response.Write(clsShd.Test() & "<br>")
Dim clsName As New cTestName
Response.Write(clsName.Test() & "<br>")
Response.Write(TypeName(shdMe) & ", " & shdString & "<br>")
%>
圖 3
以 Shared 為關鍵字宣告
當變數或函數以 Shared 為關鍵字宣告為共用後,變數及函數將變更為全域靜態,本例中宣告共用一個物件變數、一個字串變數及一個函數,由於變數為靜態變數,在所有工作階段 (Session) 連線未結束前可以疊加,因此字串變數宣告同時指定初值來避免疊加,但使用若需有類似變數需在網頁系統中共用時,不一定需要使用 Session 物件。共用函數則可於物件中直接使用,不需考慮命名空間的問題。
將主物件當作變數傳遞
傳遞物件可以透過引數或使用前述共用變數傳遞,在 cTestObj 類別中,將物件以引數方式傳遞到物件內使用,在 cTestShd 則透過共用變數傳遞到類別內。由於 PublicFun 函數為主物件下之公用函數,因此須透過主物件命名空間叫用。
宣告一個主物件引用
在cTestName 類別中,示範宣告一個主物件使用,這裡面會產生一個困擾,主物件類別的名稱為何?在本文中所有的程式碼都是被 Test.aspx 引入,對 ASP.NET 來說,實際上只是當作一個 aspx 檔來處理,所以在物件名設定上只能有一個,即是以 Test.aspx 設定為依據。在 Test.aspx 修改例中,在第一行加入 ClassName 指示詞的屬性,這邊指定的類別名稱即可在 cTestName 類別中使用,若是未指定類別名稱,則預設以該 Web Form 檔名將小數點 (dot) 替換為下減號 (under line) 後為預設物件名稱,如圖 2 中,Test_aspx 為物件名稱,圖3中則顯示 myTest 為物件名稱。當然在 cTestName 類別中可以宣告為 Test_aspx 物件,但通常可能因為共用的理由而不知道是哪個 Web Form 引用,在此情況下規定物件名會比較方便。
其他特性來說,在 Shared.aspx 及 Include.aspx 內分別存在程式碼轉譯區塊 (<% … %>),此特性亦有助於在不同分離的共用程式碼檔內,載入時執行指定程式碼或宣告特定的變數,例如在資料庫存取程式碼的規劃上,可將連線字串或常數分割在一個原始碼檔內,連線查詢的程式碼則分割在另一個原始碼檔內,不同的專案或不同的資料庫只要分別建立對應的連線字串宣告即可,仍可引用共用的程式碼存取資料庫。
此外,經由Shared宣告後的變數為靜態共用,在多使用者同時登入的環境下,亦可考慮透過 Shared 變數交換訊息,例如聊天室內的文字字串屬於不斷累加,即可使用 Shared 變數來達到效果。
最後要測試的是動態共用原始碼的限制。使用程式碼宣告區塊時,由於已無法在此區塊內再引用其他程式碼,故引用深度僅一層。使用 Include 指示詞時,由於下層的引入檔仍可繼續使用 Include 指示詞,所以並不知道最大可用深度。一般來說,深度測試屬於串聯測試,廣度測試屬於並聯測試,而通常深度限制比廣度限制來得嚴峻(遞迴深度),因此僅做最大深度測試。首先以程式產生輸出下面的程式碼到不同的檔案,n 由 1 ~ 10,000 進行深度測試,為了確認每個檔案均能被執行到,故在每個檔案內均使用程式碼轉譯區塊執行該檔的輸出函數,串接的結構圖如圖 4 所示。
loop[nnnnn].aspx
<script language=vb runat=server>
Public Function Responseloop[nnnnn]()
Response.Write("loop[nnnnn]<br>")
End Function
</script>
<% Responseloop[nnnnn]() %>
<!-- #include virtual = "loop[nnnnn+1].aspx" -->
圖 4
測試電腦作業系統為 Windows XP SP2,配備為 P4 1.6G/384 MB,在測試電腦中 n 介於 4,650 ~ 4,800 間,但是大量引用檔案下,在動態編譯時非常耗損記憶體,在測試 n=4,650 時,編譯時期記憶體使用量暴增 460 MB 左右 (aspnet_wp.exe),與原先開機中執行其他程式使用記憶體約 400 MB 相加後,達到測試電腦虛擬記憶體上限,或許在等級更高的電腦中,可以再稍加擴增深度。在測試 n=4,800 時,此時記憶體尚未達到上限,即跳出圖 5 的錯誤,並且 ASP.NET 之服務程式 (aspnet_wp.exe) 會發生異常終止,並將錯誤訊息寫入事件簿後重新啟動 ASP.NET 服務程式,因此可以推定在 ASP.NET 1.1 下,最大深度不超過 4,800。
圖 5 (註:圖中被人工遮罩之文字字串為本機電腦名。)
原則上深度超過 10 以上來說,對於一般人思考邏輯能力是較高的挑戰,一般程式設計也不會將深度無限制擴增,多半深度仍在 10 以內,故目前測試最大深度超過 4,650,對絕大多數的管理與維護而言,已遠超過設計需求。
本文討論動態共用原始碼可對中大型系統設計與管理維護提供另一方向,平時運用可考慮以網頁系統設計師為主,散佈或結案時依委託契約要求,而採原始碼或DLL檔交付,在多系統共用原始碼的情況下,亦可減少原始碼開發成本,提升原始碼管理與維護的能量,個人或開發小組所創建的類別程式庫或函數程式庫均可採用此方法管理維護,達到快速開發網頁系統的目的。