共用方式為


以資料 Web 控制項顯示二進位資料 (C#)

作者:Scott Mitchell

下載 PDF

在本教學課程中,我們將了解在網頁上呈現二進位資料的選項,包括顯示影像檔案和提供 PDF 檔案的「下載」連結。

簡介

在前面的教學課程中,我們探索了將二進位資料與應用程式的底層資料模型關聯的兩種技術,並使用 FileUpload 控制項將檔案從瀏覽器上傳到 Web 伺服器的檔案系統。 我們還沒有看到如何將上傳的二進位資料與資料模型關聯起來。 也就是說,檔案上傳並儲存到檔案系統後,該檔案的路徑必須儲存在適當的資料庫記錄中。 如果資料直接儲存在資料庫中,那麼上傳的二進位資料就不需要儲存到檔案系統中,而是必須注入到資料庫中。

不過,在考慮將資料與資料模型關聯之前,我們先看看如何向終端使用者提供二進位資料。 呈現文字資料很簡單,但是二進位資料該如何呈現呢? 當然,這取決於二進位資料的類型。 對於影像,我們可能想要顯示影像;對於 PDF、Microsoft Word 文件、ZIP 文件和其他類型的二進位資料,提供下載連結可能更合適。

在本教學課程中,我們將了解如何使用 GridView 和 DetailsView 等資料 Web 控制項來呈現二進位資料及其關聯的文字資料。 在下一個教學課程中,我們將把注意力轉向將上傳的檔案與資料庫關聯起來。

步驟 1:提供BrochurePath

Categories 表中的 Picture 列已包含各種類別影像的二進位資料。 具體來說,每個記錄的 Picture 列儲存顆粒狀、低品質、16 色點陣圖影像的二進位內容。 每個類別影像寬 172 像素,高 120 像素,佔用約 11 KB。 更重要的是,Picture 列中的二進位內容包括一個 78 位元組的 OLE 標頭,在顯示影像之前必須將其剝離。 之所以存在此標頭訊息,是因為 Northwind 資料庫源自 Microsoft Access。 在 Access 中,二進位資料使用 OLE 物件資料類型儲存,該資料類型附加在該標頭上。 現在,我們將了解如何從這些低品質影像中移除標題以顯示圖片。 在以後的教學課程中,我們將建立一個用於更新類別 Picture 列的介面,並將這些使用 OLE 標題的點陣圖影像替換為等效的 JPG 影像,而無需不必要的 OLE 標題。

在前面的教學課程中,我們了解如何使用 FileUpload 控制項。 因此,您可以繼續將手冊檔案新增至 Web 伺服器的檔案系統。 但是,這樣做不會更新 Categories 表中的 BrochurePath 列。 在下一個教學課程中,我們將了解如何完成此操作,但現在我們需要手動為此列提供值。

在本教學課程的下載中,您將在 ~/Brochures 資料夾中找到七個 PDF 手冊文件,每個類別對應除海鮮之外的每個類別。 我故意省略了新增海鮮手冊來說明如何處理並非所有記錄都具有關聯的二進位資料的情況。 若要使用這些值更新 Categories 表,請在伺服器總管中以滑鼠以滑鼠右鍵按一下 Categories 節點,然後選擇「顯示資料表資料」。 然後,輸入具有手冊的每個類別的手冊文件的虛擬路徑,如圖 1 所示。 由於沒有海鮮類別的手冊,因此將其 BrochurePath 列的值保留為 NULL

手動輸入 Categories 表的 BrochurePath 列的值

圖 1:手動輸入 Categories 表的 BrochurePath 列的值 (點擊查看完整圖片)

使用為 Categories 表提供的 BrochurePath 值,我們準備建立一個 GridView,其中列出每個類別以及用於下載類別手冊的連結。 在步驟 4 中,我們將擴展此 GridView 以顯示類別的影像。

首先將 GridView 從工具箱拖曳到 BinaryData 資料夾中 DisplayOrDownloadData.aspx 頁面的設計器上。 將 GridView 的 ID 設定為 Categories,並透過 GridView 智慧標籤,選擇將其繫結到新的資料來源。 具體來說,將其繫結到一個名為 CategoriesDataSource 的 ObjectDataSource,該資料來源使用 CategoriesBLL 物件的 GetCategories() 方法來檢索資料。

建立一個名為 CategoryDataSource 的新 ObjectDataSource

圖 2:建立一個名為 CategoriesDataSource 的新 ObjectDataSource (點擊查看完整圖片)

設定 ObjectDataSource 以使用 CategoryBLL 類別

圖 3:設定 ObjectDataSource 以使用 CategoriesBLL 類別 (點擊查看完整圖片)

使用 GetCategories() 方法檢索類別清單

圖4:使用 GetCategories() 方法檢索類別清單 (點擊查看完整圖片)

完成「設定資料來源」精靈後,Visual Studio 會自動為 CategoryIDCategoryNameDescriptionNumberOfProductsBrochurePath DataColumn 新增 BoundField 到 Categories GridView。 繼續並移除 NumberOfProducts BoundField,因為 GetCategories() 方法的查詢不會檢索此資訊。 同時移除 CategoryID BoundField,並將 CategoryNameBrochurePath BoundFields HeaderText 屬性分別重新命名為 Category 和 Brochure。 進行這些變更後,您的 GridView 和 ObjectDataSource 宣告性標記應如下所示:

<asp:GridView ID="Categories" runat="server" 
    AutoGenerateColumns="False" DataKeyNames="CategoryID"
    DataSourceID="CategoriesDataSource" EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" />
        <asp:BoundField DataField="Description" HeaderText="Description" 
            SortExpression="Description" />
        <asp:BoundField DataField="BrochurePath" HeaderText="Brochure" 
            SortExpression="BrochurePath" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>

透過瀏覽器查看此頁面 (見圖 5)。 列出了八個類別中的每一個類別。 具有 BrochurePath 值的七個類別的 BrochurePath 值會顯示在各自的 BoundField 中。 Seafood 的 BrochurePath 值為 NULL,會顯示一個空白儲存格。

列出了每個類別的名稱、描述和 BrochurePath 值

圖 5:列出了每個類別的名稱、描述和 BrochurePath 值 (點擊查看完整圖片)

我們不想顯示 BrochurePath 列的文字,而是建立指向手冊的連結。 若要實現此目的,請移除 BrochurePath BoundField,並將其替換為 HyperLinkField。 將新 HyperLinkField 的 HeaderText 屬性設為 Brochure,將其 Text 屬性設為 View Brochure,並將其 DataNavigateUrlFields 屬性設為 BrochurePath

為 BrochurePath 新增 HyperLinkField

圖 6:為 BrochurePath 新增 HyperLinkField

這將向 GridView 新增一列連結,如圖 7 所示。 點擊查看手冊連結將直接在瀏覽器中顯示 PDF 或提示使用者下載文件,具體取決於是否安裝了 PDF 閱讀器以及瀏覽器的設定。

點擊查看手冊連結,以查看類別的手冊

圖 7:點擊查看手冊連結,以查看類別的手冊 (點擊查看完整圖片)

隨即會顯示類別的手冊 PDF

圖 8:隨即會顯示類別的手冊 PDF (點擊查看完整圖片)

隱藏沒有手冊的類別的檢視手冊文字

如圖 7 所示,BrochurePath HyperLinkField 會顯示所有記錄的 Text 屬性值 (View Brochure),無論 BrochurePath 是否有非 NULL 的值。 當然,如果 BrochurePathNULL,則連結僅顯示為文字,就像海鮮類別的情況一樣 (參見圖 7)。 與顯示文字「檢視手冊」相比,讓那些沒有 BrochurePath 值的類別顯示一些替代文字 (例如「無可用手冊」) 可能會更好。

為了提供此行為,我們需要使用 TemplateField,其內容是透過呼叫頁面方法產生的,該方法根據 BrochurePath 值發出適當的輸出。 我們最早在「在 GridView 控制項中使用 TemplateFields」教學課程中探索過這種格式化技術。

透過選擇 HyperLinkField,然後按一下「編輯列」對話方塊中的「將此欄位轉換為 TemplateField」連結,將 BrochurePath HyperLinkField 轉換為 TemplateField。

將 HyperLinkField 轉換為 TemplateField

圖 9:將 HyperLinkField 轉換為 TemplateField

這將建立一個帶有 ItemTemplate 的 TemplateField,其中包含一個 HyperLink Web 控制項,其 NavigateUrl 屬性繫結到 BrochurePath 值。 將此標記替換為對 GenerateBrochureLink 方法的呼叫,並傳入 BrochurePath 的值:

<asp:TemplateField HeaderText="Brochure">
    <ItemTemplate>
        <%# GenerateBrochureLink(Eval("BrochurePath")) %>
    </ItemTemplate>
</asp:TemplateField>

接下來,在名為 GenerateBrochureLink 的 ASP.NET 頁面程式程式碼後置類別中建立 protected 方法,該方法會傳回 string 並接受 object 作為輸入參數。

protected string GenerateBrochureLink(object BrochurePath)
{
    if (Convert.IsDBNull(BrochurePath))
        return "No Brochure Available";
    else
        return string.Format(@"<a href="{0}">View Brochure</a>", 
            ResolveUrl(BrochurePath.ToString()));
}

此方法決定傳入的 object 值是否為資料庫 NULL,如果是,則傳回一則訊息,指示該類別缺少手冊。 否則,如果有 BrochurePath 值,則會顯示在超連結中。 請注意,如果 BrochurePath 值存在,則會將其傳遞到 ResolveUrl(url) 方法中。 此方法解析傳入的 url,將 ~ 字元替換為適當的虛擬路徑。 例如,如果應用程式的根目錄為 /Tutorial55,則 ResolveUrl("~/Brochures/Meats.pdf") 將傳回 /Tutorial55/Brochures/Meat.pdf

圖 10 顯示了套用這些變更後的頁面。 請注意,「海鮮」類別的 BrochurePath 欄位現在顯示文字「無可用手冊」。

對於沒有手冊的類別,會顯示「無可用手冊」文字

圖 10:對於沒有手冊的類別,會顯示「無可用手冊」文字 (點擊查看完整圖片)

步驟 3:新增網頁,以顯示類別圖片

當使用者造訪 ASP.NET 頁面時,他們會收到 ASP.NET 頁面的 HTML。 接收到的 HTML 只是文字,不包含任何二進位資料。 任何其他二進位資料 (例如映像、聲音檔案、Macromedia Flash 應用程式、嵌入式 Windows Media Player 視訊等) 都作為單獨的資源存在於 Web 伺服器上。 HTML 包含這些文件的參考,但不包括文件的實際內容。

例如,在 HTML 中,<img> 元素用於參考圖片,src 屬性指向影像檔案,如下所示:

<img src="MyPicture.jpg" ... />

當瀏覽器收到此 HTML 時,它會向 Web 伺服器發出另一個請求以檢索影像檔案的二進位內容,然後將其顯示在瀏覽器中。 相同的概念適用於任何二進位資料。 在步驟 2 中,手冊沒有作為頁面 HTML 標籤的一部分傳送到瀏覽器。 相反,呈現的 HTML 提供了超連結,按一下該超連結會使瀏覽器直接請求 PDF 文件。

為了顯示或允許使用者下載駐留在資料庫中的二進位資料,我們需要建立一個單獨的網頁來傳回資料。 對於我們的應用程式,只有一個二進位資料欄位直接儲存在資料庫中,即類別圖片。 因此,我們需要一個頁面,在呼叫時傳回特定類別的影像資料。

將新的 ASP.NET 頁新增到名為 DisplayCategoryPicture.aspxBinaryData 資料夾中。 執行此操作時,請不要勾選「選擇主版頁」核取方塊。 此頁面需要查詢字串中的 CategoryID 值並傳回該類別 Picture 列的二進位資料。 由於此頁面僅傳回二進位資料,因此不需要在 HTML 部分中進行任何標記。 因此,點擊左下角的「來源」標籤,並移除 <%@ Page %> 指示詞之外的所有頁面標記。 也就是說,DisplayCategoryPicture.aspx 的宣告式標記應包含一行:

<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="DisplayCategoryPicture.aspx.cs" 
    Inherits="BinaryData_DisplayCategoryPicture" %>

如果您在 <%@ Page %> 指示詞中看到 MasterPageFile 屬性,請將其刪除。

在頁面的程式程式碼後置類別中,將以下程式碼新增至 Page_Load 事件處理常式:

protected void Page_Load(object sender, EventArgs e)
{
    int categoryID = Convert.ToInt32(Request.QueryString["CategoryID"]);
    // Get information about the specified category
    CategoriesBLL categoryAPI = new CategoriesBLL();
    Northwind.CategoriesDataTable categories = 
        categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID);
    Northwind.CategoriesRow category = categories[0];
    // Output HTTP headers providing information about the binary data
    Response.ContentType = "image/bmp";
    // Output the binary data
    // But first we need to strip out the OLE header
    const int OleHeaderLength = 78;
    int strippedImageLength = category.Picture.Length - OleHeaderLength;
    byte[] strippedImageData = new byte[strippedImageLength];
    Array.Copy(category.Picture, OleHeaderLength, 
        strippedImageData, 0, strippedImageLength);
    
    Response.BinaryWrite(strippedImageData);
}

