【dudu】在ASP.NET 2.0中開發萬用字元對映應用程式的一些問題

iDotNetSpace發表於2008-06-10

本文主要通過分析在ASP.NET 2.0中開發ASP.NET萬用字元對映應用程式遇到的一些問題,來說明ASP.NET 2.0中頁面編譯模型的不足之處。文章中如果有不妥之處,歡迎您指出。
     這裡所說的ASP.NET萬用字元對映應用程式是指在IIS中將所有請求轉發至ASP.NET 2.0執行時處理(對於IIS 5.0,就是建立.*到aspnet_isapi.dll的對映),在程式中通過實現System.Web.IhttpHandlerFactory介面來處理所有請求,實現System.Web.IhttpHandlerFactory的類就相當於一個前端控制器。典型應用就是.Text及基於. Text開發的部落格園Blog軟體。
     在ASP.NET 1.1中,實現萬用字元對映應用程式大家可能比較清楚,主要是兩點:
     1、 實現System.Web.IhttpHandlerFactory介面,在GetHandler(HttpContext context, string requestType, string url, string path)中根據請求的url,基於一些規則,找到實際訪問的頁面檔案,然後呼叫PageParser.GetCompiledPageInstance 對頁面進行編譯並生成相應的例項處理請求。這樣做的好處是:你可以使用任意的url地址,不必關心是否存在對應的頁面檔案,而且可以方便地控制對Web伺服器上資源的訪問。
     2、 在web.config中加上:

<httpHandlers>
    
<add path="*" verb="*" type="Dottext.Common.UrlManager.UrlReWriteHandlerFactory,Dottext.Common" validate="false" />
httpHandlers>

     ASP.NET 2.0中新的頁面編譯模型給實現萬用字元對映應用程式帶來意想不到的問題,下面我以部落格園Blog軟體為例與大家一些探討這些問題。
     在部落格園Blog軟體中,實現IhttpHandlerFactory介面的是Dottext.Common.UrlManager. UrlReWriteHandlerFactory,不改變在ASP.NET 1.1中實現的UrlReWriteHandlerFactory程式碼,直接在ASP.NET 2.0中編譯並執行,當程式執行在IIS根目錄下,就會在執行PageParser.GetCompiledPageInstance時出現 “Object reference not set to an instance of an object”異常(執行在虛擬目錄中不會出現這個問題)。這個問題是ASP.NET 2.0中的一個小Bug,之前我寫的PageParser.GetCompiledPageInstance中的一個Bug及解決方法對這個問題進行了一些分析,這個問題可以通過在PageParser.GetCompiledPageInstance之前呼叫context.RewritePath("~/default.aspx")解決。
      接著進行訪問個人Blog主頁的測試,比如:http://www.cnblogs.com/dudu,訪問時出現錯誤:

“There is no build provider registered for the extension ''. You can register one in the <compilation><buildProviders> section in machine.config or web.config. Make sure is has a BuildProviderAppliesToAttribute attribute which includes the value 'Web' or 'All'.”

     在ASP.NET 2.0中,當我們第一次訪問一個頁面時,必不少的兩個過程是:1、頁面編譯 2、建立編譯後的頁面程式碼的例項。頁面編譯是根據所訪問的url地址中的副檔名找到匹配的Build Provider對頁面進行編譯。這裡出現的問題是由於ASP.NET 2.0執行時沒找到相應的Build Provider,對http://www.cnblogs.com/dudu這樣地址,由於使用了萬用字元對映,在ASP.NET 2.0執行時處理時,得到的副檔名是空(如果沒有使用萬用字元對映,IIS會自動將地址改為:http://www.cnblogs.com/dudu/default.aspx)。ASP.NET 2.0在這裡的設計不足之處是沒有考慮這種情況,無法通過在web.config中進行相應的配置來解決這個問題。如果能提供下面的配置,這個問題就可以輕鬆解決:

【dudu】在ASP.NET 2.0中開發萬用字元對映應用程式的一些問題<buildProviders >
【dudu】在ASP.NET 2.0中開發萬用字元對映應用程式的一些問題        
<add  extension=".*" type="System.Web.Compilation.PageBuildProvider"/>
【dudu】在ASP.NET 2.0中開發萬用字元對映應用程式的一些問題
buildProviders>

     對於這個問題,我的解決方法是在PageParser.GetCompiledPageInstance之前對url進行處理,在url中加上預設檔案,比如:default.aspx。如果你想使用其他的副檔名,比如:.html,需要在web.config中加上:

