显示数据 Web 控件中的二进制数据 (VB)

作者 :Scott Mitchell

下载 PDF

在本教程中,我们将介绍在网页上显示二进制数据的选项,包括图像文件的显示和为 PDF 文件提供“下载”链接。

简介

在前面的教程中,我们探讨了将二进制数据与应用程序基础数据模型关联的两种技术,并使用 FileUpload 控件将文件从浏览器上传到 Web 服务器的文件系统。 我们尚未了解如何将上传的二进制数据与数据模型相关联。 也就是说,上传文件并将其保存到文件系统后,文件的路径必须存储在相应的数据库记录中。 如果数据直接存储在数据库中,则上传的二进制数据无需保存到文件系统,但必须注入数据库。

不过,在了解如何将数据与数据模型关联之前,让我们先看看如何向最终用户提供二进制数据。 呈现文本数据非常简单,但应如何呈现二进制数据? 当然,这取决于二进制数据的类型。 对于图像,我们可能需要显示图像;对于 PDF、Microsoft Word文档、ZIP 文件和其他类型的二进制数据,提供下载链接可能更合适。

在本教程中,我们将了解如何使用数据 Web 控件(如 GridView 和 DetailsView)显示二进制数据及其关联的文本数据。 在下一教程中,我们将把注意力转向将上传的文件与数据库相关联。

步骤 1:提供BrochurePath

Picture表中的Categories列已包含各种类别图像的二进制数据。 具体而言,每条记录的 Picture 列包含粒度、低质量、16 色位图图像的二进制内容。 每个类别图像的宽度为 172 像素,高度为 120 像素,大约占用 11 KB。 此外,列中的二进制内容 Picture 包括一个 78 字节的 OLE 标头,必须在显示图像之前去除该标头。 之所以存在此标头信息,是因为 Northwind 数据库在 Microsoft Access 中具有其根。 在 Access 中,二进制数据使用 OLE 对象数据类型进行存储,该数据类型在此标头上进行标记。 现在,我们将了解如何从这些低质量图像中去除页眉,以便显示图片。 在将来的教程中,我们将生成一个用于更新类别列的 Picture 接口,并将这些使用 OLE 标头的位图图像替换为等效的 JPG 图像,而无需不必要的 OLE 标头。

在前面的教程中,我们了解了如何使用 FileUpload 控件。 因此,可以继续将宣传册文件添加到 Web 服务器的文件系统。 但是,这样做不会更新表中的BrochurePathCategories列。 在下一教程中,我们将了解如何实现此目的,但目前需要手动提供此列的值。

在本教程下载中,你将在 ~/Brochures 文件夹中找到七个 PDF 宣传册文件,其中一个文件适用于除海鲜以外的每个类别。 为了说明如何处理并非所有记录都具有关联的二进制数据的情况,我特意省略了添加海鲜宣传册。 若要使用这些值更新 Categories 表,请在服务器资源管理器中 Categories 右键单击节点,然后选择“显示表数据”。 然后,输入包含宣传册的每个类别的宣传册文件的虚拟路径,如图 1 所示。 由于没有“海鲜”类别的小册子,因此将其 BrochurePath 列值保留为 NULL

手动输入类别表宣传册路径列的值

图 1:手动输入表列BrochurePath的值 Categories (单击以查看全尺寸图像)

BrochurePath使用为Categories表提供的值,我们准备创建一个 GridView,其中列出了每个类别以及用于下载类别手册的链接。 在步骤 4 中,我们将扩展此 GridView,以显示类别的图像。

首先,将“工具箱”中的 GridView 拖到文件夹中页面BinaryDataDisplayOrDownloadData.aspxDesigner。 将 GridView ID 设置为 Categories ,并通过 GridView 智能标记选择将其绑定到新的数据源。 具体而言,请将其绑定到名为 CategoriesDataSource 的 ObjectDataSource,该对象使用 CategoriesBLL 对象 s GetCategories() 方法检索数据。

创建新的对象DataSource 命名类别DataSource

图 2:新建名为 CategoriesDataSource 的对象数据源 (单击以查看全尺寸图像)

将 ObjectDataSource 配置为使用 CategoriesBLL 类

图 3:将 ObjectDataSource 配置为使用 CategoriesBLL 类 (单击以查看全尺寸图像)

使用 GetCategories () 方法检索类别列表

图 4:使用 GetCategories() 方法检索类别列表 (单击以查看全尺寸图像)

完成“配置数据源”向导后,Visual Studio 会自动将 BoundField 添加到 Categories GridView CategoryID中的 、 CategoryNameDescriptionNumberOfProductsBrochurePathDataColumn 。 继续删除 BoundField,NumberOfProductsGetCategories()因为方法查询不会检索此信息。 另请删除 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 中。 海鲜,其 NULL 值为 BrochurePath,显示一个空单元格。