此程式碼會先將 CategoryID 查詢字串值讀入名為 categoryID 的變數中。 接下來,透過呼叫 CategoriesBLL 類別的 GetCategoryWithBinaryDataByCategoryID(categoryID) 方法來檢索圖片資料。 該資料透過使用 Response.BinaryWrite(data) 方法傳回給用戶端,但在呼叫方法之前,必須移除 Picture 列值的 OLE 標頭。 這是透過建立一個名為 strippedImageDatabyte 陣列來實現的,該陣列將比 Picture 列中的字元少 78 個字元。 Array.Copy 方法用於將資料從 category.Picture 的位置 78 開始複製到 strippedImageData

Response.ContentType 屬性指定傳回內容的 MIME 類型,以便瀏覽器知道如何呈現它。 由於 Categories 表的 Picture 列是點陣圖影像,因此這裡使用點陣圖 MIME 類型 (image/bmp)。 如果省略 MIME 類型,大多數瀏覽器仍會正確顯示影像,因為它們可以根據影像檔案的二進位資料的內容推斷類型。 但是,明智的做法是盡可能包含 MIME 類型。 有關 MIME 媒體類型的完整清單,請參閱「互聯網號碼分配機構的網站」。

建立此頁面後,可以透過造 DisplayCategoryPicture.aspx?CategoryID=categoryID 訪查看特定類別的圖片。 圖 11 顯示了 Beverages 類別的圖片,可以從 DisplayCategoryPicture.aspx?CategoryID=1 中查看。

