显示数据 Web 控件中的二进制数据 (VB)
作者 :斯科特·米切尔
在本教程中,我们将查看在网页上显示二进制数据的选项,包括图像文件的显示以及为 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
。
图 1:手动输入表BrochurePath
列的值Categories
(单击以查看全尺寸图像)
步骤 2:为 GridView 中的小册子提供下载链接
BrochurePath
通过为Categories
表提供的值,我们准备创建一个 GridView,其中列出了每个类别以及下载类别小册子的链接。 在步骤 4 中,我们将扩展此 GridView 以显示类别图像。
首先,将 GridView 从工具箱拖到文件夹中页面BinaryData
的DisplayOrDownloadData.aspx
设计器上。 将 GridView 设置为 ID
Categories
GridView 智能标记以及通过 GridView 智能标记,选择将其绑定到新的数据源。 具体而言,请将其绑定到一个名为CategoriesDataSource
使用对象方法GetCategories()
检索数据的 CategoriesBLL
ObjectDataSource。
图 2:创建名为 CategoriesDataSource
的新 ObjectDataSource (单击可查看全尺寸图像)
图 3:将 ObjectDataSource 配置为使用 CategoriesBLL
类(单击以查看全尺寸图像)
图 4:使用 GetCategories()
方法检索类别列表(单击以查看全尺寸图像)
完成“配置数据源”向导后,Visual Studio 会自动将 BoundField 添加到 Categories
GridView for the CategoryID
, , CategoryName
NumberOfProducts
Description
和 BrochurePath
DataColumn
s。 继续操作并删除 NumberOfProducts
BoundField, GetCategories()
因为方法的查询不会检索此信息。 此外, CategoryID
请删除 BoundField 并将 CategoryName
BrochurePath
and 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
。
图 6:为 BrochurePath
这会将一列链接添加到 GridView,如图 7 所示。 单击“查看手册”链接将直接在浏览器中显示 PDF,或提示用户下载文件,具体取决于是否安装了 PDF 阅读器和浏览器设置。
图 7:可以通过单击“查看小册子”链接来查看类别的小册子(单击以查看全尺寸图像)
图 8:显示类别的小册子 PDF(单击以查看全尺寸图像)
隐藏没有小册子的类别的视图小册子文本
如图 7 所示, BrochurePath
HyperLinkField 为所有记录显示其 Text
属性值(查看小册子),而不考虑是否有非NULL
值 BrochurePath
。 当然,如果是BrochurePath
NULL
,则链接仅显示为文本,与“海鲜”类别的情况一样(请参阅图 7)。 与其显示文本“查看小册子”,最好让这些类别没有 BrochurePath
值显示一些备用文本,例如“无小册子可用”。
为了提供此行为,我们需要使用 TemplateField,该模板字段的内容是通过调用页面方法生成的,该方法会根据 BrochurePath
值发出相应的输出。 我们首先在 GridView 控件教程的 Using TemplateFields 中回顾了此格式设置技术。
通过选择 BrochurePath
HyperLinkField,然后单击“编辑列”对话框中的“将此字段转换为 TemplateField”链接,将 HyperLinkField 转换为 TemplateField。
图 9:将 HyperLinkField 转换为 TemplateField
这将创建一个 TemplateField,其中包含 ItemTemplate
一个 HyperLink Web 控件,该控件的属性 NavigateUrl
绑定到该值 BrochurePath
。 将此标记替换为对方法 GenerateBrochureLink
的调用,并传入以下 BrochurePath
值:
<asp:TemplateField HeaderText="Brochure">
<ItemTemplate>
<%# GenerateBrochureLink(Eval("BrochurePath")) %>
</ItemTemplate>
</asp:TemplateField>
接下来,在名为返回并接受Object
输入参数的 ASP.NET 页代码隐藏类String
GenerateBrochureLink
中创建Protected
方法。
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 只是文本,不包含任何二进制数据。 任何其他二进制数据(如图像、声音文件、宏媒体 Flash 应用程序、嵌入式Windows 媒体播放器视频等)都作为 Web 服务器上的单独资源存在。 HTML 包含对这些文件的引用,但不包括文件的实际内容。
例如,在 HTML <img>
中,元素用于引用图片,该 src
属性指向图像文件,如下所示:
<img src="MyPicture.jpg" ... />
当浏览器收到此 HTML 时,它会向 Web 服务器发出另一个请求,以检索图像文件的二进制内容,然后在浏览器中显示该文件。 相同的概念适用于任何二进制数据。 在步骤 2 中,该小册子未作为页面 HTML 标记的一部分发送到浏览器。 相反,呈现的 HTML 提供了超链接,单击时,浏览器直接请求 PDF 文档。
若要显示或允许用户下载驻留在数据库中的二进制数据,我们需要创建返回数据的单独网页。 对于应用程序,只有一个二进制数据字段直接存储在类别图片的数据库中。 因此,我们需要一个页面,在调用时返回特定类别的图像数据。
向名为 DisplayCategoryPicture.aspx
的文件夹添加新的 ASP.NET 页BinaryData
。 执行此操作时,请取消选中“选择母版页”复选框。 此页面需要 CategoryID
查询字符串中的值,并返回该类别的 Picture
列的二进制数据。 由于此页面返回二进制数据,而没有任何其他数据,因此它不需要 HTML 节中的任何标记。 因此,单击左下角的“源”选项卡,删除除指令以外的 <%@ Page %>
所有页面标记。 也就是说, DisplayCategoryPicture.aspx
声明性标记应包含一行:
<%@ Page Language="VB" AutoEventWireup="true"
CodeFile="DisplayCategoryPicture.aspx.vb"
Inherits="BinaryData_DisplayCategoryPicture" %>
如果在指令中看到<%@ Page %>
该MasterPageFile
属性,请将其删除。
在页面代码隐藏类中,将以下代码添加到 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 标头。 这是通过创建一个名为Byte
strippedImageData
数组的数组来实现的,该数组的字符数与列中的字符数精确相差 Picture
78 个字符。 该方法Array.Copy
用于将数据从category.Picture
位置 78 开始strippedImageData
复制到 。
该 Response.ContentType
属性指定 返回的内容的 MIME 类型 ,以便浏览器知道如何呈现它。 Categories
由于表的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.gif
Images
文件,你希望在没有图片的情况下显示这些类别。
如果 CategoriesTableAdapter
s GetCategoryWithBinaryDataByCategoryID
方法的 SELECT
语句已还原回主查询的列列表,则也可能引发此异常,如果使用的是即席 SQL 语句,并且重新运行 TableAdapter 主查询的向导,则可能会发生这种情况。 检查以确保 GetCategoryWithBinaryDataByCategoryID
方法的 SELECT
语句仍然包含该 Picture
列。
注意
每次访问数据库时 DisplayCategoryPicture.aspx
,都会访问数据库,并返回指定的类别图片数据。 但是,如果用户上次查看该类别后图片没有更改,则这浪费了工作量。 幸运的是,HTTP 允许 条件 GET。 使用条件 GET 时,发出 HTTP 请求的客户端会随 If-Modified-Since
HTTP 标头发送,该标头 提供客户端上次从 Web 服务器检索到此资源的日期和时间。 如果内容自此指定日期以来未更改,Web 服务器可能会响应 “未修改”状态代码(304), 并放弃发送回请求的资源内容。 简言之,如果自客户端上次访问资源以来尚未修改该资源的内容,则此方法可缓解 Web 服务器不得不发送回内容。
但是,若要实现此行为,需要将列添加到PictureLastModified
Categories
表中以捕获上次更新列的时间Picture
,以及检查If-Modified-Since
标头的代码。 有关标头和条件 GET 工作流的详细信息 If-Modified-Since
,请参阅 HTTP 条件 GET for RSS 黑客 ,以及 更深入地了解在 ASP.NET 页面中执行 HTTP 请求。
步骤 4:在 GridView 中显示类别图片
现在,我们有一个网页来显示特定类别的图片,可以使用图像 Web 控件或指向DisplayCategoryPicture.aspx?CategoryID=categoryID
的 HTML <img>
元素来显示它。 其 URL 由数据库数据确定的图像可以使用 ImageField 显示在 GridView 或 DetailsView 中。 ImageField 包含DataImageUrlField
和DataImageUrlFormatString
属性,其工作类似于 HyperLinkField 和DataNavigateUrlFields
DataNavigateUrlFormatString
属性。
让我们通过添加 ImageField 来增强 Categories
GridView DisplayOrDownloadData.aspx
,以显示每个类别的图片。 只需添加 ImageField 并将其及其属性分别设置为DataImageUrlField
CategoryID
和设置DisplayCategoryPicture.aspx?CategoryID={0}
。DataImageUrlFormatString
这将创建一个 GridView 列,该列呈现<img>
其属性引用DisplayCategoryPicture.aspx?CategoryID={0}
的元素,其中{0}替换为 GridView 行的值CategoryID
src
。
图 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 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0。 他可以通过他的博客联系到mitchell@4GuysFromRolla.com他,可以在该博客中找到http://ScottOnWriting.NET。
特别感谢
本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是 Teresa Murphy 和 Dave Gardner。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请把我扔一条线。mitchell@4GuysFromRolla.com