從 .NET 程序集提供圖像(二)
2024-07-10 13:05:04
供稿:網友
 
中國最大的web開發資源網站及技術社區,
目錄:
增強代碼
整理
安全性
小結
關于作者 
增強代碼 
代碼中首先要處理的是大小寫形式。http 認為以下所有 url 都相同,因為 url 不區分大小寫。 
<img src=http://www.163design.net/n/a/".mfr?assem=imageserver&image=winxp.gif" />
<img src=http://www.163design.net/n/a/".mfr?assem=imageserver&image=winxp.gif" />
<img src=http://www.163design.net/n/a/".mfr?assem=imageserver&image=winxp.gif" />
 
我們的代碼目前有一個問題,由于它不保留原 http 請求不區分大小寫的特征,因此必須向 loadandreturnimage 函數再添加一些代碼。 
不區分大小寫 
我們要做的是從程序集加載圖像而不考慮大小寫,但由于 assembly.getmanifestresourcestream 是區分大小寫的,因此還得另想辦法。assembly.getmanifestresourcenames() 函數將返回給定程序集內的所有資源列表,因此我們要做的就是調用此列表,與這些資源名稱進行不區分大小寫的比較,然后使用查找出的名稱: 
assembly resourceassem = assembly.load ( assembly ) ;
// 查找緩存的名稱
string[] names = httpcontext.current.application [ assembly ] as string[] ;
if ( null == names )
{
 // 獲取程序集內所有資源的名稱
 names = resourceassem.getmanifestresourcenames() ;
 array.sort ( names , caseinsensitivecomparer.default ) ;
 httpcontext.current.application [ assembly ] = names ;
}
// 如果此程序集內存在一些資源,
// 檢查所需的資源
if ( names.length > 0 )
{
 // 在名稱數組中查找圖像
 int pos = array.binarysearch ( names , image , 
caseinsensitivecomparer.default ) ;
 if ( pos > -1 )
writeimage ( resourceassem , names[pos] , true ) ;
}
 
這里我加載了包含資源的程序集,然后查找應用程序緩存以查看該程序集內的資源列表是否已被加載和緩存。如果沒有,我通過調用 assembly.getmanifestresourcenames() 來讀取資源列表,然后對列表進行排序,并將其存儲在應用程序狀態中。 
然后,我就可以使用 array.binarysearch() 方法對名稱列表執行二進制搜索。這比按順序搜索字符串列表要快得多,且在應用程序狀態存儲資源列表所需的系統開銷也較小。 
這樣就解決了區分大小寫的問題,但性能如何呢?目前,每次當圖像請求到達時,我們都要調用全部代碼 - 除了最小的 web 站點之外,其余所有的請求都可能會造成嚴重的性能問題。下一節我們將處理這個問題。 
緩存 
像普通的圖像和一些 aspx 頁面一樣,把從程序集返回的圖像進行緩存是很有用的 - 因為這些圖像駐留在程序集中,一般不會頻繁地更改。 
如果我們在編寫一個簡單的 aspx 頁面,則可以添加 outputcache 指令以緩存頁面。但在我們的方案中,我們需要一種方法能夠通過編程方式將緩存控件標題添加到響應流中。幸運的是,在 asp.net 中這很容易完成。在把圖像寫入輸出流的函數中,只需添加以下幾行: 
response.cache.setexpires ( datetime.now.addminutes ( 60 ) ) ;
response.cache.setcacheability ( httpcacheability.public ) ;
response.cache.varybyparams["assem"] = true ;
response.cache.varybyparams["image"] = true ;
// 將圖像寫入響應流...
 
此設置使圖像在一小時(這個時間顯然可以延長以減少服務器負載)后過期,并定義圖像可以在任意位置(客戶端、代理服務器、服務器等)進行緩存。它還定義了更改緩存行為的參數。現在,代碼幾乎已經完成了,但我們需要決定如何處理異常情況。 
關于異常的編程 
我們的代碼中可能會引發很多異常。現在,用戶的瀏覽器可能會斷開鏈接,甚至可能仍然會遇到 asp.net 錯誤頁面。我們可以推測出很多種可能發生的情況。如下所示: 
·程序集可能不存在。
·程序集存在但不包含任何圖像。
·程序集可能不包含所請求的圖像。 
代碼也可能會造成其他錯誤。當找不到圖像時,瀏覽器默認的響應是返回一個帶有紅十字的圖像以表示一個斷開的鏈接。 
您當然希望用自己的默認圖像來代替此圖像。我已將一個默認的斷開鏈接圖像包含在 imageserver 程序集中,當發生異常時,該圖像將返回到瀏覽器。此行為可以通過在 web.config 文件的 appconfig 部分添加一個設置來實現。 
當發生錯誤時,如果要覆蓋默認行為(返回鏈上的異常),請將以下內容添加到 web.config 中。 
<appsettings>
 <add key="mfrshowbrokenlink" value="true" />
</appsettings>
 
現在,當代碼中出現異常時,將向瀏覽器返回斷開鏈接圖像,并在跟蹤日志中寫入警告。 
 