顯示飲料類別的圖片

圖 11:顯示飲料類別的圖片 (點擊查看完整圖片)

如果在造訪 DisplayCategoryPicture.aspx?CategoryID=categoryID 時出現例外狀況,顯示 Unable tocast object of type 'System.DBNull' to type 'System.Byte[]',則可能有兩件事導致此情況。 首先,Categories 表的 Picture 列確實允許 NULL 值。 然而,DisplayCategoryPicture.aspx 頁面假定存在非 NULL 的值。 如果 CategoriesDataTablePicture 屬性有 NULL 值,則無法直接存取該屬性。 如果您想讓 Picture 列有 NULL 值,則需要包含以下條件:

if (category.IsPictureNull())
{
    // Display some "No Image Available" picture
    Response.Redirect("~/Images/NoPictureAvailable.gif");
}
else
{
    // Send back the binary contents of the Picture column
    // ... Set ContentType property and write out ...
    // ... data via Response.BinaryWrite ...
}

上面的程式碼假設 Images 資料夾中有一個名為 NoPictureAvailable.gif 的影像檔案,您要為那些沒有圖片的類別顯示這些影像檔案。

如果 CategoriesTableAdapterGetCategoryWithBinaryDataByCategoryID 方法的 SELECT 陳述式已恢復回主要查詢的列清單,也可能會導致此例外狀況,如果您使用臨時 SQL 陳述式並且重新執行 TableAdapter 的精靈,則可能會發生這種情況。 檢查以確保 GetCategoryWithBinaryDataByCategoryID 方法的 SELECT 陳述式仍然包含 Picture 列。

注意

每次造訪 DisplayCategoryPicture.aspx 時,都會存取資料庫並傳回指定類別的圖片資料。 但是,如果自使用者上次查看以來該類別的圖片沒有發生變化,那麼這就是浪費精力。 幸運的是,HTTP 允許條件式 GET。 透過條件式 GET,發出 HTTP 請求的用戶端會傳送 If-Modified-SinceHTTP 標頭,該標頭提供用戶端上次從 Web 伺服器檢索此資源的日期和時間。 如果自指定日期以來內容未發生更改,則 Web 伺服器可能會以「未修改狀態代碼 (304)」進行回應,並放棄發回所請求資源的內容。 簡而言之,如果自用戶端上次存取該資源以來該資源尚未被修改,則該技術使 Web 伺服器不必發回該資源的內容。

但是,要實現此行為,需要您在 Categories 表中新增 PictureLastModified 列,以擷取 Picture 列上次更新的時間以及檢查 If-Modified-Since 標題的程式碼。 有關 If-Modified-Since 標頭和條件式 GET 工作流程的詳細資訊,請參閱「RSS 駭客的 HTTP 條件式 GET」和「深入了解在 ASP.NET 頁面中執行 HTTP 請求」。

步驟 4:在 GridView 中顯示類別圖片

現在我們有一個網頁來顯示特定類別的圖片,我們可以使用 Image Web 控制項或指向 DisplayCategoryPicture.aspx?CategoryID=categoryID 的 HTML <img> 元素來顯示它。 URL 由資料庫資料決定的影像可以使用 ImageField 顯示在 GridView 或 DetailsView 中。 ImageField 所包含的 DataImageUrlFieldDataImageUrlFormatString 屬性的工作方式類似於 HyperLinkField 的 DataNavigateUrlFieldsDataNavigateUrlFormatString 屬性。

讓我們透過新增 ImageField 來顯示每個類別的圖片來增強 DisplayOrDownloadData.aspx 中的 Categories GridView。 只需新增 ImageField 並將其 DataImageUrlFieldDataImageUrlFormatString 屬性分別設為 CategoryIDDisplayCategoryPicture.aspx?CategoryID={0}。 這將建立一個 GridView 列,該列呈現 <img> 元素,該元素的 src 屬性參考 DisplayCategoryPicture.aspx?CategoryID={0},其中 {0} 替換為 GridView 行的 CategoryID 值。

將 ImageField 新增到 GridView

圖 12:將 ImageField 新增到 GridView

新增 ImageField 後,您的 GridView 宣告式語法應如下所示:

<asp:GridView ID="Categories" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource" 
    EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            SortExpression="CategoryName" />
        <asp:BoundField DataField="Description" HeaderText="Description" 
            SortExpression="Description" />
        <asp:TemplateField HeaderText="Brochure">
            <ItemTemplate>
                <%# GenerateBrochureLink(Eval("BrochurePath")) %>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:ImageField DataImageUrlField="CategoryID" 
            DataImageUrlFormatString="DisplayCategoryPicture.aspx?CategoryID={0}">
        </asp:ImageField>
    </Columns>
</asp:GridView>

請花點時間透過瀏覽器查看此頁面。 請注意,現在每個記錄都包含該類別的圖片。

每行顯示類別圖片

圖 13:每行顯示類別圖片 (點擊查看完整圖片)

摘要

在本教學課程中,我們研究如何呈現二進位資料。 資料的呈現方式取決於資料的類型。 對於 PDF 手冊文件,我們為使用者提供了一個「查看手冊」連結,按一下該連結後,使用者將直接移至 PDF 文件。 對於類別的圖片,我們先建立一個頁面來從資料庫中檢索並傳回二進位資料,然後使用該頁面在 GridView 中顯示每個類別的圖片。

現在我們已經了解如何顯示二進位資料,接下來我們準備好研究如何使用二進位資料對資料庫執行插入、更新和刪除。 在下一個教學課程中,我們將了解如何將上傳的檔案與其對應的資料庫記錄相關聯。 在後面的教學課程中,我們將了解如何更新現有的二進位資料以及如何在刪除關聯記錄時刪除二進位資料。

祝您程式設計愉快!

關於作者

Scott Mitchell,七本 ASP/ASP.NET 書籍的作者和 4GuysFromRolla.com 創始人,自 1998 年以來便開始使用 Microsoft Web 技術。 Scott 擔任獨立顧問、講師和作家。 他的新書是 Sams Teach Yourself ASP.NET 2.0 in 24 Hours。 您可以透過 mitchell@4GuysFromRolla.com 或他的部落格 (可以在 http://ScottOnWriting.NET 找到) 與他聯繫。

特別感謝

本教學課程系列已經過許多熱心的檢閱者檢閱。 本教學課程的主要審閱者是 Teresa Murphy 和 Dave Gardner。 有興趣檢閱我即將推出的 MSDN 文章嗎? 如果有,請發信到 mitchell@4GuysFromRolla.com 。