【dudu】在ASP.NET 2.0中開發萬用字元對映應用程式的一些問題<buildProviders >
【dudu】在ASP.NET 2.0中開發萬用字元對映應用程式的一些問題        
<add  extension=".html" type="System.Web.Compilation.PageBuildProvider"/>
【dudu】在ASP.NET 2.0中開發萬用字元對映應用程式的一些問題
buildProviders>

     這裡還有一個小bug,在上面的錯誤資訊“Make sure is has a BuildProvider AppliesToAttribute attribute which includes the value 'Web' or 'All'.”提示需要設定AppliesToAttribute屬性,實際上web.config中並不支援這樣的屬性,可能是正式版之前的 ASP.NET 2.0支援過這個屬性,後來去掉後,錯誤提示資訊並沒有修改。
     解決了上面的兩個問題,原以為萬用字元對映應用程式可以在ASP.NET 2.0中正常執行了,我在本機上測試部落格園的程式,頁面能正常訪問。可是今天凌晨在伺服器進上將網站升級到ASP.NET 2.0之後,發現ASP.NET執行時在頻繁地編譯頁面,CPU佔用一直100%,編譯了一個多小時還在編譯,而且編譯似乎與訪問量有關,訪問少的站點頁面還能開啟,部落格園主站由於訪問量大,幾乎無法訪問。問題出在哪?於是我從PageParser.GetCompiledPageInstance的原始碼尋找線索,在BuildManager.GetCacheKeyFromVirtualPath中發現可疑之處,BuildManager是根據所請求的虛擬路徑建立快取鍵,然後根據這個鍵查詢或建立頁面編譯後的快取物件。當對一個頁面發出請求時,BuildManager會檢查快取,先從記憶體中檢查,如果記憶體中沒有就從快取資料夾(Temporary ASP.NET Files)中查詢,如果找到,就直接建立該型別的例項,不進行動態編譯。如果沒找到,就進行頁面編譯工作,而且查詢的依據就是根據虛擬路徑建立的快取鍵。對於通常的頁面訪問方式,這樣處理不會引起問題。但對於萬用字元對映的情況,就會帶來問題。因為萬用字元對映時,常見情況是不同的url地址訪問的卻是同一個頁面檔案。比如部落格園中每篇文章地址不同,但訪問的卻是同樣的頁面程式碼,如果按照目前ASP.NET 2.0中的頁面編譯模型,每篇文章第一次訪問都要進行編譯,如果部落格園中的幾十萬篇文章被訪問,就要進行幾十萬編譯,難怪今天部落格園網站升級至 ASP.NET 2.0之後,伺服器一直忙於編譯。
經過測試情況果然這樣,當然訪問地址:http://www.cnblogs.com/dudu/archive/2006/03/07/345107.html時會在Temporary ASP.NET Files中資料夾編譯生成類ASP.dudu_archive_2006_03_07_345107_html,而訪問其他文章地時,也根據文章地址生成另外一個類(2006年3月12日修改:對於這個問題,通過傳給PageParser.GetCompiledPageInstance一個真實的虛擬地址就能解決問題,比如在部落格園程式中,對於上面的地址,改為這樣的程式碼就行了:GetCompiledPageInstance(app+ "~/default.aspx", pagepath, context))。這樣編譯效率實在太低了!為什麼要根據虛擬路徑建立緩鍵,設計者設計時根本沒考慮到萬用字元對映的問題,真是糟糕的設計!如果按照ASP.NET 1.1那樣根據實際訪問的頁面檔名建立快取鍵,就可以輕鬆地避免這個問題。ASP.NET 2.0新的頁面編譯模型在這裡似乎是一個敗筆。更糟糕的是連讓開發人員彌補這個Bug的機會都沒有, System.Web.Compilation.BuildManager中沒有提供一個讓開發人員自己設定快取鍵的方法或屬性。(注:建立快取鍵的方法是BuildManager. GetCacheKeyFromVirtualPath(VirtualPath virtualPath, out bool keyFromVPP))。更糟糕的是,System.Web.Compilation中的很多類都是internal,很多類的方法是灰色(Reflector用灰色顯示internal static或private,顏色用的不錯,讓人看了就灰心),想自己呼叫相應方法進行頁面編譯幾乎是不可能(用反射的方法不知能否呼叫,還沒試過,即使能呼叫,也要考慮效能上的損失)。難道要自己寫System.Web.Compilation中那些類去處理頁面編譯?我寧願選擇ASP.NET 1.1,然後等ASP.NET 2.0 SP1,SP1解決不了,等SP2......希望不要等到ASP.NET 3.0。
      也許你想到了在GetHandler(HttpContext context, string requestType, string url, string path)中呼叫 System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath來編譯並建立頁面的例項。這個方法我也嘗試過,答案是不行,還不如PageParser.GetCompiledPageInstance,至少後者能讓程式執行起來。使用BuildManager.CreateInstanceFromVirtualPath時,當訪問的地址中不帶副檔名時就會出現“The resource cannot be found”錯誤,原因是在GetVPathBuildResultInternal(VirtualPath virtualPath, bool noBuild, bool allowCrossApp, bool allowBuildInPrecompile)中呼叫了Util.CheckVirtualFileExists(virtualPath)對虛擬路徑進行檢查,檢查時將虛擬路徑轉換為物理路徑,檢查當前請求的頁面檔案是否存在,對於萬用字元對映應用程式,很多地址是實際上不存在的,所以就出現“The resource cannot be found”錯誤。而PageParser.GetCompiledPageInstance中通過呼叫 HostingEnvironment.AddVirtualPathToFileMapping避免了這個問題。而這個方法被
Internal保護,在程式碼中也無法呼叫。
      我覺得問題的核心是ASP.NET 2.0設計者在設計時沒有考慮萬用字元對映這樣的情況。是忽略還是另有考慮,就不得而知了。但ASP.NET 1.1能正確處理這個問題,而ASP.NET 2.0卻處理不了,這裡很不應該的。使用萬用字元對映的Web應用程式使用者只能望ASP.NET 2.0心嘆。最近花了很大精力想把部落格園的程式遷移到ASP.NET 2.0,而結果卻是無法遷移到ASP.NET 2.0,令人失望! 只能寄希望微軟推出相應的補丁。
     還好,使用萬用字元對映的Web應用程式不是很多,這個問題影響不是很大。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-343212/,如需轉載,請註明出處,否則將追究法律責任。

相關文章