ASP.NET 成功的其中一個原因在於它降低了Web 開發人員的門檻。即便您不是計算機科學博士也可以寫ASP.NET 程式碼。我在工作中遇到的許多ASP.NET 開發人員都是自學成材的,他們在編寫C# 或Visual Basic® 之前都在編寫Microsoft® Excel® 電子表格。現在,他們在編寫Web 應用程序,總的來說,他們所做的工作值得表揚。
但與能力隨之而來的還有責任,即使是經驗豐富的ASP.NET 開發人員也難免會出錯。在多年的ASP.NET 專案諮詢工作中,我發現某些錯誤特別容易導致缺陷不斷發生。其中某些錯誤會影響效能。其他錯誤會抑制可伸縮性。有些錯誤還會使開發團隊耗費寶貴的時間來追蹤錯誤和意外的行為。
以下是會導致ASP.NET 生產應用程式的發布過程中出現問題的10 個缺陷以及可避免它們的方法。所有範例均來自我對真實的公司建立真實的Web 應用程式的親身體驗,在某些情況下,我會透過介紹ASP.NET 開發團隊在開發過程中遇到的一些問題來提供相關的背景。
LoadControl 和輸出快取極少有不使用使用者控制項的ASP.NET 應用程式。在出現母版頁之前,開發人員使用使用者控制項來擷取公用內容,如頁首和頁尾。即使在ASP.NET 2.0 中,使用者控制項也提供了有效的方法來封裝內容和行為以及將頁面分為多個區域,這些區域的快取能力可以獨立於作為整體的頁面進行控制(一種稱為段緩存的特殊輸出緩存形式)。
用戶控制項可以採用聲明的方式加載,也可以強制加載。強制載入依賴Page.LoadControl,它實例化使用者控制項並傳回控制項參考。如果使用者控制項包含自訂類型的成員(例如,公有屬性),則您可以轉換該參考並從您的程式碼存取自訂成員。圖1 中的使用者控制項實作名為BackColor 的屬性。以下程式碼載入使用者控制項並向BackColor 指派一個值:
protected void Page_Load(object sender, EventArgs e){// 載入使用者控制項並將其新增至頁面中Control control = LoadControl("~/MyUserControl.ascx");PlaceHolder11 .Controls.Add(control);// 設定其背景色((MyUserControl)control).BackColor = Color.Yellow;}
以上程式碼其實很簡單,但卻是一個等待粗心的開發人員掉進去的陷阱。您能找出其中的破綻嗎?
如果您猜到該問題與輸出快取有關,那麼您是正確的。如您所看到的一樣,上述程式碼範例編譯和運行都正常,但是如果嘗試將以下語句(完全合法)新增至MyUserControl.ascx 中:
<%@ OutputCache Duration="5" VaryByParam="None" %>
則當您下一次運行該頁面時,您將看到InvalidCastException (oh joy!) 和以下錯誤訊息:
“無法將類型為'System.Web.UI.PartialCachingControl'的物件轉換為類型'MyUserControl'。”
因此,此程式碼在沒有OutputCache 指令時運作正常,但如果新增了OutputCache 指令就會出錯。 ASP.NET 不應該以這種方式運作。頁面(和控制)對於輸出快取應該是不可知的。那麼,這代表什麼意思呢?
問題在於為使用者控制項啟用輸出快取時,LoadControl 不再傳回對控制項實例的參考;相反,它會傳回對PartialCachingControl 實例的引用,而PartialCachingControl 可能會也可能不會包裝控制項實例,這取決於控制項的輸出是否被快取.因此,如果開發人員呼叫LoadControl 以動態載入使用者控制項並且為了存取控制項特定的方法和屬性而轉換控制項引用,他們必須注意進行該操作的方式,以便不管是否具有OutputCache 指令,程式碼都可以執行。
圖2 說明動態載入使用者控制項以及轉換傳回的控制項引用的正確方法。以下是其運作方式概要:
• 如果ASCX 檔案缺少OutputCache 指令,則LoadControl 傳回一個MyUserControl 引用。 Page_Load 將該參考轉換為MyUserControl 並設定控制項的BackColor 屬性。
• 如果ASCX 檔案包含一個OutputCache 指令且控制項的輸出沒有被緩存,則LoadControl 傳回一個對PartialCachingControl 的引用,此PartialCachingControl 的CachedControl 屬性包含對基礎MyUserControl 的參考。 Page_Load 將PartialCachingControl.CachedControl 轉換為MyUserControl 並設定該控制項的BackColor 屬性。
• 如果ASCX 檔案包含一個OutputCache 指令且控制項的輸出被緩存,則LoadControl 傳回一個對PartialCachingControl(其CachedControl 屬性為空)的參考。請注意,Page_Load 不再繼續執行操作。無法設定控制項的BackColor 屬性,因為該控制項的輸出來自輸出快取。換句話說,根本沒有要設定屬性的MyUserControl。
不管.ascx 檔案中是否有OutputCache 指令,圖2中的程式碼都會運作。雖然看起來複雜一點,但它會避免煩人的錯誤。簡單並不總是代表易於維護。
傳回頁首會話和輸出快取談到輸出快取,ASP.NET 1.1 和ASP.NET 2.0 都存在一個潛在的問題,問題會影響在Windows Server™ 2003 和IIS 6.0 上執行的伺服器中的輸出快取頁。我曾經親眼看到該問題在ASP.NET 生產伺服器中出現過兩次,這兩次都是透過關閉輸出緩衝來解決的。後來我了解到有一個比禁用輸出快取更好的解決方案。以下是我第一次遇到該問題時的情況。
當時的情況是這樣的,某個網站(我們在此稱為Contoso.com,它在小型ASP.NET Web 領域中運行公共電子商務應用程式)與我的團隊聯繫,抱怨他們遇到了“跨線程”錯誤。使用Contoso.com 網站的客戶常常突然遺失已輸入的數據,但卻看到另一用戶的相關數據。稍做分析即發現,跨執行緒這個描述並不準確;「跨會話」錯誤較為貼切。看起來Contoso.com 是在會話狀態中儲存資料的,由於某些原因,使用者偶爾會隨機連接到其他使用者的會話。
我的一個團隊成員編寫了一個診斷工具,用來將每個HTTP 請求和回應的關鍵要素(包括Cookie 標頭)記錄到日誌中。然後,他將該工具安裝在Contoso.com 的Web 伺服器上,並讓其運行了幾天。結果非常明顯。大概每100000 個請求中會發生一次這樣的情況:ASP.NET 正確地為全新會話分配一個會話ID 並傳回Set-Cookie 標頭中的會話ID。然後,它會在下一個緊相鄰的請求中傳回相同的會話ID(即,相同的Set-Cookie 標頭),即使該請求已經與一個有效的會話相關聯並且正確提交了Cookie 中的會話ID。實際上,ASP.NET 是隨機將使用者從自己的會話中切換出去並將他們連接到其他會話。
我們很驚訝,於是開始尋找原因。我們先檢查了Contoso.com 的源代碼,讓我們感到欣慰的是,問題不在那裡。接著,為了確保問題與應用程式宿主在Web 領域無關,我們只保留一個伺服器在運行,而關閉了所有其他伺服器。問題仍然存在,這並不意外,因為我們的日誌顯示相符的Set-Cookie 標頭絕對不會來自兩個不同的伺服器。 ASP.NET 意外地產生了重複的會話ID,這令人難以置信,因為它使用.NET Framework RNGCryptoServiceProvider 類別來產生這些ID,並且會話ID 的長度足以確保相同的ID 絕不會產生兩次(至少在下一個兆年內不會生成兩次)。除此之外,即使RNGCryptoServiceProvider 錯誤地產生了重複的隨機數字,也無法解釋ASP.NET 為何不可思議地將有效的會話ID 替換為新的ID(不唯一)。
憑直覺,我們決定看一下輸出快取。當OutputCacheModule 快取HTTP 回應時,它必須小心不要快取了Set-Cookie 標頭;否則,包含新會話ID 的快取回應會將快取回應的所有接收者(以及其請求產生了快取回應的使用者)連接到相同會話。我們檢查了原始程式碼;Contoso.com 在兩個頁面中啟用了輸出快取。我們關閉了輸出緩存。結果,應用程式運行數天而沒有發生一個跨會話問題。此後,它運行了兩年多都沒有發生任何錯誤。在另一家具有不同應用程式和一組不同Web 伺服器的公司中,我們看到完全相同的問題也消失了。就像在Contoso.com 一樣,消除輸出快取就能解決問題。
Microsoft 後來確認此行為源自於OutputCacheModule 中的問題。 (當您閱讀本文時,可能已經發布了更新。)當ASP.NET 與IIS 6.0 一起使用並且啟用核心模式快取時,OutputCacheModule 有時無法從它傳遞給Http.sys 的快取回應中刪除Set-Cookie 標頭。以下是導致出現錯誤的特定事件順序:
• 最近沒有造訪網站(因此也沒有對應的會話)的使用者請求一個啟用了輸出快取的頁面,但是其輸出目前在快取中不可用。
• 此要求執行用於存取使用者最新建立的會話的程式碼,從而導致會話ID Cookie 在回應的Set-Cookie 標頭中傳回。
• OutputCacheModule 提供Http.sys 輸出,但無法從回應中刪除Set-Cookie 標頭。
• Http.sys 在後續的請求中傳回快取回應,誤將其他使用者連接到會話。
故事的寓意又是什麼呢?會話狀態和核心模式輸出快取不能混合使用。如果您在啟用輸出快取的頁面中使用會話狀態,且應用程式在IIS 6.0 上執行,則您需要關閉核心模式輸出快取。您仍將受益於輸出緩存,但因為核心模式輸出快取比普通輸出快取快得多,所以快取不會同樣有效。有關此問題的詳細信息,請參見support.microsoft.com/kb/917072。
您可以透過在頁面的OutputCache 指令中包含VaryByParam="*" 屬性來關閉單一頁面的核心模式輸出緩存,雖然這樣做可能導致記憶體需求驟增。另一種更安全的方法是透過在web.config 中包含下列元素來關閉整個應用程式的核心模式快取:
<httpRuntime enableKernelOutputCache="false" />
您也可以使用註冊表設定來全域性停用核心模式輸出緩存,即停用全部伺服器的核心模式輸出快取。有關詳細信息,請參見support.microsoft.com/kb/820129。
每次我聽到客戶報告會話發生了費解的問題,我都會詢問他們是否在任何頁面中使用了輸出快取。如果確實使用了輸出緩存,並且宿主作業系統是Windows Server 2003,我會建議他們停用核心模式輸出快取。問題通常就會迎刃而解。如果問題沒有解決,則錯誤存在於程式碼中。警惕!
返回頁首
Forms 身份驗證票證生存期您能找出以下代碼的問題嗎?
FormsAuthentication.RedirectFromLoginPage(username, true);
此程式碼看似沒有問題,但絕不能在ASP.NET 1.x 應用程式中使用,除非應用程式中其他位置的程式碼抵消了此語句的負面作用。如果您不能確定原因,請繼續閱讀。
FormsAuthentication.RedirectFromLoginPage 執行兩個任務。首先,當FormsAuthenticationModule 將使用者重新導向到登入頁面時,FormsAuthentication.RedirectFromLoginPage 將使用者重新導向到他們原來要求的頁面。其次,它發布一個身份驗證票證(通常攜帶在Cookie 中,而且在ASP.NET 1.x 中總是攜帶在Cookie 中),這個票證允許使用者在預定的一段時間內保持已經過身份驗證狀態。
問題就在於這個時間段。在ASP.NET 1.x 中,向RedirectFromLoginPage 傳遞另一個為false 的參數會發出一個臨時驗證票證,該票證預設在30 分鐘之後到期。 (您可以使用web.config 的元素中的Timeout 屬性來更改逾時期限。)然而,傳遞另一個為true 的參數則會發出永久身份驗證票證,其有效期為50 年!這樣就會發生問題,因為如果有人竊取了該身分驗證票證,他們就可以在票證的有效期內使用受害者的身分存取網站。竊取身分驗證票證有多種方法— 在公共無線存取點探測未加密的通訊、跨網站編寫腳本、以實體方式存取受害者的電腦等等— 因此,向RedirectFromLoginPage 傳遞true 比禁用您的網站的安全性好不了多少。幸運的是,此問題已經在ASP.NET 2.0 中解決了。現在的RedirectFromLoginPage 以相同的方式接受在web.config 中為臨時和永久身份驗證票證指定的逾時。
一個解決方案是絕不在ASP.NET 1.x 應用程式的RedirectFromLoginPage 的第二個參數中傳遞true。但這不切實際,因為登入頁的特點通常是包含一個「將我保持為登入狀態」框,使用者可以選中該框以收到永久而不是臨時身份驗證Cookie。另一個解決方案是使用Global.asax(如果您願意的話,也可以使用HTTP 模組)中的程式碼段,此程式碼段會在包含永久身分驗證票證的Cookie 返回瀏覽器之前對其進行修改。
圖3 包含一個這樣的程式碼段。如果此程式碼段位於Global.asax 中,它會修改傳出永久Forms 驗證Cookie 的Expires 屬性,以使Cookie 在24 小時後過期。透過修改註釋為「新的過期日期」的行,您可以將超時設定為您喜歡的任何日期。
您可能會覺得奇怪,Application_EndRequest 方法呼叫本機Helper 方法(GetCookieFromResponse) 來檢查驗證Cookie 的傳出回應。 Helper 方法是解決ASP.NET 1.1 中另一個錯誤的方法,如果您使用HttpCookieCollection 的字串索引產生器來檢查不存在的Cookie,此錯誤會導致假Cookie 新增至回應。使用整數索引產生器作為GetCookieFromResponse 可以解決此問題。
返回頁首視圖狀態:無聲的效能殺手從某種意義上說,視圖狀態是有史以來最偉大的事情。畢竟,視圖狀態使得頁面和控制項能夠在回發之間保持狀態。因此,您不必像在傳統的ASP 中那樣編寫程式碼,以防止在按一下按鈕時文字方塊中的文字消失,或在回發後重新查詢資料庫和重新綁定DataGrid。
但是視圖狀態也有缺點:當它成長得過大時,它便成為一個無聲的效能殺手。某些控制項(例如文字方塊)會根據檢視狀態作出相應判斷。其他控制項(特別是DataGrid 和GridView)則根據顯示的資訊量來決定視圖狀態。如果GridView 顯示200 或300 行數據,我會望而生畏。即使ASP.NET 2.0 視圖狀態大致是ASP.NET 1 x 視圖狀態的一半大小,一個糟糕的GridView 也可以輕鬆地將瀏覽器和Web 伺服器之間的連接的有效頻寬減少50% 或更多。
您可以透過將EnableViewState 設為false 來關閉單一控制項的檢視狀態,但某些控制項(特別是DataGrid)在無法使用檢視狀態時會失去某些功能。控制視圖狀態的更佳解決方案是將其保留在伺服器上。在ASP.NET 1.x 中,您可以重寫頁面的LoadPageStateFromPersistenceMedium 和SavePageStateToPersistenceMedium 方法並以您喜歡的方式處理視圖狀態。圖4 中的程式碼顯示的重寫可防止視圖狀態保留在隱藏欄位中,而將其保留在會話狀態中。當與預設會話狀態進程模型一起使用時(即,會話狀態儲存在記憶體中的ASP.NET 輔助進程中時),在會話狀態中儲存視圖狀態尤其有效。相反,如果會話狀態儲存在資料庫中,則只有測試才能顯示在會話狀態中保留視圖狀態會提高還是降低效能。
在ASP.NET 2.0 中使用相同的方法,但是ASP.NET 2.0 能夠提供更簡單的方法將視圖狀態保留在會話狀態中。首先,定義一個自訂頁面適配器,其GetStatePersister 方法
回傳.NET Framework SessionPageStatePersister 類別的一個實例: public class SessionPageStateAdapter :System.Web.UI.Adapters.PageAdapter{public over PageStatePersister
GetStatePisterPisterP.); }}
然後,透過將App.browsers 檔案以以下方式放入應用程式的App_Browsers 資料夾,將自訂頁面適配器註冊為預設頁面適配器:
<browsers><browser refID="Default"><controlAdapters><adapter controlType=" System.Web.UI.Page"adapterType="SessionPageStateAdapter" /></controlAdapters></browser></browsers>
(您可以將檔案命名為您喜歡的任何名稱,只要它的副檔名為.browsers 即可。)此後,ASP.NET 將載入頁面適配器並使用傳回的SessionPageStatePersister 以保留所有頁面狀態,包括視圖狀態。
使用自訂頁適配器的一個缺點是它全局性地作用於應用程式中的每一頁。如果您寧願將其中一些頁面的視圖狀態保留在會話狀態中而不保留其他頁面的視圖狀態,請使用圖4 中顯示的方法。另外,如果使用者在同一會話中建立多個瀏覽器窗口,您使用該方法可能會遇到問題。
回頁首
SQL Server 會話狀態:另一個效能殺手
ASP.NET 讓在資料庫中儲存會話狀態變得簡單:只需切換web.config 中的開關,會話狀態就會輕鬆地移至後端資料庫。對於在Web 領域中運行的應用程式來說,這是一項重要功能,因為它允許該領域中的每個伺服器共享會話狀態的一個公共庫。新增的資料庫活動降低了單一請求的效能,但是可擴展性的提高彌補了效能的損失。
這看起來都還不錯,但是您略微考慮一下下列幾點,情況就會有所不同:
• 即使在使用會話狀態的應用程式中,大多數頁面也不使用會話狀態。
• 預設情況下,ASP.NET 會話狀態管理器對每個請求中的會話資料儲存執行兩個存取(一個讀取存取和一個寫入存取),而不管請求的頁是否使用會話狀態。
換句話說,當您使用SQL Server™ 會話狀態選項時,您在每個請求中都要付出代價(兩個資料庫存取)— 甚至在與會話狀態無關的頁面的請求中。這會直接對整個網站的吞吐量造成負面影響。
圖5 消除不必要的會話狀態資料庫存取
那麼您該怎麼辦呢?很簡單:停用不使用會話狀態的頁中的會話狀態。這樣做總是一個好辦法,但是當會話狀態儲存在資料庫中時,該方法尤其重要。圖5 顯示如何停用會話狀態。如果頁面完全不使用會話狀態,請在其Page 指令中包含EnableSessionState="false",如下所示:
<%@ Page EnableSessionState="false" ... %>
此指令阻止會話狀態管理器在每個請求中讀取和寫入會話狀態資料庫。如果頁面從會話狀態中讀取數據,但卻不寫入資料(即,不修改使用者會話的內容),則將EnableSessionState 設定為ReadOnly,如下所示:
<%@ Page EnableSessionState="ReadOnly" ... %>
最後,如果頁面需要對會話狀態進行讀取/寫入訪問,則省略EnableSessionState 屬性或將其設為true:
<%@ Page EnableSessionState="true" ... %>
透過以這種方式控制會話狀態,可以確保ASP.NET 只在真正需要時才存取會話狀態資料庫。消除不必要的資料庫存取是建立高效能應用程式的第一步。
順便說一下,EnableSessionState 屬性是公開的。該屬性自ASP.NET 1.0 以來就已經進行了說明,但是我至今仍很少見到開發人員利用該屬性。也許是因為它對於記憶體中的預設會話狀態模型並不十分重要。但是它對於SQL Server 模型卻很重要。
傳回頁首未快取的角色以下語句經常出現於ASP.NET 2.0 應用程式的web.config 檔案以及介紹ASP.NET 2.0 角色管理器的範例:
<roleManager enabled="true" />
但如上述所示,該語句確實會對表現產生明顯的負面影響。您知道為什麼嗎?
預設情況下,ASP.NET 2.0 角色管理器不會快取角色資料。相反,它會在每次需要確定使用者屬於哪個角色(如果有)時參考角色資料儲存。這意味著一旦使用者經過了身份驗證,任何利用角色資料的頁面(例如,使用啟用了安全裁減設定的網站圖的頁,以及使用web.config 中基於角色的URL 指令進行存取受到限制的頁)將導致角色管理器查詢角色資料儲存。如果角色儲存在資料庫中,那麼對於每個請求需要存取多個資料庫的情況,您可以輕鬆地免除存取多個資料庫。解決方案是配置角色管理器以在Cookie 中快取角色資料:
<roleManager enabled="true" cacheRolesInCookie="true" />
您可以使用其他<roleManager> 屬性來控制角色Cookie 的特性— 例如,Cookie 應維持有效的期限(以及角色管理器因此傳回角色資料庫的頻率)。角色Cookie 預設是經過簽署和加密的,因此安全風險雖然不為零,但也有所緩解。
返回頁首設定檔屬性序列化
ASP.NET 2.0 設定檔服務為保持每個使用者的狀態(例如個人化首選項和語言首選項)的問題提供了一個現成的解決方案。若要使用設定檔服務,您可以定義一個XML 設定文件,其中包含要保留的代表單一使用者的屬性。然後,ASP.NET 編譯一個包含相同屬性的類,並透過新增到頁面的設定檔屬性提供對類別實例的強型別存取。
設定檔靈活性很強,它甚至允許將自訂資料類型用作設定檔屬性。但是,其中卻存在一個問題,我親眼看到該問題導致開發人員出差錯。圖6 包含一個名為Posts 的簡單類,以及將Posts 用作設定檔屬性的設定檔定義。但是,該類別和該設定檔在運行時會產生意外的行為。您能找出其中的原因嗎?
問題在於Posts 包含一個名為_count 的私有字段,該字段必須進行序列化和反序列化,才能完全凍結和重新凍結類別實例。但是_count 卻沒有經過序列化和反序列化,因為它是私有的,而且預設情況下ASP.NET 設定檔管理器使用XML 序列化對自訂類型進行序列化和反序列化。 XML 序列化程序將忽略非公共成員。因此,會對Posts 的實例進行序列化和反序列化,但是每次反序列化類別實例時,_count 都會重設為0。
一種解決方案是使_count 成為公用欄位而非私有欄位。另一種解決方案是使用公共讀取/寫入屬性封裝_count。最佳解決方案是將Posts 標記為可序列化(使用SerializableAttribute),並將設定檔管理器設定為使用.NET Framework 二進位序列化程式對類別實例進行序列化和反序列化。該解決方案能夠保持類別本身的設計。與XML 序列化程式不同的是,二進位序列化程式序列化字段,而不管是否可以存取。圖7 顯示Posts 類別的修復版本並突出顯示了更改的附帶設定檔定義。
您應該牢記的一點是,如果您使用自訂資料類型作為設定檔屬性,並且該資料類型具有必須序列化才能完全序列化類型實例的非公共資料成員,則在屬性聲明中使用serializeAs="Binary"屬性並確保類型本身是可序列化的。否則,將無法進行完整的序列化,而且您還將浪費時間嘗試確定設定檔無法運作的原因。
當返回頁首線程池飽和在執行資料庫查詢並等待15 秒或更長時間來獲得傳回的查詢結果時,我經常對看到的實際的ASP.NET 頁數感到非常驚訝。 (我也等待了15 分鐘才看到查詢結果!)有時,延遲是由於返回的資料量很大而導致的不可避免的無奈結果;而有時,延遲則是由於資料庫的設計不佳導致的。但不管是什麼原因,長時間的資料庫查詢或任何類型的長時間I/O 操作都會在ASP.NET 應用程式中導致吞吐量的下降。
關於這個問題我以前已經詳細描述過,所以在此就不再作過多的說明了。我只說一點就夠了,ASP.NET 依賴有限的線程池處理請求,如果所有線程都被佔用來等待資料庫查詢、Web 服務呼叫或其他I/O 操作完成,則在某個操作完成並且釋放在出一個線程之前,其他請求都必須排隊等待。當請求排隊時,效能會急劇下降。如果佇列已滿,則ASP.NET 會使隨後的請求失敗並出現HTTP 503 錯誤。這種情況不是我們希望在Web 生產伺服器的生產應用程式上所樂見的。
解決方案非非同步頁面莫屬,這是ASP.NET 2.0 中最佳卻鮮為人知的功能之一。對非同步頁面的請求從一個執行緒上開始,但是當它開始一個I/O 操作時,它將傳回該執行緒以及ASP.NET 的IAsyncResult 介面。操作完成後,請求透過IAsyncResult 通知ASP.NET,ASP.NET 從池中提取另一個執行緒並完成對請求的處理。值得注意的是,當I/O 操作發生時,沒有佔用執行緒池執行緒。這樣可以透過阻止其他頁面(不執行較長的I/O 操作的頁面)的請求在佇列中等待,從而顯著地提高吞吐量。
您可以在MSDN®Magazine 的2005 年10 月刊中閱讀有關非同步頁面的所有資訊。 I/O 綁定而不是電腦綁定且需要很長時間執行的任何頁面很有可能成為非同步頁面。
當我將關於非同步頁面的資訊告知開發人員時,他們經常回答「那真是太棒了,但是我的應用程式中並不需要它們。」對此我回答說:「你們的任何頁面需要查詢資料庫嗎?負載可能會增加
。請切記這一點!
返回頁首模擬和ACL 授權以下是一個簡單的配置指令,但是每當在web.config 中看到它時都讓我眼前一亮:
<identity impersonate="true" />
此指令在ASP.NET 應用程式中啟用客戶端模擬。它將代表客戶端的存取權杖附加到處理請求的線程,以便作業系統執行的安全性檢查針對的是客戶端身份而不是輔助進程身份。 ASP.NET 應用程式很少需要模擬;我的經驗告訴我,開發人員通常都是因為錯誤的原因而啟用模擬的。以下是原因所在。
開發人員經常在ASP.NET 應用程式中啟用模擬,以便可以使用檔案系統權限來限制對頁面的存取。如果Bob 沒有查看Salaries.aspx 的權限,則開發人員將會啟用模擬,以便可以透過將存取控制清單(ACL) 設定為拒絕Bob 的讀取權限,阻止Bob 查看Salaries.aspx。但有以下隱患:對於ACL 授權來說,模擬是不必要的。在ASP.NET 應用程式中啟用Windows 驗證時,ASP.NET 會自動為要求的每個.aspx 頁面檢查ACL 並拒絕沒有讀取檔案權限的呼叫者的請求。即使禁用了模擬,它仍會這樣操作。
有的時候需要證明模擬的合理性。但是您通常可以用良好的設計來避免它。例如,假定Salaries.aspx 在資料庫中查詢只有管理者才能知道的薪資資訊。透過模擬,您可以使用資料庫權限拒絕非管理人員查詢薪資資料的能力。或者您可以不考慮模擬,並且透過為Salaries.aspx 設定ACL 以使非管理人員不具有讀取權限,從而限制對薪資資料的存取。後一種方法提供的性能更佳,因為它完全避免了模擬。它也消除了不必要的資料庫存取。為什麼查詢資料庫僅因安全性原因被拒絕?
順便說一下,我曾經幫忙對一個傳統的ASP 應用程式進行故障排除,該應用程式由於記憶體佔用不受限製而定期重新啟動。一個沒有經驗的開發人員將目標SELECT 語句轉換成了SELECT *,而沒有考慮要查詢的表格包含圖像,這些圖像很大而且數目很多。問題由於未檢測到內存洩漏而惡化。 (我的託管程式碼領域!)多年來運行正常的應用程式開始突然停止工作,因為以前傳回一兩千位元組資料的SELECT 語句現在卻傳回了幾兆位元組。如果再加上不充分的版本控制,開發團隊的生活將不得不“亢奮起來”— 這裡所謂的“亢奮”,就如同當您在晚上要睡覺時,還不得不看著您的孩子玩令人厭煩的足球遊戲一樣。
理論上,傳統的記憶體洩漏不會發生在完全由託管程式碼組成的ASP.NET 應用程式中。但是記憶體使用量不足會透過強制垃圾收集更頻繁地發生而影響效能。即使是在ASP.NET 應用程式中,也要警惕SELECT *!
返回頁首不要完全信賴它— 請設定資料庫的設定檔!
作為一名顧問,我經常被詢問為何應用程式沒有按預期執行。最近,有人詢問我的團隊為何ASP.NET 應用程式只完成請求文件所需吞吐量(每秒的請求數)的大約1/100。我們以前發現的問題是我們在無法正常運行的Web 應用程式中發現的問題特有的— 和我們所有人應該認真對待的教訓。
我們運行SQL Server Profiler 並監視此應用程式和後端的資料庫之間的互動情況。在一個更極端的案例中,僅僅只是一個按鈕單擊,就導致資料庫發生了1,500 多個錯誤。您不能那樣建立高效能的應用程式。良好的體系結構總是從良好的資料庫設計開始。不管您的程式碼的效率有多高,如果它被編寫不佳的資料庫所拖累,就會不起作用。
糟糕的資料存取體系結構通常源自於下面的一個或多個方面:
• 拙劣的資料庫設計(通常由開發人員設計,而不是資料庫管理員)。
• DataSets 和DataAdapters 的使用— 尤其是DataAdapter.Update,它適用於Windows 窗體應用程式和其他胖用戶端,但對於Web 應用程式來說通常不理想。
• 具有拙劣編制計算程序、以及執行相對簡單的操作需消耗很多CPU 週期的設計糟糕的資料存取層(DAL)。
必須先確定問題才能處理。確定資料存取問題的方式是執行SQL Server Profiler 或等效的工具以查看後台正在執行的操作。檢查應用程式和資料庫之間的通訊之後,效能調整才完成。試試看— 您可能會對您的發現大吃一驚。
返回頁首結論現在您已經了解在生成ASP.NET 生產應用程式過程中可能遇到的一些問題及其解決方案了。下一步是仔細查看您自己的程式碼並嘗試避免我在此概述的一些問題。 ASP.NET 可能降低了Web 開發人員的門檻,但是您的應用程式完全有理由靈活、穩定且有效率。請認真考慮,避免新手易犯的錯誤。
圖8 提供了一個簡短檢查列表,您可以使用它來避免本文中描述的缺陷。您可以建立一個類似的安全缺陷檢查清單。例如:
• 您是否已經對包含敏感資料的組態節進行加密?
• 您是否正在檢查並驗證在資料庫操作中使用的輸入,是否使用了HTML編碼輸入作為輸出?
• 您的虛擬目錄中是否包含具有不受保護的副檔名的檔案?
如果您重視網站、承載網站的伺服器以及它們所依賴的後端資源的完整性,則這些問題非常重要。
Jeff Prosise 是對MSDN Magazine 貢獻很大的編輯以及多本書籍的作者,這些書籍中包括Programming Microsoft .NET (Microsoft Press, 2002)。他也是軟體諮詢和教育公司Wintellect 的共同創辦人。
摘自MSDN Magazine 的2006 年7 月號。