目錄: 增強(qiáng)代碼 整理 安全性 小結(jié) 關(guān)于作者
增強(qiáng)代碼
代碼中首先要處理的是大小寫形式。HTTP 認(rèn)為以下所有 URL 都相同,因?yàn)?URL 不區(qū)分大小寫。
<img src=http://cfan.net.cn/info/".mfr?assem=ImageServer&image=winxp.gif" /> <img src=http://cfan.net.cn/info/".mfr?assem=ImageServer&image=winxp.gif" /> <img src=http://cfan.net.cn/info/".mfr?assem=ImageServer&image=winxp.gif" />
我們的代碼目前有一個問題,由于它不保留原 HTTP 請求不區(qū)分大小寫的特征,因此必須向 LoadAndReturnImage 函數(shù)再添加一些代碼。
不區(qū)分大小寫
我們要做的是從程序集加載圖像而不考慮大小寫,但由于 Assembly.GetManifestResourceStream 是區(qū)分大小寫的,因此還得另想辦法。Assembly.GetManifestResourceNames() 函數(shù)將返回給定程序集內(nèi)的所有資源列表,因此我們要做的就是調(diào)用此列表,與這些資源名稱進(jìn)行不區(qū)分大小寫的比較,然后使用查找出的名稱:
Assembly resourceAssem = Assembly.Load ( assembly ) ;
// 查找緩存的名稱 string[] names = HttpContext.Current.Application [ assembly ] as string[] ;
if ( null == names ) { // 獲取程序集內(nèi)所有資源的名稱 names = resourceAssem.GetManifestResourceNames() ; Array.Sort ( names , CaseInsensitiveComparer.Default ) ; HttpContext.Current.Application [ assembly ] = names ; }
// 如果此程序集內(nèi)存在一些資源, // 檢查所需的資源 if ( names.Length > 0 ) { // 在名稱數(shù)組中查找圖像 int pos = Array.BinarySearch ( names , image , CaseInsensitiveComparer.Default ) ;
if ( pos > -1 ) WriteImage ( resourceAssem , names[pos] , true ) ; }
這里我加載了包含資源的程序集,然后查找應(yīng)用程序緩存以查看該程序集內(nèi)的資源列表是否已被加載和緩存。如果沒有,我通過調(diào)用 Assembly.GetManifestResourceNames() 來讀取資源列表,然后對列表進(jìn)行排序,并將其存儲在應(yīng)用程序狀態(tài)中。
然后,我就可以使用 Array.BinarySearch() 方法對名稱列表執(zhí)行二進(jìn)制搜索。這比按順序搜索字符串列表要快得多,且在應(yīng)用程序狀態(tài)存儲資源列表所需的系統(tǒng)開銷也較小。
這樣就解決了區(qū)分大小寫的問題,但性能如何呢?目前,每次當(dāng)圖像請求到達(dá)時,我們都要調(diào)用全部代碼 - 除了最小的 Web 站點(diǎn)之外,其余所有的請求都可能會造成嚴(yán)重的性能問題。下一節(jié)我們將處理這個問題。
緩存
像普通的圖像和一些 ASPX 頁面一樣,把從程序集返回的圖像進(jìn)行緩存是很有用的 - 因?yàn)檫@些圖像駐留在程序集中,一般不會頻繁地更改。
如果我們在編寫一個簡單的 ASPX 頁面,則可以添加 OutputCache 指令以緩存頁面。但在我們的方案中,我們需要一種方法能夠通過編程方式將緩存控件標(biāo)題添加到響應(yīng)流中。幸運(yùn)的是,在 ASP.NET 中這很容易完成。在把圖像寫入輸出流的函數(shù)中,只需添加以下幾行:
response.Cache.SetExpires ( DateTime.Now.AddMinutes ( 60 ) ) ; response.Cache.SetCacheability ( HttpCacheability.Public ) ; response.Cache.VaryByParams["assem"] = true ; response.Cache.VaryByParams["image"] = true ; // 將圖像寫入響應(yīng)流...
此設(shè)置使圖像在一小時(這個時間顯然可以延長以減少服務(wù)器負(fù)載)后過期,并定義圖像可以在任意位置(客戶端、代理服務(wù)器、服務(wù)器等)進(jìn)行緩存。它還定義了更改緩存行為的參數(shù)。現(xiàn)在,代碼幾乎已經(jīng)完成了,但我們需要決定如何處理異常情況。
關(guān)于異常的編程
我們的代碼中可能會引發(fā)很多異常。現(xiàn)在,用戶的瀏覽器可能會斷開鏈接,甚至可能仍然會遇到 ASP.NET 錯誤頁面。我們可以推測出很多種可能發(fā)生的情況。如下所示:
·程序集可能不存在。 ·程序集存在但不包含任何圖像。 ·程序集可能不包含所請求的圖像。
代碼也可能會造成其他錯誤。當(dāng)找不到圖像時,瀏覽器默認(rèn)的響應(yīng)是返回一個帶有紅十字的圖像以表示一個斷開的鏈接。
您當(dāng)然希望用自己的默認(rèn)圖像來代替此圖像。我已將一個默認(rèn)的斷開鏈接圖像包含在 ImageServer 程序集中,當(dāng)發(fā)生異常時,該圖像將返回到瀏覽器。此行為可以通過在 web.config 文件的 AppConfig 部分添加一個設(shè)置來實(shí)現(xiàn)。
當(dāng)發(fā)生錯誤時,如果要覆蓋默認(rèn)行為(返回鏈上的異常),請將以下內(nèi)容添加到 web.config 中。
<appSettings> <add key="MFRShowBrokenLink" value="true" /> </appSettings>
現(xiàn)在,當(dāng)代碼中出現(xiàn)異常時,將向?yàn)g覽器返回?cái)嚅_鏈接圖像,并在跟蹤日志中寫入警告。
圖4:鏈接斷開時返回的圖像
如果查看跟蹤日志,您會看到有關(guān)圖像不存在的項(xiàng),該項(xiàng)與下面類似。
圖5:無效圖像請求的示例跟蹤日志輸出
本文討論的所有代碼都可以通過本頁頂部的 MFRImages.exe 下載鏈接獲得。此下載包括本節(jié)完成的所有增強(qiáng)工作。還包括一些測試頁,通過這些測試頁可以查看使用處理程序和 ASPX 方法來呈現(xiàn)圖像的結(jié)果。
整理
下面要添加一種方法,以返回駐留在程序集內(nèi)的圖像的正確 URL,然后自定義控件編寫人員(或是您)可以調(diào)用此方法來返回圖像。
如果已選擇了處理程序方法來提供圖像,則您所需的函數(shù)如下。
public static string ConstructImageURL ( Assembly assembly, string image ) { return string.Concat ( ".mfr?assem=" , HttpUtility.UrlEncode ( assembly.FullName.ToString ( ) ) , "&image=" , HttpUtility.UrlEncode ( image ) ) ; }
對于這段代碼,我使用的是 string.Concat(),因?yàn)樗?string.Format() 大約快 4 倍。每個小技巧都會有所幫助! 然后可以用它來設(shè)置您在自定義控件中創(chuàng)建的所有圖像的 ImageURL 屬性。
安全性
到目前為止的討論中,我們一直基于程序集名稱和資源名稱提供圖像。這沒什么不好,但這意味著任何人都可以得到磁盤上的程序集名稱,并可以嘗試通過將其他程序集名稱傳遞給處理程序來進(jìn)行攻擊。
為了避免這個潛在的問題,最好用某種方法對返回的值進(jìn)行加密。我們可以提供一些從程序集名稱和圖像名稱生成的散列碼,或使用程序集名稱和圖像名稱的加密格式,然后在接收到請求后再進(jìn)行解密。
前一種方法(使用散列碼)需要服務(wù)器中有查找表,并且表中為每個提供的圖像填充了內(nèi)容。這就給 Web 領(lǐng)域帶來一個潛在的問題。在 Web 領(lǐng)域,可能一個服務(wù)器提供初始圖像請求(并緩存散列碼),而另一個服務(wù)器實(shí)際響應(yīng)圖像。
因此,我選擇了第二種方法,即在返回到用戶的 URL 中包含加密的程序集名稱和圖像名稱。這樣就不會遇到 Web 領(lǐng)域中存在的問題,但卻意味著需要從瀏覽器多傳送一些數(shù)據(jù)到服務(wù)器,因?yàn)閳D像 URL 要長一些。
示例代碼包含一個類,它使用 Triple-DES(數(shù)據(jù)加密標(biāo)準(zhǔn))算法加密和解密字符串。通常,程序集名稱和圖像名稱在傳遞到客戶端之前已進(jìn)行了加密。當(dāng)請求圖像時,這些值被解密,并調(diào)用與原來相同的代碼。
我已將這些內(nèi)容以可配置的方式添加到解決方案中。在 web.config 中僅有一個標(biāo)志,如果設(shè)置為“true”,則會在向客戶端提供資源名稱時對其進(jìn)行加密:
<appSettings> <add key="MFRSecure" value="true" /> </appSettings>
在處理程序的 ProcessRequest 方法中,我對此標(biāo)記進(jìn)行檢查:
bool secure = false ; string shouldSecure = ConfigurationSettings.AppSettings["MFRSecure"] ;
if ( null != shouldSecure ) secure = Convert.ToBoolean ( shouldSecure ) ;
string assembly = context.Request.QueryString["assem"] ; string image = context.Request.QueryString["image"] ;
if ( secure ) { assembly = Crypto.Decrypt ( assembly ) ; image = Crypto.Decrypt ( image ) ; }
ManifestImageLoader.RenderImage ( assembly , image ) ;
類似地,在前面介紹的 ConstructImageURL 方法中,在程序集名稱和圖像名稱被傳遞給客戶端之前,我對它們進(jìn)行了加密。代碼的很多部分都可以進(jìn)行擴(kuò)展或改進(jìn)。下面是我的幾點(diǎn)建議。
·當(dāng)無法找到資源時,配置項(xiàng)不對使用的圖像進(jìn)行硬編碼,而是指定圖像的 URL。這樣在出現(xiàn)異常時,您就可以從磁盤(或從其他程序集)加載特定的圖像并將其返回到瀏覽器。·圖像的緩存超時也可以定義為配置項(xiàng)。 ·可以擴(kuò)展代碼,以允許從程序集提供任何類型的圖像 - 目前,mime 類型被硬編碼為 image/GIF。 ·對于為何此示例中的代碼不能提供程序集內(nèi)的其他資源,沒有什么原因 - 您完全可以提供 TXT 文件、WAV 文件等。
小結(jié)
本文介紹了兩種方法,用于從程序集檢索格式適合包含在 Web 站點(diǎn)中的圖像。第一種方法是從 ASPX 頁面提供圖像,這種方法簡單而且不需要修改 Web 服務(wù)器配置,但是提供圖像的 ASPX 頁面的路徑必須正確,以使圖像能夠正確顯示。
另一種方法是從自定義處理程序提供圖像。這種方法克服了基于路徑的限制,但需要更改 IIS 配置數(shù)據(jù)庫,以允許由 aspnet_isapi.dll 擴(kuò)展程序提供 .mfr 擴(kuò)展名。而且還要為給定的應(yīng)用程序修改 web.config。我個人建議使用處理程序方法而不要使用 ASPX 方法,因?yàn)樵?Web 服務(wù)器中配置處理程序方法后,使用起來會更容易(而且不需要路徑)。
關(guān)于作者
Morgan 是 Microsoft 在英國工作的應(yīng)用程序開發(fā)顧問,專攻 Visual C#、控件、WinForms 和 ASP.NET。自從 2000 年發(fā)布 PDC 以來,他就從事 .NET 工作,并且非常喜歡 .NET,因此加盟該本公司。他的主頁是 http://www.morganskinner.com/,在那兒您可以找到他寫的其他一些文章的鏈接。在有限的閑暇時間里,他喜歡在自家的花園中鋤鋤草,或者享受幾塊風(fēng)味獨(dú)特的菜
|