列出每个类别的名称、说明和宣传册路径值

图 5:列出每个类别的名称、说明和 BrochurePath 值 (单击以查看全尺寸图像)

我们希望创建一个指向宣传册的链接,而不是显示列的文本 BrochurePath 。 为此,请删除 BrochurePath BoundField 并将其替换为 HyperLinkField。 将新的 HyperLinkField 属性 HeaderText 设置为“宣传册”,将其 Text 属性设置为“查看宣传册”,将其 DataNavigateUrlFields 属性设置为 BrochurePath

为 BrochurePath 添加 HyperLinkField

图 6:为 添加 HyperLinkField BrochurePath

这将向 GridView 添加一列链接,如图 7 所示。 单击“查看手册”链接将直接在浏览器中显示 PDF,或提示用户下载文件,具体取决于是否安装了 PDF 阅读器以及浏览器的设置。

可以通过单击“查看宣传册”链接来查看类别的宣传册

图 7:可以通过单击“查看宣传册”链接 (单击查看全尺寸图像来查看 类别的宣传册)

显示类别手册 PDF

图 8:显示类别的小册子 PDF (单击以查看全尺寸图像)

隐藏无宣传册的类别的视图宣传册文本

如图 7 所示, BrochurePath HyperLinkField 为所有记录显示其 Text 属性值 ( 视图宣传册 ) ,而不考虑 是否有非NULLBrochurePath。 当然,如果 BrochurePathNULL,则链接仅显示为文本,如海鲜类别 (请参阅图 7) 。 最好让这些类别不 BrochurePath 带值显示一些备用文本,例如没有可用的宣传册,而不是显示文本“查看宣传册”。

为了提供此行为,我们需要使用 TemplateField,其内容是通过调用页面方法生成的,该方法根据 BrochurePath 值发出相应的输出。 我们首先在 GridView 控件中使用 TemplateFields 教程中 探索了这种格式设置技术。

通过选择 BrochurePath HyperLinkField,然后单击“编辑列”对话框中的“将此字段转换为 TemplateField”链接,将 HyperLinkField 转换为 TemplateField。

将 HyperLinkField 转换为 TemplateField

图 9:将 HyperLinkField 转换为 TemplateField

这将创建一个 TemplateField,其中包含ItemTemplate其属性绑定到 BrochurePath 值的 HyperLink Web 控件NavigateUrl。 将此标记替换为对 方法 GenerateBrochureLink的调用 ,并传入 的值 BrochurePath

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

接下来,在名为 GenerateBrochureLink ASP.NET 页的代码隐藏类中创建一个ProtectedString方法,该方法返回 并接受 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,并将 ~ 字符替换为相应的虚拟路径。 例如,如果应用程序的根位于 /Tutorial55ResolveUrl("~/Brochures/Meats.pdf") 将返回 /Tutorial55/Brochures/Meat.pdf

图 10 显示了应用这些更改后的页面。 请注意,“海鲜”类别的 BrochurePath 字段现在显示文本“无宣传册可用”。

为没有宣传册的类别显示文本“没有可用的宣传册”

图 10:为没有宣传册的类别显示文本没有可用的宣传册 (单击以查看全尺寸图像)

步骤 3:添加网页以显示类别图片

当用户访问 ASP.NET 页面时,他们会收到 ASP.NET 页 HTML。 收到的 HTML 只是文本,不包含任何二进制数据。 任何其他二进制数据(如图像、声音文件、Macromedia Flash应用程序、嵌入式Windows 媒体播放器视频等)都作为单独的资源存在于 Web 服务器上。 HTML 包含对这些文件的引用,但不包括文件的实际内容。

例如,在 HTML 中, <img> 元素用于引用图片,其 src 属性指向图像文件,如下所示:

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

当浏览器收到此 HTML 时,它会向 Web 服务器发出另一个请求,以检索图像文件的二进制内容,然后它将显示在浏览器中。 相同的概念适用于任何二进制数据。 在步骤 2 中,没有将宣传册作为页面 HTML 标记的一部分发送到浏览器。 相反,呈现的 HTML 提供的超链接在单击时会导致浏览器直接请求 PDF 文档。

若要显示或允许用户下载驻留在数据库中的二进制数据,我们需要创建返回数据的单独网页。 对于我们的应用程序,只有一个直接存储在数据库中的二进制数据字段类别图片。 因此,我们需要一个页面,在调用时返回特定类别的图像数据。

将新的 ASP.NET 页添加到 BinaryData 名为 DisplayCategoryPicture.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 类 s GetCategoryWithBinaryDataByCategoryID(categoryID) 方法检索图片数据。 此数据通过使用 Response.BinaryWrite(data) 方法返回到客户端,但在调用此数据之前, Picture 必须删除列值 OLE 标头。 这是通过创建一个名为 BytestrippedImageData 数组来实现的,该数组包含的字符数恰好比 Picture 列中的字符少 78 个字符。 方法Array.Copy用于将数据从category.Picture位置 78 strippedImageData开始复制到 。

