显示数据 Web 控件中的二进制数据 (C#)
在本教程中,我们将介绍在网页上显示二进制数据的选项,包括图像文件的显示和为 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 服务器的文件系统。 但是,这样做不会更新表中的BrochurePath
Categories
列。 在下一教程中,我们将了解如何实现此目的,但目前需要手动提供此列的值。
在本教程下载中,你将在 ~/Brochures
文件夹中找到七个 PDF 宣传册文件,其中一个文件适用于除海鲜以外的每个类别。 为了说明如何处理并非所有记录都具有关联的二进制数据的情况,我特意省略了添加海鲜宣传册。 若要使用这些值更新 Categories
表,请在服务器资源管理器中 Categories
右键单击节点,然后选择“显示表数据”。 然后,输入包含宣传册的每个类别的宣传册文件的虚拟路径,如图 1 所示。 由于没有“海鲜”类别的小册子,因此将其 BrochurePath
列值保留为 NULL
。
图 1:手动输入表列BrochurePath
的值 Categories
(单击以查看全尺寸图像)
步骤 2:为 GridView 中的宣传册提供下载链接
BrochurePath
使用为Categories
表提供的值,我们准备创建一个 GridView,其中列出了每个类别以及用于下载类别手册的链接。 在步骤 4 中,我们将扩展此 GridView,以显示类别的图像。
首先,将“工具箱”中的 GridView 拖到文件夹中页面BinaryData
的DisplayOrDownloadData.aspx
Designer。 将 GridView ID
设置为 Categories
,并通过 GridView 智能标记选择将其绑定到新的数据源。 具体而言,请将其绑定到名为 CategoriesDataSource
的 ObjectDataSource,该对象使用 CategoriesBLL
对象 s GetCategories()
方法检索数据。
图 2:新建名为 CategoriesDataSource
的对象数据源 (单击以查看全尺寸图像)
图 3:将 ObjectDataSource 配置为使用 CategoriesBLL
类 (单击以查看全尺寸图像)
图 4:使用 GetCategories()
方法检索类别列表 (单击以查看全尺寸图像)
完成“配置数据源”向导后,Visual Studio 会自动将 BoundField 添加到 Categories
GridView CategoryID
中的 、 CategoryName
、 Description
、 NumberOfProducts
和 BrochurePath
DataColumn
。 继续删除 BoundField,NumberOfProducts
GetCategories()
因为方法查询不会检索此信息。 另请删除 CategoryID
BoundField,并将 CategoryName
和 BrochurePath
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:为 添加 HyperLinkField BrochurePath
这将向 GridView 添加一列链接,如图 7 所示。 单击“查看手册”链接将直接在浏览器中显示 PDF,或提示用户下载文件,具体取决于是否安装了 PDF 阅读器以及浏览器的设置。
图 7:可以通过单击“查看宣传册”链接 (单击查看全尺寸图像来查看 类别的宣传册)
图 8:显示类别的小册子 PDF (单击以查看全尺寸图像)
隐藏无宣传册的类别的视图宣传册文本
如图 7 所示, BrochurePath
HyperLinkField 为所有记录显示其 Text
属性值 ( 视图宣传册 ) ,而不考虑 是否有非NULL
值 BrochurePath
。 当然,如果 BrochurePath
为 NULL
,则链接仅显示为文本,如海鲜类别 (请参阅图 7) 。 最好让这些类别不 BrochurePath
带值显示一些备用文本,例如没有可用的宣传册,而不是显示文本“查看宣传册”。
为了提供此行为,我们需要使用 TemplateField,其内容是通过调用页面方法生成的,该方法根据 BrochurePath
值发出相应的输出。 我们首先在 GridView 控件中使用 TemplateFields 教程中 探索了这种格式设置技术。
通过选择 BrochurePath
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 页的代码隐藏类中创建一个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 媒体播放器视频等)都作为单独的资源存在于 Web 服务器上。 HTML 包含对这些文件的引用,但不包括文件的实际内容。
例如,在 HTML 中, <img>
元素用于引用图片,其 src
属性指向图像文件,如下所示:
<img src="MyPicture.jpg" ... />
当浏览器收到此 HTML 时,它会向 Web 服务器发出另一个请求,以检索图像文件的二进制内容,然后它将显示在浏览器中。 相同的概念适用于任何二进制数据。 在步骤 2 中,没有将小册子作为页面 HTML 标记的一部分发送到浏览器。 相反,呈现的 HTML 提供的超链接在单击时会导致浏览器直接请求 PDF 文档。
若要显示或允许用户下载驻留在数据库中的二进制数据,我们需要创建一个单独的网页来返回数据。 对于我们的应用程序,只有一个二进制数据字段直接存储在数据库中,即类别图片。 因此,我们需要一个页面,在调用时返回特定类别的图像数据。
将新的 ASP.NET 页添加到 BinaryData
名为 的文件夹 DisplayCategoryPicture.aspx
。 执行此操作时,请将“选择母版页”复选框保留为未选中状态。 此页需要 CategoryID
querystring 中的值,并返回该类别的 列的 Picture
二进制数据。 由于此页面返回二进制数据,且不返回任何其他数据,因此它不需要 HTML 节中的任何标记。 因此,单击左下角的“源”选项卡,并删除除 指令以外的 <%@ Page %>
所有页面标记。 也就是说, DisplayCategoryPicture.aspx
声明性标记应包含一行:
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="DisplayCategoryPicture.aspx.cs"
Inherits="BinaryData_DisplayCategoryPicture" %>
如果在 指令中看到 MasterPageFile
<%@ Page %>
属性,请将其删除。
在页面代码隐藏类中,将以下代码添加到 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
类 s GetCategoryWithBinaryDataByCategoryID(categoryID)
方法检索图片数据。 此数据通过使用 Response.BinaryWrite(data)
方法返回到客户端,但在调用它之前, Picture
必须删除列值 OLE 标头。 这是通过创建名为 byte
的 strippedImageData
数组来实现的,该数组包含的字符数比 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
表 s Picture
列允许 NULL
值。 但是,该 DisplayCategoryPicture.aspx
页假定存在非NULL
值。 Picture
如果 具有NULL
值,CategoriesDataTable
则无法直接访问 的 属性。 如果确实希望允许 NULL
列的值 Picture
,需要包含以下条件:
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 ...
}
上面的代码假定文件夹中有一些名为 NoPictureAvailable.gif
Images
的图像文件,要为没有图片的类别显示这些文件。
如果 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 服务器不必发送回资源的内容。
但是,若要实现此行为,需要向表添加列PictureLastModified
Categories
以捕获上次更新列Picture
的时间,以及为 标头检查If-Modified-Since
的代码。 有关 标头和条件 GET 工作流的详细信息 If-Modified-Since
,请参阅 RSS 黑客的 HTTP 条件 GET 和 深入了解在 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}
的src
元素,其中 {0} 替换为 GridView 行的 CategoryID
值。
图 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 担任独立顾问、培训师和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0自学。 可以在 上联系 mitchell@4GuysFromRolla.com他, 也可以通过他的博客联系到他,该博客可在 http://ScottOnWriting.NET中找到。
特别感谢
本教程系列由许多有用的审阅者查看。 本教程的主要审阅者是 Teresa Murphy 和 Dave Gardner。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处放置一行 mitchell@4GuysFromRolla.com。
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