圖4:鏈接斷開時返回的圖像
如果查看跟蹤日志,您會看到有關圖像不存在的項,該項與下面類似。 
 
圖5:無效圖像請求的示例跟蹤日志輸出
本文討論的所有代碼都可以通過本頁頂部的 mfrimages.exe 下載鏈接獲得。此下載包括本節完成的所有增強工作。還包括一些測試頁,通過這些測試頁可以查看使用處理程序和 aspx 方法來呈現圖像的結果。 
整理 
下面要添加一種方法,以返回駐留在程序集內的圖像的正確 url,然后自定義控件編寫人員(或是您)可以調用此方法來返回圖像。 
如果已選擇了處理程序方法來提供圖像,則您所需的函數如下。 
public static string constructimageurl ( assembly assembly, string image )
{
 return string.concat ( ".mfr?assem=" , 
 httputility.urlencode ( assembly.fullname.tostring ( ) ) , 
 "&image=" ,
 httputility.urlencode ( image ) ) ;
}
 
對于這段代碼,我使用的是 string.concat(),因為它比 string.format() 大約快 4 倍。每個小技巧都會有所幫助! 然后可以用它來設置您在自定義控件中創建的所有圖像的 imageurl 屬性。 
安全性 
到目前為止的討論中,我們一直基于程序集名稱和資源名稱提供圖像。這沒什么不好,但這意味著任何人都可以得到磁盤上的程序集名稱,并可以嘗試通過將其他程序集名稱傳遞給處理程序來進行攻擊。 
為了避免這個潛在的問題,最好用某種方法對返回的值進行加密。我們可以提供一些從程序集名稱和圖像名稱生成的散列碼,或使用程序集名稱和圖像名稱的加密格式,然后在接收到請求后再進行解密。 
前一種方法(使用散列碼)需要服務器中有查找表,并且表中為每個提供的圖像填充了內容。這就給 web 領域帶來一個潛在的問題。在 web 領域,可能一個服務器提供初始圖像請求(并緩存散列碼),而另一個服務器實際響應圖像。 
因此,我選擇了第二種方法,即在返回到用戶的 url 中包含加密的程序集名稱和圖像名稱。這樣就不會遇到 web 領域中存在的問題,但卻意味著需要從瀏覽器多傳送一些數據到服務器,因為圖像 url 要長一些。 
示例代碼包含一個類,它使用 triple-des(數據加密標準)算法加密和解密字符串。通常,程序集名稱和圖像名稱在傳遞到客戶端之前已進行了加密。當請求圖像時,這些值被解密,并調用與原來相同的代碼。 
我已將這些內容以可配置的方式添加到解決方案中。在 web.config 中僅有一個標志,如果設置為“true”,則會在向客戶端提供資源名稱時對其進行加密: 
<appsettings>
<add key="mfrsecure" value="true" />
 </appsettings>
 
在處理程序的 processrequest 方法中,我對此標記進行檢查: 
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 方法中,在程序集名稱和圖像名稱被傳遞給客戶端之前,我對它們進行了加密。代碼的很多部分都可以進行擴展或改進。下面是我的幾點建議。 
·當無法找到資源時,配置項不對使用的圖像進行硬編碼,而是指定圖像的 url。這樣在出現異常時,您就可以從磁盤(或從其他程序集)加載特定的圖像并將其返回到瀏覽器。·圖像的緩存超時也可以定義為配置項。
·可以擴展代碼,以允許從程序集提供任何類型的圖像 - 目前,mime 類型被硬編碼為 image/gif。
·對于為何此示例中的代碼不能提供程序集內的其他資源,沒有什么原因 - 您完全可以提供 txt 文件、wav 文件等。 
小結 
本文介紹了兩種方法,用于從程序集檢索格式適合包含在 web 站點中的圖像。第一種方法是從 aspx 頁面提供圖像,這種方法簡單而且不需要修改 web 服務器配置,但是提供圖像的 aspx 頁面的路徑必須正確,以使圖像能夠正確顯示。 
另一種方法是從自定義處理程序提供圖像。這種方法克服了基于路徑的限制,但需要更改 iis 配置數據庫,以允許由 aspnet_isapi.dll 擴展程序提供 .mfr 擴展名。而且還要為給定的應用程序修改 web.config。我個人建議使用處理程序方法而不要使用 aspx 方法,因為在 web 服務器中配置處理程序方法后,使用起來會更容易(而且不需要路徑)。 
關于作者 
morgan 是 microsoft 在英國工作的應用程序開發顧問,專攻 visual c#、控件、winforms 和 asp.net。自從 2000 年發布 pdc 以來,他就從事 .net 工作,并且非常喜歡 .net,因此加盟該本公司。他的主頁是 http://www.morganskinner.com/,在那兒您可以找到他寫的其他一些文章的鏈接。在有限的閑暇時間里,他喜歡在自家的花園中鋤鋤草,或者享受幾塊風味獨特的菜肉烘餅。