属性 Response.ContentType 指定要返回的内容的 MIME 类型 ,以便浏览器知道如何呈现它。 Categories由于表 s Picture 列是位图图像,因此此处使用位图 MIME 类型 (image/bmp) 。 如果省略 MIME 类型,大多数浏览器仍将正确显示图像,因为它们可以根据图像文件二进制数据的内容推断类型。 但是,如果可能,最好包括 MIME 类型。 有关 MIME 媒体类型的完整列表,请参阅 Internet 分配号码管理局的网站

创建此页面后,可以通过访问 DisplayCategoryPicture.aspx?CategoryID=categoryID来查看特定类别的图片。 图 11 显示了饮料类别的图片,可以从 查看 DisplayCategoryPicture.aspx?CategoryID=1

显示饮料类别的图片

图 11:饮料类别的图片显示 (单击以查看全尺寸图像)

如果在访问 DisplayCategoryPicture.aspx?CategoryID=categoryID时收到一个异常,该异常显示无法将类型为“System.DBNull”的对象强制转换为类型“System.Byte[]”,则有两个原因可能导致此问题。 首先, Categories 表的 列 Picture 允许 NULL 值。 但是,该 DisplayCategoryPicture.aspx 页假定存在非NULL 值。 Picture如果 具有 NULL 值,CategoriesDataTable则无法直接访问 的 属性。 如果确实希望允许 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.gifImages 的图像文件,要为没有图片的类别显示这些类别。

如果 CategoriesTableAdapter s GetCategoryWithBinaryDataByCategoryID 方法 s SELECT 语句已还原回main查询列列表,则也可能导致此异常。如果使用即席 SQL 语句,并且已为 TableAdapter main 查询重新运行向导,则可能发生此异常。 检查以确保 GetCategoryWithBinaryDataByCategoryID 方法的 SELECT 语句仍包含 列 Picture

注意

每次 DisplayCategoryPicture.aspx 访问 时,都会访问数据库,并返回指定类别的图片数据。 但是,如果类别的图片自用户上次查看后未更改,则这是浪费的精力。 幸运的是,HTTP 允许 条件 GET。 使用条件 GET 时,发出 HTTP 请求的客户端会沿着 If-Modified-Since HTTP 标头 发送,该标头提供客户端上次从 Web 服务器检索此资源的日期和时间。 如果内容自此指定日期以来未更改,Web 服务器可能会响应 “未修改”状态代码 (304) ,并放弃发送请求的资源内容。 简言之,如果自客户端上次访问某个资源以来未对其进行修改,则此方法使 Web 服务器不必为资源发送回内容。

但是,若要实现此行为,需要向表添加列PictureLastModifiedCategories以捕获列的上次更新时间Picture,以及要为If-Modified-Since标头检查的代码。 有关标头和条件 GET 工作流的详细信息 If-Modified-Since ,请参阅 RSS 黑客的 HTTP 条件 GET更深入地了解在 ASP.NET 页面中执行 HTTP 请求

步骤 4:在 GridView 中显示类别图片

现在,我们有了一个用于显示特定类别图片的网页,可以使用 图像 Web 控件 或指向 的 HTML <img> 元素来 DisplayCategoryPicture.aspx?CategoryID=categoryID显示它。 URL 由数据库数据确定的图像可以使用 ImageField 显示在 GridView 或 DetailsView 中。 ImageField 包含 DataImageUrlField 的 和 DataImageUrlFormatString 属性的工作方式类似于 HyperLinkField 和 DataNavigateUrlFieldsDataNavigateUrlFormatString 属性。

让我们通过添加 ImageField 来增加 Categories GridView DisplayOrDownloadData.aspx ,以显示每个类别的图片。 只需添加 ImageField 并将其 和 属性分别设置为 DataImageUrlFieldCategoryIDDisplayCategoryPicture.aspx?CategoryID={0}DataImageUrlFormatString 这将创建一个 GridView 列,该列呈现<img>其属性引用 DisplayCategoryPicture.aspx?CategoryID={0}的元素,其中 {0} 替换为 GridView 行的值CategoryIDsrc

将 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 担任独立顾问、培训师和作家。 他的最新一本书是 山姆斯在 24 小时内 ASP.NET 2.0。 可以在 上mitchell@4GuysFromRolla.com联系他,也可以通过他的博客(可在 中找到http://ScottOnWriting.NET)。

特别感谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是 Teresa Murphy 和 Dave Gardner。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处mitchell@4GuysFromRolla.com放置一行。