作者 :斯科特·米切尔
本教程演示如何创建一个 Web 界面,允许用户输入文本数据和上传二进制文件。 为了说明可用于存储二进制数据的选项,一个文件将保存在数据库中,另一个文件存储在文件系统中。
介绍
在前面的两个教程中,我们探讨了存储与应用程序数据模型关联的二进制数据的技术,并介绍了如何使用 FileUpload 控件将文件从客户端发送到 Web 服务器,以及如何在数据 Web 控件中显示此二进制数据。 不过,我们尚未讨论如何将上传的数据与数据模型相关联。
在本教程中,我们将创建一个网页来添加新类别。 除了类别名称和说明的 TextBoxes 外,此页面还需要包含两个 FileUpload 控件,一个用于新类别图片,另一个用于小册子。 上传的图片将直接存储在新记录的 Picture
列中,而小册子将保存到 ~/Brochures
文件夹中,其中包含新记录列中 BrochurePath
保存的文件的路径。
在创建新网页之前,需要更新体系结构。 主 CategoriesTableAdapter
查询不会检索 Picture
列。 因此,自动生成Insert
的方法仅包含CategoryName
、Description
和BrochurePath
字段的输入。 因此,我们需要在 TableAdapter 中创建一个附加方法,以提示输入所有四 Categories
个字段。 业务逻辑层中的CategoriesBLL
类也需要更新。
步骤 1:将方法InsertWithPicture
添加到CategoriesTableAdapter
在CategoriesTableAdapter
”教程中创建后,我们将其配置为基于主查询自动生成INSERT
UPDATE
和DELETE
语句。 此外,我们指示 TableAdapter 使用 DB Direct 方法,这创建了方法 Insert
、Update
和 Delete
。 这些方法执行自动生成的语句 INSERT
、UPDATE
和 DELETE
,因此,根据主查询返回的列来接受输入参数。 在“上传文件”教程中,我们对CategoriesTableAdapter
的主要查询进行了增强,以使用BrochurePath
列。
由于CategoriesTableAdapter
的主查询不引用Picture
列,因此既不能添加新记录,也不能更新现有记录使用Picture
列的值。 为了捕获此信息,我们可以在 TableAdapter 中创建一个新方法,该方法专门用于插入包含二进制数据的记录,也可以自定义自动生成的 INSERT
语句。 自定义自动生成的 INSERT
语句的问题在于,我们自定义的内容可能会被向导覆盖。 例如,假设我们自定义了 INSERT
语句,以便包含使用 Picture
列。 这将更新 TableAdapter 的 Insert
方法,以增加一个类别图片的二进制数据输入参数。 然后,我们可以在业务逻辑层中创建一个方法,以使用此 DAL 方法并通过呈现层调用此 BLL 方法,并且一切都会非常出色。 也就是说,直到我们下次通过 TableAdapter 配置向导再次配置 TableAdapter。 向导完成后,我们对 INSERT
语句的自定义将被覆盖,Insert
方法将恢复为旧形式,并且代码将无法编译!
注释
使用存储过程而不是即席 SQL 语句时,这种烦恼将不再成为问题。 将来的教程将探讨如何使用存储过程代替数据访问层中的临时 SQL 语句。
为了避免这种潜在的头痛,而不是自定义自动生成的 SQL 语句,让我们改为为 TableAdapter 创建新的方法。 此方法(命名InsertWithPicture
)将接受值CategoryName
、Description
BrochurePath
列和Picture
列,并执行一个语句,该语句将所有四个INSERT
值存储在新记录中。
打开类型化数据集,然后在设计器中右键单击 CategoriesTableAdapter
该标头,然后从上下文菜单中选择“添加查询”。 这会启动 TableAdapter 查询配置向导,该向导首先询问 TableAdapter 查询应如何访问数据库。 选择“使用 SQL 语句”,然后单击“下一步”。 下一步将提示生成查询的类型。 由于我们正在创建查询以向表添加新记录 Categories
,请选择 INSERT 并单击“下一步”。
图 1:选择 INSERT 选项(单击以查看全尺寸图像)
现在需要指定 INSERT
SQL 语句。 向导会自动建议与 INSERT
TableAdapter 主查询对应的语句。 在本例中,它是一个INSERT
插入CategoryName
和Description
BrochurePath
值的语句。 更新语句,使 Picture
列与 @Picture
参数一起包含,如下所示:
INSERT INTO [Categories]
([CategoryName], [Description], [BrochurePath], [Picture])
VALUES
(@CategoryName, @Description, @BrochurePath, @Picture)
向导的最后一个屏幕要求我们命名新的 TableAdapter 方法。 输入 InsertWithPicture
并单击“完成”。
图 2:命名新的 TableAdapter 方法 InsertWithPicture
(单击以查看全尺寸图像)
步骤 2:更新业务逻辑层
由于表示层只应与业务逻辑层接口,而不是绕过它直接转到数据访问层,因此我们需要创建一个 BLL 方法来调用刚刚创建的 DAL 方法(InsertWithPicture
)。 在本教程中,在CategoriesBLL
类中创建一个名为InsertWithPicture
的方法,该方法接受三个String
和一个Byte
数组作为输入。 输入 String
参数用于类别的名称、说明和小册子文件路径,而 Byte
数组用于类别图片的二进制内容。 如以下代码所示,此 BLL 方法调用相应的 DAL 方法:
<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Insert, False)> _
Public Sub InsertWithPicture(categoryName As String, description As String, _
brochurePath As String, picture() As Byte)
Adapter.InsertWithPicture(categoryName, description, brochurePath, picture)
End Sub
注释
在将 InsertWithPicture
方法添加到 BLL 之前,请确保已保存好类型化数据集。
CategoriesTableAdapter
由于类代码是基于类型化数据集自动生成的,因此如果不首先将更改保存到类型化数据集,则Adapter
属性将不知道该方法InsertWithPicture
。
步骤 3:列出现有类别及其二进制数据
在本教程中,我们将创建一个页面,允许最终用户向系统添加新类别,并为新类别提供图片和小册子。 在 前面的教程中 ,我们使用 GridView 和 TemplateField 和 ImageField 显示每个类别的名称、说明、图片和下载其小册子的链接。 让我们复制本教程的功能,创建一个页面,其中列出了所有现有类别,并允许创建新类别。
首先从DisplayOrDownload.aspx
文件夹中打开BinaryData
页面。 转到源视图并复制 GridView 和 ObjectDataSource 的声明性语法,并将其粘贴到 <asp:Content>
元素中 UploadInDetailsView.aspx
。 此外,不要忘记将 GenerateBrochureLink
方法从 DisplayOrDownload.aspx
的代码隐藏类复制到 UploadInDetailsView.aspx
。
图 3:复制并粘贴声明性语法(DisplayOrDownload.aspx
UploadInDetailsView.aspx
单击以查看全尺寸图像)
将声明性语法和 GenerateBrochureLink
方法复制到 UploadInDetailsView.aspx
页面后,通过浏览器查看页面,以确保正确复制所有内容。 您应该会看到一个 GridView,上面列出八个类别,并包括下载小册子的链接,以及类别的图片。
图 4:现在应看到每个类别及其二进制数据(单击以查看全尺寸图像)
步骤 4:配置CategoriesDataSource
以支持插入
CategoriesDataSource
GridView 使用的 Categories
ObjectDataSource 当前不提供插入数据的功能。 为了支持通过此数据源控件插入,我们需要将其方法映射到其 Insert
基础对象 CategoriesBLL
中的方法。 具体而言,我们希望将其映射到 CategoriesBLL
方法,这个方法是我们早在步骤 2 中添加的。
首先单击 ObjectDataSource 智能标记中的“配置数据源”链接。 第一个屏幕显示数据源配置为使用的对象。 CategoriesBLL
保留此设置 as-is 并单击“下一步”转到“定义数据方法”屏幕。 移动到 INSERT 选项卡,然后从下拉列表中选择 InsertWithPicture
方法。 单击“完成”以完成向导。
图 5:将 ObjectDataSource 配置为使用 InsertWithPicture
方法(单击以查看全尺寸图像)
注释
完成向导后,Visual Studio 可能会询问是否要刷新字段和密钥,这将重新生成数据 Web 控件字段。 选择“否”,因为选择“是”将覆盖你可能所做的任何字段自定义。
完成向导后,ObjectDataSource 现在将包含其 InsertMethod
属性的值以及 InsertParameters
四个类别列的值,如以下声明性标记所示:
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="original_{0}" SelectMethod="GetCategories"
TypeName="CategoriesBLL" InsertMethod="InsertWithPicture">
<InsertParameters>
<asp:Parameter Name="categoryName" Type="String" />
<asp:Parameter Name="description" Type="String" />
<asp:Parameter Name="brochurePath" Type="String" />
<asp:Parameter Name="picture" Type="Object" />
</InsertParameters>
</asp:ObjectDataSource>
步骤 5:创建插入接口
如“ 插入、更新和删除数据概述”中所述,DetailsView 控件提供了一个内置插入接口,在处理支持插入的数据源控件时,可以使用该接口。 让我们将 DetailsView 控件添加到 GridView 上方的此页面,该控件将永久呈现其插入界面,允许用户快速添加新类别。 在 DetailsView 中添加新类别后,其下方的 GridView 将自动刷新并显示新类别。
首先,将 DetailsView 从工具箱拖到设计器中,并置于 GridView 的上方,将其ID
属性设置为NewCategory
,并清除Height
和Width
属性值。 从 DetailsView 智能标记中,将其绑定到现有 CategoriesDataSource
标记,然后选中“启用插入”复选框。
图 6:将 DetailsView 绑定到 CategoriesDataSource
,并启用插入功能(单击以查看全尺寸图像)
若要在其插入接口中永久呈现 DetailsView,请将其 DefaultMode
属性设置为 Insert
。
请注意,DetailsView 具有五个 BoundFields CategoryID
、CategoryName
、Description
、NumberOfProducts
、BrochurePath
,尽管 CategoryID
BoundField 未在插入接口中呈现,因为其 InsertVisible
属性设置为 False
。 这些 BoundFields 之所以存在,是因为它们是GetCategories()
方法返回的列,而这些列是 ObjectDataSource 调用以检索数据的。 对于插入,我们不希望用户指定一个 NumberOfProducts
值。 此外,我们需要允许他们上传新类别的图片,以及为小册子上传 PDF。
从 DetailsView 中删除NumberOfProducts
BoundField,然后分别将HeaderText
和CategoryName
BoundFields 的BrochurePath
属性更新为 Category 和 Brochure。 接下来,将 BrochurePath
BoundField 转换为 TemplateField,并添加一个用于图片的新 TemplateField,将其值设为 HeaderText
图片。 将 Picture
TemplateField 移动到 BrochurePath
TemplateField 和 CommandField 之间。
图 7:将 DetailsView 绑定到 CategoriesDataSource
并启用插入
如果通过“编辑字段”对话框将 BrochurePath
BoundField 转换为 TemplateField,则 TemplateField 包括ItemTemplate
、EditItemTemplate
和InsertItemTemplate
。 只需要 InsertItemTemplate
,因此可以随意删除其他模板。 此时,DetailsView 的声明性语法应如下所示:
<asp:DetailsView ID="NewCategory" runat="server" AutoGenerateRows="False"
DataKeyNames="CategoryID" DataSourceID="CategoriesDataSource"
DefaultMode="Insert">
<Fields>
<asp:BoundField DataField="CategoryID" HeaderText="CategoryID"
InsertVisible="False" ReadOnly="True"
SortExpression="CategoryID" />
<asp:BoundField DataField="CategoryName" HeaderText="Category"
SortExpression="CategoryName" />
<asp:BoundField DataField="Description" HeaderText="Description"
SortExpression="Description" />
<asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
<InsertItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Bind("BrochurePath") %>'></asp:TextBox>
</InsertItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Picture"></asp:TemplateField>
<asp:CommandField ShowInsertButton="True" />
</Fields>
</asp:DetailsView>
为小册子和图片字段添加 FileUpload 控件
目前, BrochurePath
TemplateField 包含 InsertItemTemplate
TextBox,而 Picture
TemplateField 不包含任何模板。 我们需要更新这两个 TemplateFields InsertItemTemplate
以使用 FileUpload 控件。
从 DetailsView 的智能标记中选择“编辑模板”选项,然后从下拉列表中选择 TemplateField 的 BrochurePath
和 InsertItemTemplate
。 删除 TextBox,然后将 FileUpload 控件从工具箱拖动到模板中。 将 FileUpload 控件 ID
设置为 BrochureUpload
. 同样,向 TemplateField s Picture
添加 FileUpload 控件InsertItemTemplate
。 将此 FileUpload 控件 ID
设置为 PictureUpload
.
图 8:将 FileUpload 控件添加到 InsertItemTemplate
(单击以查看全尺寸图像)
添加这些内容后,两个 TemplateField 的声明性语法将为:
<asp:TemplateField HeaderText="Brochure" SortExpression="BrochurePath">
<InsertItemTemplate>
<asp:FileUpload ID="BrochureUpload" runat="server" />
</InsertItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Picture">
<InsertItemTemplate>
<asp:FileUpload ID="PictureUpload" runat="server" />
</InsertItemTemplate>
</asp:TemplateField>
当用户添加新类别时,我们希望确保小册子和图片是正确的文件类型。 对于小册子,用户必须提供 PDF。 对于图片,我们需要用户上传图像文件,但我们是否允许 任何 图像文件或仅特定类型的图像文件,例如 GIF 或 JPG? 为了允许不同的文件类型,我们需要扩展Categories
架构以包含一个用于捕获文件类型的列,以便此类型可以通过Response.ContentType
在DisplayCategoryPicture.aspx
中发送到客户端。 由于我们没有此类列,因此应谨慎地限制用户仅提供特定的图像文件类型。
Categories
表的现有图像是位图,但 JPG 是 Web 上提供的图像更合适的文件格式。
如果用户上传了不正确的文件类型,则需要取消插入并显示指示问题的消息。 在 DetailsView 下添加标签 Web 控件。 将其ID
属性设置为UploadWarning
,清除其Text
属性,将CssClass
属性设置为“警告”,并将Visible
和EnableViewState
属性都设置为False
。
Warning
CSS 类定义在 Styles.css
中,并以大号红色斜体粗体字体呈现文本。
注释
理想情况下,CategoryName
和 Description
BoundFields 应该转换为 TemplateFields,并定制化其插入界面。
Description
例如,插入接口可能更适合通过多行文本框。 由于 CategoryName
该列不接受 NULL
值,因此应添加 RequiredFieldValidator 以确保用户为新类别名称提供值。 这些步骤作为练习留给读者。 请参阅 自定义数据修改接口 ,深入了解如何增强数据修改接口。
步骤 6:将上传的小册子保存到 Web 服务器文件系统
当用户输入新类别的值并点击“插入”按钮后,会触发页面回发,并开始执行插入流程。 首先,DetailsView s ItemInserting
事件触发。 接下来,调用 ObjectDataSource s Insert()
方法,这将导致将新记录添加到 Categories
表中。 之后,DetailsView 的ItemInserted
事件将触发。
在调用 ObjectDataSource s Insert()
方法之前,必须先确保用户上传适当的文件类型,然后将小册子 PDF 保存到 Web 服务器文件系统。 为 DetailsView 事件 ItemInserting
创建事件处理程序并添加以下代码:
' Reference the FileUpload controls
Dim BrochureUpload As FileUpload = _
CType(NewCategory.FindControl("BrochureUpload"), FileUpload)
If BrochureUpload.HasFile Then
' Make sure that a PDF has been uploaded
If String.Compare(System.IO.Path.GetExtension _
(BrochureUpload.FileName), ".pdf", True) <> 0 Then
UploadWarning.Text = _
"Only PDF documents may be used for a category's brochure."
UploadWarning.Visible = True
e.Cancel = True
Exit Sub
End If
End If
事件处理程序首先从 DetailsView 模板引用 BrochureUpload
FileUpload 控件。 然后,如果已上传小册子,则会检查已上传文件的扩展名。 如果未.PDF扩展,则会显示警告、取消插入,并结束事件处理程序的执行。
注释
依靠文件扩展名来确认上传的文件是否为PDF文档并不是一种绝对可靠的方法。 用户可以具有扩展名 .Brochure
的有效 PDF 文档,或者可能采用非 PDF 文档,并为其提供 .pdf
扩展。 需要以编程方式检查文件二进制内容,以便更最终地验证文件类型。 不过,这种彻底的方法往往过于过度:检查扩展足以满足大多数方案。
如 “上传文件” 教程中所述,将文件保存到文件系统时必须小心,以便一个用户的上传不会覆盖另一个文件。 在本教程中,我们将尝试使用与上传的文件相同的名称。 但是,如果目录中已存在具有相同文件名的文件 ~/Brochures
,我们将在末尾追加一个数字,直到找到唯一名称。 例如,如果用户上传名为Meats.pdf
的小册子文件,但文件夹中已经有一个文件Meats.pdf
~/Brochures
,我们将保存的文件名更改为Meats-1.pdf
。 如果存在,我们将尝试 Meats-2.pdf
等,直到找到唯一的文件名。
以下代码使用 File.Exists(path)
该方法 来确定文件是否已存在具有指定文件名。 如果是这样,它将继续为小册子尝试新的文件名,直到找不到冲突。
Const BrochureDirectory As String = "~/Brochures/"
Dim brochurePath As String = BrochureDirectory & BrochureUpload.FileName
Dim fileNameWithoutExtension As String = _
System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName)
Dim iteration As Integer = 1
While System.IO.File.Exists(Server.MapPath(brochurePath))
brochurePath = String.Concat(BrochureDirectory, _
fileNameWithoutExtension, "-", iteration, ".pdf")
iteration += 1
End While
找到有效的文件名后,需要将文件保存到文件系统,并且需要更新 ObjectDataSource 的值 brochurePath``InsertParameter
,以便将此文件名写入数据库。 正如我们在 上传文件 教程中看到的那样,可以使用 FileUpload 控件的方法 SaveAs(path)
保存该文件。 若要更新 ObjectDataSource s brochurePath
参数,请使用 e.Values
集合。
' Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath))
e.Values("brochurePath") = brochurePath
步骤 7:将上传的图片保存到数据库
若要将上传的图片存储在新 Categories
记录中,我们需要将上传的二进制内容分配给 DetailsView 事件中的 ObjectDataSource s picture
ItemInserting
参数。 但是,在进行此分配之前,首先需要确保上传的图片是 JPG,而不是其他一些图像类型。 与步骤 6 中一样,让我们使用上传的图片文件扩展名来确定其类型。
Categories
虽然该表允许NULL
列Picture
的值,但所有类别当前都有图片。 让我们强制用户在通过此页面添加新类别时提供图片。 以下代码检查以确保图片已上传,并且具有适当的扩展。
' Reference the FileUpload controls
Dim PictureUpload As FileUpload = _
CType(NewCategory.FindControl("PictureUpload"), FileUpload)
If PictureUpload.HasFile Then
' Make sure that a JPG has been uploaded
If String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
".jpg", True) <> 0 AndAlso _
String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
".jpeg", True) <> 0 Then
UploadWarning.Text = _
"Only JPG documents may be used for a category's picture."
UploadWarning.Visible = True
e.Cancel = True
Exit Sub
End If
Else
' No picture uploaded!
UploadWarning.Text = _
"You must provide a picture for the new category."
UploadWarning.Visible = True
e.Cancel = True
Exit Sub
End If
此代码应放置在步骤 6 中的代码 之前 ,以便如果图片上传出现问题,事件处理程序将在将小册子文件保存到文件系统之前终止。
假设上传了适当的文件,请使用以下代码行将上传的二进制内容分配给图片参数的值:
' Set the value of the picture parameter
e.Values("picture") = PictureUpload.FileBytes
完整的ItemInserting
事件处理程序
为了完整性,下面是 ItemInserting
整个事件处理程序:
Protected Sub NewCategory_ItemInserting _
(sender As Object, e As DetailsViewInsertEventArgs) _
Handles NewCategory.ItemInserting
' Reference the FileUpload controls
Dim PictureUpload As FileUpload = _
CType(NewCategory.FindControl("PictureUpload"), FileUpload)
If PictureUpload.HasFile Then
' Make sure that a JPG has been uploaded
If String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
".jpg", True) <> 0 AndAlso _
String.Compare(System.IO.Path.GetExtension(PictureUpload.FileName), _
".jpeg", True) <> 0 Then
UploadWarning.Text = _
"Only JPG documents may be used for a category's picture."
UploadWarning.Visible = True
e.Cancel = True
Exit Sub
End If
Else
' No picture uploaded!
UploadWarning.Text = _
"You must provide a picture for the new category."
UploadWarning.Visible = True
e.Cancel = True
Exit Sub
End If
' Set the value of the picture parameter
e.Values("picture") = PictureUpload.FileBytes
' Reference the FileUpload controls
Dim BrochureUpload As FileUpload = _
CType(NewCategory.FindControl("BrochureUpload"), FileUpload)
If BrochureUpload.HasFile Then
' Make sure that a PDF has been uploaded
If String.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName), _
".pdf", True) <> 0 Then
UploadWarning.Text = _
"Only PDF documents may be used for a category's brochure."
UploadWarning.Visible = True
e.Cancel = True
Exit Sub
End If
Const BrochureDirectory As String = "~/Brochures/"
Dim brochurePath As String = BrochureDirectory & BrochureUpload.FileName
Dim fileNameWithoutExtension As String = _
System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName)
Dim iteration As Integer = 1
While System.IO.File.Exists(Server.MapPath(brochurePath))
brochurePath = String.Concat(BrochureDirectory, _
fileNameWithoutExtension, "-", iteration, ".pdf")
iteration += 1
End While
' Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath))
e.Values("brochurePath") = brochurePath
End If
End Sub
步骤 8:修复DisplayCategoryPicture.aspx
页面
让我们花点时间测试在最后几个步骤中创建的插入接口和 ItemInserting
事件处理程序。
UploadInDetailsView.aspx
通过浏览器访问页面并尝试添加类别,但省略图片,或指定非 JPG 图片或非 PDF 小册子。 在这些情况下,将显示错误消息并取消插入工作流。
图 9:如果上传了无效的文件类型,将显示警告消息(单击以查看全尺寸图像)
验证页面要求上传图片且不接受非 PDF 或非 JPG 文件后,请添加具有有效 JPG 图片的新类别,使“小册子”字段为空。 单击“插入”按钮后,页面将回发,并会在 Categories
表中添加一条新记录,其中直接将已上传图像的二进制内容存储到数据库中。 GridView 已更新并显示新添加的类别的行,但如图 10 所示,新类别的图片未正确呈现。
图 10:“新建类别”图片未显示(单击以查看全尺寸图像)
新图片未显示的原因是处理具有 OLE 标头的位图的页面配置为返回指定类别的图片。 在将二进制内容发送回客户端之前,Picture
列的 78 字节头部会被剥离。 但是,我们刚刚为新类别上传的 JPG 文件没有此 OLE 标头;因此,将从图像的二进制数据中删除必要的有效字节。
由于现在在Categories
表中同时存在具有OLE标头的位图和JPG,因此我们需要更新DisplayCategoryPicture.aspx
,以便它对原始的八个类别执行OLE标头剥离,并绕过新类别记录的剥离。 在下一教程中,我们将了解如何更新现有记录的图像,并更新旧有类别的所有图片,使它们成为 JPG 文件。 因此,目前,请使用以下代码 DisplayCategoryPicture.aspx
仅去除原始八个类别的 OLE 标头。
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)
If categoryID <= 8 Then
' 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)
Else
' For new categories, images are JPGs...
' Output HTTP headers providing information about the binary data
Response.ContentType = "image/jpeg"
' Output the binary data
Response.BinaryWrite(category.Picture)
End If
End Sub
通过此更改,JPG 图像现在在 GridView 中正确呈现。
图 11:正确呈现新类别的 JPG 图像(单击以查看全尺寸图像)
步骤 9:在遇到异常时删除小册子
在 Web 服务器的文件系统上存储二进制数据的挑战之一是,它造成了数据模型与其二进制数据之间的断开。 因此,每当删除记录时,还必须删除文件系统上的相应二进制数据。 插入时可能涉及此问题。 请考虑以下方案:用户添加新类别,并指定有效的图片和小册子。 单击“插入”按钮时,会发生回发,并触发 DetailsView 的 ItemInserting
事件,将小册子保存到 Web 服务器的文件系统中。 接下来,调用 ObjectDataSource 的 Insert()
方法,该方法调用 CategoriesBLL
类的 InsertWithPicture
方法,而该方法调用 CategoriesTableAdapter
的 InsertWithPicture
方法。
现在,如果数据库处于脱机状态,或者 SQL 语句中 INSERT
是否存在错误,会发生什么情况? 显然,INSERT 将失败,因此不会向数据库添加新的类别行。 但我们仍然将上传的小册子文件放在 Web 服务器文件系统上! 在插入工作流期间,需要在出现异常时删除此文件。
如先前在 ASP.NET 页面教程中的“处理 BLL 异常与 DAL-Level 异常” 所讨论,当异常从架构的深层抛出时,它会通过系统的各个层逐步传递。 在呈现层中,我们可以确定 DetailsView 事件 ItemInserted
中是否发生了异常。 此事件处理程序还提供 ObjectDataSource s 的值 InsertParameters
。 因此,我们可以为 ItemInserted
检查是否存在异常的事件创建事件处理程序,如果是,则删除 ObjectDataSource s brochurePath
参数指定的文件:
Protected Sub NewCategory_ItemInserted _
(sender As Object, e As DetailsViewInsertedEventArgs) _
Handles NewCategory.ItemInserted
If e.Exception IsNot Nothing Then
' Need to delete brochure file, if it exists
If e.Values("brochurePath") IsNot Nothing Then
System.IO.File.Delete(Server.MapPath _
(e.Values("brochurePath").ToString()))
End If
End If
End Sub
概要
必须执行许多步骤,以便提供基于 Web 的接口来添加包含二进制数据的记录。 如果二进制数据直接存储在数据库中,则可能需要更新体系结构,并添加特定方法来处理插入二进制数据的情况。 更新体系结构后,下一步是创建插入接口,可以使用已自定义的 DetailsView 来完成该接口,该接口已自定义为包含每个二进制数据字段的 FileUpload 控件。 然后,可以将上传的数据保存到 Web 服务器文件系统,或分配给 DetailsView 事件处理程序 ItemInserting
中的数据源参数。
将二进制数据保存到文件系统比将数据直接保存到数据库中需要更多规划。 为了避免一个用户的上传覆盖其他用户的上传,必须选择命名方案。 此外,如果数据库插入失败,则必须执行额外的步骤来删除上传的文件。
我们现在能够使用小册子和图片向系统添加新类别,但我们尚未了解如何更新现有类别的二进制数据,或者如何正确删除已删除类别的二进制数据。 我们将在下一教程中探讨这两个主题。
快乐编程!
关于作者
斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是 《Sams 教你自己学 ASP.NET 2.0,24小时掌握》。 可以通过 mitchell@4GuysFromRolla.com 联系到他。
特别致谢
本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是戴夫·加德纳、特蕾莎·墨菲和伯纳黛特·利。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我写信。mitchell@4GuysFromRolla.com