程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> .NET實例教程 >> 通過避免下列10個常見ASP.NET缺陷使網站平穩運行

通過避免下列10個常見ASP.NET缺陷使網站平穩運行

編輯:.NET實例教程

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");PlaceHolder1.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 override PageStatePersister GetStatePersister ()    {return new SessionPageStatePersister(this.Page);    }}
然後,通過將 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 綁定而不是計算機綁定且需要很長時間執行的任何頁面很有可能成為異步頁面。

當我將關於異步頁面的信息告知開發人員時,他們經常回答“那真是太棒了,但是我的應用程序中並不需要它們。”對此我回答說:“你們的任何頁面需要查詢數據庫嗎?它們調用 Web 服務嗎?您是否已經檢查 ASP.Net 性能計數器中關於排隊請求和平均等待時間的統計信息?即使您的應用程序至今運行正常,但是隨著您的客戶規模的增長,應用程序的負載可能會增加。”

實際上,絕大多數實際的 ASP.Net 應用程序都需要異步頁面。請切記這一點!

返回頁首
模擬和 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 的共同創始人。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved