共用方式為


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

斯科特·米切爾

下載 PDF

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

簡介

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

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

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

步驟 1:提供BrochurePath

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

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

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

手動輸入類別表的 BrochurePath 欄的值

圖 1:手動輸入Categories表格BrochurePath欄的值(點擊以檢視完整大小的圖片

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

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

建立 ObjectDataSource 新物件,並將其命名為 CategoriesDataSource

圖 2:建立名為 CategoriesDataSource 的新 ObjectDataSource (按兩下以檢視完整大小的影像

將 ObjectDataSource 設定為使用 CategoriesBLL 類別

圖 3:將 ObjectDataSource 設定為使用 CategoriesBLL 類別 (按一下即可檢視完整大小的影像

使用 GetCategories() 方法擷取類別清單

圖 4:使用 GetCategories() 方法擷取類別清單 (按兩下以檢視完整大小的影像

完成「設定資料來源」精靈後,Visual Studio 會自動為 CategoriesCategoryIDCategoryNameDescriptionNumberOfProductsBrochurePath 新增 BoundField 到 DataColumn 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 的 NULL 值為 BrochurePath,會顯示一個空白儲存格。

列出每個類別的名稱、描述和 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),無論 NULL 是否有非 BrochurePath 的值。 當然,如果 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>

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

Protected Function GenerateBrochureLink(BrochurePath As Object) As String
    If Convert.IsDBNull(BrochurePath) Then
        Return "No Brochure Available"
    Else
        Return String.Format("<a href="{0}">View Brochure</a>", _
            ResolveUrl(BrochurePath.ToString()))
    End If
End Function

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

<%@ Page Language="VB" AutoEventWireup="true" 
    CodeFile="DisplayCategoryPicture.aspx.vb" 
    Inherits="BinaryData_DisplayCategoryPicture" %>

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

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

Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
    Dim categoryID As Integer = _
        Convert.ToInt32(Request.QueryString("CategoryID"))
    ' Get information about the specified category
    Dim categoryAPI As New CategoriesBLL()
    Dim categories As Northwind.CategoriesDataTable = _
        categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID)
    Dim category As Northwind.CategoriesRow = 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 OleHeaderLength As Integer = 78
    Dim strippedImageLength As Integer = _
        category.Picture.Length - OleHeaderLength
    Dim strippedImageData(strippedImageLength) As Byte
    Array.Copy(category.Picture, OleHeaderLength, _
        strippedImageData, 0, strippedImageLength)
    Response.BinaryWrite(strippedImageData)
End Sub

此程式碼會先將 CategoryID 查詢字串值讀入名為 categoryID 的變數中。 接下來,透過呼叫 CategoriesBLL 類別的 GetCategoryWithBinaryDataByCategoryID(categoryID) 方法來檢索圖片資料。 該資料透過使用 Response.BinaryWrite(data) 方法傳回給用戶端,但在呼叫方法之前,必須移除 Picture 列值的 OLE 標頭。 這是透過建立一個名為 BytestrippedImageData 陣列來實現的,該陣列將比 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 的值。 如果 PictureCategoriesDataTable 屬性有 NULL 值,則無法直接存取該屬性。 如果您想讓 NULL 列有 Picture 值,則需要包含以下條件:

If category.IsPictureNull() Then
    ' 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 ...
End If

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

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

注意

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

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

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

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

讓我們透過新增 ImageField 來顯示每個類別的圖片來增強 Categories 中的 DisplayOrDownloadData.aspx 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 中顯示每個類別的圖片。

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

祝您程式設計愉快!

關於作者

斯科特·米切爾,七本 ASP/ASP.NET 書籍和 4GuysFromRolla.com 創始人的作者,自1998年以來一直與Microsoft Web 技術合作。 Scott 擔任獨立顧問、講師和作家。 他的最新著作是 《Sams自學ASP.NET 2.0,24小時搞定》。 可以透過 mitchell@4GuysFromRolla.com 聯絡他。

特別感謝

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