摘要:大多數 Web 站點的用戶界面都需要有圖像,這些圖像通常存儲在磁盤上。本文介紹如何從程序集提供圖像。從程序集提供圖像可以避免眾多文件散布在磁盤上,簡化 Web 服務器的安裝和配置,以及提高圖像的安全性。(本文包含一些指向英文站點的鏈接)
適用于: 下載 MFRImages.exe 示例文件。下載內容中包括 readme.htm,用于說明如何配置示例。 目錄簡介經常會聽到那些編寫過 Windows 控件,后來又轉向 Web 的程序員抱怨:“為什么不能像控件那樣將圖像存儲在同一個程序集內呢?” 答案是可以,您只需要知道如何做到這一點。本文介紹如何從一個程序集內提供圖像,并提供了兩種檢索圖像的方法。下載本頁頂端的 MFRImages.exe,以便完整地了解下面討論的示例代碼。 問題概述Web 站點上的圖像通常通過 URL 來引用,例如 http://www.morganskinner.com/images/morganskinner.gif。它告訴 Web 客戶端到哪里去查找圖像。在 Web 頁中,圖像與文字是分別下載的。圖像通常存儲在 Web 服務器中命名為 /images 等名稱的子目錄中,頁面只是提供到這些圖像的引用,以使它們能夠顯示在客戶端的瀏覽器中。 當創建自定義控件時,情況也是如此 - 圖像被用戶下載之前通常需要存儲在磁盤上。作為控件的編寫者,您可能希望能夠提供一個程序集,其中不僅包括控件,而且還包括相應的默認圖像?丶3知毩⑼暾苡幸,因為它只需要用戶進行較少的配置。(負責配置的用戶可能會忘記將大量圖像文件復制到生產服務器上,從而導致其他用戶的不滿。) 希望將資源綁定到程序集的另外一個原因可能是為了確保用戶不能更改這些資源 - 例如對于公司商標,應始終使用特定的圖像,而不能由某個用戶將錯誤的圖像(如不正確的字樣或顏色)誤保存到磁盤中。如果已對您的程序集進行了嚴格命名,當加載該程序集時,可以進行測試以確保該程序集未被篡改。 從程序集提供圖像的主要問題是 HTTP 需要通過 URL 獲得圖像 - 您不能只將直接插入 HTML 的一串字節返回給用戶,就期望能夠正確顯示圖像。設法將圖像請求重定向到程序集內的某個資源是很有必要的,在本文中我將介紹兩種方法。 在繼續下面的內容之前,還要注意一個問題。如果在 Web 服務器上沒有進行其他一些配置,想提供一個能夠正確呈現圖像的完全獨立控件是不可能的。您還需要在服務器上創建其他的文件(至少一個),或對 IIS 配置數據庫做一些更改,以將圖像提供給客戶端。不過,完成這些簡單的更改后,您就可以輕松地從任何程序集提供圖像了。 提供圖像既然問題的關鍵是要提供圖像,那么我們現在就進入正題。當從調用方傳來信息后,我們需要找到一種方法來加載圖像并在響應流中返回圖像。請注意,此示例中提供的圖像只有 .gif 一種類型 - 要提供其他類型的圖像,則需要通過圖像擴展名來推斷內容類型和圖像格式。 以下函數顯示如何從給定的程序集加載圖像,并通過 HttpResponse 流返回給客戶端。我們將把此函數作為后面代碼的基礎,并在接下來的內容中添加更有用的功能,例如異常處理和圖像緩存。我將把此函數定義為 ManifestImageLoader 類中的靜態函數。 public class ManifestImageLoader { 函數 RenderImage 接受程序集名稱、圖像名稱和響應流。有了有效的程序集名稱和圖像名稱,加載圖像并將其返回到輸出流就輕而易舉了。首先加載程序集,然后使用 Assembly.GetManifestResourceStream 函數返回已命名資源(在此實例中為圖像)的數據流。您需要為 System.Reflection 和 System.IO 添加 using 子句,使其能夠通過編譯,并引用 System.Drawing 程序集。 有了圖像數據流之后,可以使用 Image.FromStream() 方法從該字節流構造圖像 - 請注意,我們使用的是 System.Drawing 中的圖像類,而不是 System.Web.UI.WebControls 中具有類似名稱的類,因為前者具有訪問 Win32 圖像函數的權限,而后者在 Web 控件中包含了 <img> 標記。 您或許不熟悉 C# using 語法,該語法在 Try/Finally 塊中包含了代碼,可以確保對括號中的項調用 Dispose。 現在我們可以從程序集提供圖像了,首先需要能夠為該圖像創建 URL。第一種方法是使用 .ASPX 頁面。 從 ASPX 頁面提供圖像第一種提供圖像的方法需要使用 .ASPX 頁面,該頁面通常要駐留在服務器上的某個位置。頁面本身不包含內容 - 它的主要功能是從 URL 檢索參數,并使用這些參數來檢索圖像。 例如,有一個名為 ImageFromASPX.aspx 的頁面位于 Web 站點的根目錄下。然后可以在 HTML 中使用以下 URL 編碼語法來定義從此頁面提供的所有圖像。這里我們提供的是名為 winxp.gif 的標題圖像。
在 URL 中,我們定義了 ASPX 頁面的路徑,然后定義了兩個參數,一個是程序集名稱(在此實例中為 ImageServer),另一個是圖像名稱。這在安全性方面可能存在風險,因此在本文的后面我將介紹一種可用于此數據的加密方法。 在 ASPX 頁面的代碼中,可以寫入以下內容:
代碼所做的就是根據請求分析參數,然后調用我們前面編寫的 RenderImage 函數。正如您看到的,這并不難實現。但它有一個缺點,即所有對圖像的請求都需要通過同一個 URL。也就是說,每個自定義控件都必須知道 imageserver.aspx 文件的位置和名稱,才能提供圖像。如何避免這個限制是下一節的主題。 從自定義處理程序提供圖像如果您以前從未接觸過處理程序,我將在這里大概介紹一下。處理程序是用于實現 IHttpHandler 接口的對象。當包含給定文件擴展名的請求通過 ASP.NET 管道時,將為特定動詞(如 POST、GET 等)或一組動詞調用處理程序。 通常,ASP.NET 會檢查請求的文件擴展名,并將請求傳送給與該擴展名相關聯的處理程序。 了解了這些知識,我們就可以創建處理程序,將其與自己的文件擴展名相關聯(這樣,ASP.NET 就會知道我們要調用的是處理程序,而不是其他內容),并且以該方式提供圖像。 下面的代碼顯示的是一個簡單的處理程序,該處理程序使用了上面聲明的 RenderImage 函數。
這段代碼與上面為 ASPX 頁面顯示的代碼非常類似 - 從傳來的 URL 讀取參數,然后將這些參數傳遞到 RenderImage 函數。 現在,要想使用處理程序來提供圖像,我們需要使用不同的 URL。在此實例中,需要創建一個虛構的文件擴展名(即在 IIS 中不存在的擴展名),這樣,圖像請求就可以傳送給正確的處理程序。在此示例中我將使用擴展名“mfr”(表示“清單資源”)。圖像請求現在看起來有點像下面的描述。
注意,我還未指定資源的路徑,只是指定了文件擴展名 .mfr。 使用處理程序的主要好處是可以為所有請求調用該程序,而不用考慮它們的路徑。 要使處理程序能夠工作還需要另外兩個步驟。首先,需要修改 web.config,以指定新的處理程序: <configuration> <system.web> ... <httpHandlers> <add verb="GET" path="*.mfr" type="ImageServer.ManifestResourceHandler, ImageServer" /> </httpHandlers> </system.web> </configuration> 上述配置文件中的類型定義了實現處理程序的類型和程序集。注意,動詞屬性區分大小寫,因此應設置為 GET 而不是其他的大小寫形式。程序集本身需要駐留在您 Web 站點的二進制目錄中,或安裝在全局程序集緩存 (GAC) 中。 其次,您需要在 IIS 管理中編輯 Web 服務器的配置。單擊您要更改的 Web 站點的 Properties(屬性),選擇 Home Directory(主目錄)選項卡,然后單擊 Configuration(配置)。將顯示與以下窗口類似的窗口。 圖 1:配置 IIS 單擊 Add(添加)按鈕,為 .mfr 文件類型創建條目。每個擴展名都會被映射到處理資源請求的 ISAPI 過濾器。對于 ASP.NET,為 aspnet_isapi.dll 過濾器。此庫駐留在磁盤中已安裝的 Framework 下,因此要設置 .mfr 擴展名的所有請求以通過相應的 ISAPI dll,需要進行如下設置:
其余的設置顯示在以下圖像中。請注意,必須清除 Verify that file exists(檢查文件是否存在)復選框,否則永遠不會調用處理程序(因為磁盤中不存在實際的 .mfr 文件)。 圖 2:為擴展名設置屬性 現在應該可以運行處理程序了。在瀏覽器中鍵入映射到程序集中的資源的 URL: 圖 3:從程序集提供圖像 如果您接收到的不是請求的圖像,而是一個異常(如“‘null’不是‘stream’的有效值”),那么您可能遇到了目前我們還沒有在代碼中進行處理的一些小問題 - 如果圖像有錯誤怎么辦? 我們將在下一節對這個問題及其他一些小問題進行糾正。 增強代碼代碼中首先要處理的是大小寫形式。HTTP 認為以下所有 URL 都相同,因為 URL 不區分大小寫。
我們的代碼目前有一個問題,由于它不保留原 HTTP 請求不區分大小寫的特征,因此必須向 LoadAndReturnImage 函數再添加一些代碼。 不區分大小寫我們要做的是從程序集加載圖像而不考慮大小寫,但由于 Assembly.GetManifestResourceStream 是區分大小寫的,因此還得另想辦法。Assembly.GetManifestResourceNames() 函數將返回給定程序集內的所有資源列表,因此我們要做的就是調用此列表,與這些資源名稱進行不區分大小寫的比較,然后使用查找出的名稱:
這里我加載了包含資源的程序集,然后查找應用程序緩存以查看該程序集內的資源列表是否已被加載和緩存。如果沒有,我通過調用 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 ; // 將圖像寫入響應流... 此設置使圖像在一小時(這個時間顯然可以延長以減少服務器負載)后過期,并定義圖像可以在任意位置(客戶端、代理服務器、服務器等)進行緩存。它還定義了更改緩存行為的參數。 現在,代碼幾乎已經完成了,但我們需要決定如何處理異常情況。 關于異常的編程我們的代碼中可能會引發很多異!,F在,用戶的瀏覽器可能會斷開鏈接,甚至可能仍然會遇到 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 方法中,在程序集名稱和圖像名稱被傳遞給客戶端之前,我對它們進行了加密。 代碼的很多部分都可以進行擴展或改進。下面是我的幾點建議。
小結本文介紹了兩種方法,用于從程序集檢索格式適合包含在 Web 站點中的圖像。第一種方法是從 ASPX 頁面提供圖像,這種方法簡單而且不需要修改 Web 服務器配置,但是提供圖像的 ASPX 頁面的路徑必須正確,以使圖像能夠正確顯示。 另一種方法是從自定義處理程序提供圖像。這種方法克服了基于路徑的限制,但需要更改 IIS 配置數據庫,以允許由 aspnet_isapi.dll 擴展程序提供 .mfr 擴展名。而且還要為給定的應用程序修改 web.config。 我個人建議使用處理程序方法而不要使用 ASPX 方法,因為在 Web 服務器中配置處理程序方法后,使用起來會更容易(而且不需要路徑)。 |
文章來源于領測軟件測試網 http://www.kjueaiud.com/