上载和删除现有的二进制数据 (VB)
在前面的教程中,我们了解了 GridView 控件如何使编辑和删除文本数据变得简单。 在本教程中,我们将了解 GridView 控件如何使编辑和删除二进制数据成为可能,无论二进制数据是保存在数据库中还是存储在文件系统中。
简介
在过去的三个教程中,我们添加了大量用于处理二进制数据的功能。 我们首先向表中添加列 BrochurePath
, Categories
并相应地更新了体系结构。 我们还添加了数据访问层和业务逻辑层方法,用于处理类别表的现有 Picture
列,这些列保存图像文件的二进制内容。 我们已构建网页以在 GridView 中显示二进制数据,并提供了小册子的下载链接,其中类别的图片显示在 元素 <img>
中,并添加了 DetailsView 以允许用户添加新类别并上传其小册子和图片数据。
要实现的只是编辑和删除现有类别的功能,我们将在本教程中使用 GridView 的内置编辑和删除功能完成此操作。 编辑类别时,用户可以选择上传新图片或让类别继续使用现有图片。 对于小册子,他们可以选择使用现有小册子,上传新的小册子,或指示该类别不再具有与之关联的小册子。 让我们开始吧!
步骤 1:更新数据访问层
DAL 具有自动生成的 Insert
、 Update
和 Delete
方法,但这些方法是基于CategoriesTableAdapter
不包含 列的 main 查询Picture
生成的。 因此, Insert
和 Update
方法不包括用于指定类别图片的二进制数据的参数。 与 前面的教程中一样,我们需要创建一个新的 TableAdapter 方法,用于在指定二进制数据时更新 Categories
表。
打开“类型化数据集”,然后从Designer右键单击CategoriesTableAdapter
标头,然后从上下文菜单中选择“添加查询”以启动 TableAdapter 查询配置向导。 此向导首先询问 TableAdapter 查询应如何访问数据库。 选择“使用 SQL 语句”,然后单击“下一步”。 下一步将提示要生成的查询类型。 由于我们创建了一个查询来向表添加新记录 Categories
,因此请选择“更新”,然后单击“下一步”。
图 1:选择“更新选项” (单击以查看全尺寸图像)
我们现在需要指定 UPDATE
SQL 语句。 向导会自动建议一个UPDATE
与 TableAdapter main 查询对应的语句, (更新 CategoryName
Description
、 和 BrochurePath
值) 。 更改 语句,以便将 Picture
列与 参数一起包含在内 @Picture
,如下所示:
UPDATE [Categories] SET
[CategoryName] = @CategoryName,
[Description] = @Description,
[BrochurePath] = @BrochurePath ,
[Picture] = @Picture
WHERE (([CategoryID] = @Original_CategoryID))
向导的最后一个屏幕要求我们命名新的 TableAdapter 方法。 输入 UpdateWithPicture
并单击“完成”。
图 2:将 New TableAdapter 方法 UpdateWithPicture
命名 (单击以查看全尺寸图像)
步骤 2:添加业务逻辑层方法
除了更新 DAL 外,还需要更新 BLL 以包含用于更新和删除类别的方法。 这些是从表示层调用的方法。
若要删除类别,可以使用 CategoriesTableAdapter
自动生成 Delete
的 方法。 将以下方法添加到 CategoriesBLL
类:
<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Delete, True)> _
Public Function DeleteCategory(ByVal categoryID As Integer) As Boolean
Dim rowsAffected As Integer = Adapter.Delete(categoryID)
' Return true if precisely one row was deleted, otherwise false
Return rowsAffected = 1
End Function
在本教程中,让我们创建两个用于更新类别的方法 - 一个需要二进制图片数据并调用 UpdateWithPicture
我们刚刚添加到 CategoriesTableAdapter
的方法,另一种方法只 CategoryName
接受 、 Description
和 BrochurePath
值并使用 CategoriesTableAdapter
类自动生成的 Update
语句。 使用两种方法背后的理由是,在某些情况下,用户可能需要更新类别图片及其其他字段,在这种情况下,用户必须上传新图片。 然后,可以在 语句中使用 UPDATE
上传的图片的二进制数据。 在其他情况下,用户可能只对更新名称和说明感兴趣。 但是, UPDATE
如果 语句也需要列的 Picture
二进制数据,那么我们也需要提供该信息。 这需要额外的数据库行程,以恢复正在编辑的记录的图片数据。 因此,我们需要两 UPDATE
种方法。 业务逻辑层将根据更新类别时是否提供图片数据来确定要使用哪一个。
为方便执行此操作,请向 CategoriesBLL
类添加两个方法,这两个方法都名为 UpdateCategory
。 第一个应接受三 String
个 、一个 Byte
数组和 一个 Integer
作为其输入参数;第二个应接受三 String
个 和一个 Integer
。 输入String
参数用于类别的名称、说明和小册子文件路径,Byte
数组用于类别图片的二进制内容,标识Integer
CategoryID
要更新的记录的 。 请注意,如果传入 Byte
的数组为 ,则第一个重载将调用第二个 Nothing
重载:
<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Update, False)> _
Public Function UpdateCategory(categoryName As String, description As String, _
brochurePath As String, picture() As Byte, categoryID As Integer) As Boolean
' If no picture is specified, use other overload
If picture Is Nothing Then
Return UpdateCategory(categoryName, description, brochurePath, categoryID)
End If
' Update picture, as well
Dim rowsAffected As Integer = Adapter.UpdateWithPicture _
(categoryName, description, brochurePath, picture, categoryID)
' Return true if precisely one row was updated, otherwise false
Return rowsAffected = 1
End Function
<System.ComponentModel.DataObjectMethodAttribute _
(System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateCategory(categoryName As String, description As String, _
brochurePath As String, categoryID As Integer) As Boolean
Dim rowsAffected As Integer = Adapter.Update _
(categoryName, description, brochurePath, categoryID)
' Return true if precisely one row was updated, otherwise false
Return rowsAffected = 1
End Function
步骤 3:通过插入和查看功能复制
在 前面的教程中 ,我们创建了一个名为 UploadInDetailsView.aspx
的页面,其中列出了 GridView 中的所有类别,并提供 DetailsView 以向系统添加新类别。 在本教程中,我们将扩展 GridView 以包括编辑和删除支持。 与其继续从 UploadInDetailsView.aspx
中工作,不如将本教程的更改 UpdatingAndDeleting.aspx
放在同一文件夹中 ~/BinaryData
的页面中。 将 声明性标记和代码从 UploadInDetailsView.aspx
复制并粘贴到 UpdatingAndDeleting.aspx
。
首先打开 UploadInDetailsView.aspx
页面。 复制 元素中的所有 <asp:Content>
声明性语法,如图 3 所示。 接下来,打开 UpdatingAndDeleting.aspx
此标记并将其粘贴到其 <asp:Content>
元素中。 同样,将页面代码隐藏类中的 UploadInDetailsView.aspx
代码复制到 UpdatingAndDeleting.aspx
。
图 3:从 UploadInDetailsView.aspx
(复制声明性标记 单击以查看全尺寸图像)
复制声明性标记和代码后,请访问 UpdatingAndDeleting.aspx
。 应会看到与上一教程中的页面相同的输出和相同的用户体验 UploadInDetailsView.aspx
。
步骤 4:向 ObjectDataSource 和 GridView 添加删除支持
正如我们在 插入、更新和删除数据概述 教程中所述,GridView 提供了内置的删除功能,如果网格的基础数据源支持删除,则可以在复选框的刻度处启用这些功能。 目前,GridView 绑定到的 ObjectDataSource (CategoriesDataSource
) 不支持删除。
若要解决此问题,请单击 ObjectDataSource 智能标记中的“配置数据源”选项以启动向导。 第一个屏幕显示 ObjectDataSource 已配置为使用 CategoriesBLL
类。 点击“下一步”。 目前,仅指定 ObjectDataSource 和InsertMethod
SelectMethod
属性。 但是,向导会分别使用 UpdateCategory
和 DeleteCategory
方法自动填充“更新”和“删除”选项卡中的下拉列表。 这是因为在 CategoriesBLL
类中,我们使用 作为更新和删除的默认方法标记这些 DataObjectMethodAttribute
方法。
现在,请将“更新”选项卡下拉列表设置为“ (无”) ,但将“删除”选项卡下拉列表设置为 DeleteCategory
。 我们将在步骤 6 中返回到此向导,以添加更新支持。
图 4:将 ObjectDataSource 配置为使用 DeleteCategory
方法 (单击以查看全尺寸图像)
注意
完成向导后,Visual Studio 可能会询问是否要刷新字段和密钥,这将重新生成数据 Web 控件字段。 选择“否”,因为选择“是”将覆盖你可能所做的任何字段自定义。
ObjectDataSource 现在将包含其 DeleteMethod
属性的值以及 DeleteParameter
。 回想一下,使用向导指定方法时,Visual Studio 将 ObjectDataSource 的 OldValuesParameterFormatString
属性设置为 original_{0}
,这会导致更新和删除方法调用出现问题。 因此,请完全清除此属性或将其重置为默认值 {0}
。 如果需要刷新此 ObjectDataSource 属性上的内存,请参阅 插入、更新和删除数据概述 教程。
完成向导并修复 OldValuesParameterFormatString
后,ObjectDataSource 的声明性标记应如下所示:
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="{0}" SelectMethod="GetCategories"
TypeName="CategoriesBLL" InsertMethod="InsertWithPicture"
DeleteMethod="DeleteCategory">
<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>
<DeleteParameters>
<asp:Parameter Name="categoryID" Type="Int32" />
</DeleteParameters>
</asp:ObjectDataSource>
配置 ObjectDataSource 后,通过选中 GridView 智能标记中的“启用删除”复选框,将删除功能添加到 GridView。 这会将 CommandField 添加到 GridView,其 ShowDeleteButton
属性设置为 True
。
图 5:在 GridView 中启用对删除的支持 (单击以查看全尺寸图像)
花点时间测试删除功能。 表 和 Categories
CategoryID
表 CategoryID
之间Products
有一个外键,因此,如果尝试删除前八个类别中的任何一个类别,则会出现外键约束冲突异常。 若要测试此功能,请添加新类别,同时提供小册子和图片。 我的测试类别如图 6 所示,包括名为 Test.pdf
的测试手册文件和测试图片。 图 7 显示了添加测试类别后的 GridView。
图 6:使用宣传册和图像添加测试类别 (单击以查看全尺寸图像)
图 7:插入测试类别后,它显示在 GridView 中 (单击以查看全尺寸图像)
在 Visual Studio 中,刷新解决方案资源管理器。 现在应会在 文件夹中看到一个新文件 ~/Brochures
, Test.pdf
(请参阅图 8) 。
接下来,单击“测试类别”行中的“删除”链接,导致页面回发并 CategoriesBLL
触发类 s DeleteCategory
方法。 这将调用 DAL s Delete
方法,导致将相应的 DELETE
语句发送到数据库。 然后,数据将反弹到 GridView,标记将发送回客户端,但测试类别不再存在。
虽然删除工作流已成功从 Categories
表中删除了测试类别记录,但它没有从 Web 服务器的文件系统中删除其手册文件。 刷新解决方案资源管理器,你将看到Test.pdf
它仍然位于 文件夹中~/Brochures
。
图 8: Test.pdf
未从 Web 服务器的文件系统中删除文件
步骤 5:删除已删除类别的宣传册文件
在数据库外部存储二进制数据的一个缺点是,删除关联的数据库记录时,必须采取额外的步骤来清理这些文件。 GridView 和 ObjectDataSource 提供在执行删除命令之前和之后触发的事件。 实际上,我们需要为操作前事件和操作后事件创建事件处理程序。 在 Categories
删除记录之前,我们需要确定其 PDF 文件的路径,但不希望在删除类别之前删除 PDF,以防出现某种异常且类别未删除。
GridView 事件RowDeleting
在调用 ObjectDataSource 的 delete 命令之前触发,而其RowDeleted
事件在调用后触发。 使用以下代码为这两个事件创建事件处理程序:
' A page variable to "remember" the deleted category's BrochurePath value
Private deletedCategorysPdfPath As String = Nothing
Protected Sub Categories_RowDeleting(sender As Object, e As GridViewDeleteEventArgs) _
Handles Categories.RowDeleting
' Determine the PDF path for the category being deleted...
Dim categoryID As Integer = Convert.ToInt32(e.Keys("CategoryID"))
Dim categoryAPI As New CategoriesBLL()
Dim categoriesData As Northwind.CategoriesDataTable = _
categoryAPI.GetCategoryByCategoryID(categoryID)
Dim category As Northwind.CategoriesRow = categoriesData(0)
If category.IsBrochurePathNull() Then
deletedCategorysPdfPath = Nothing
Else
deletedCategorysPdfPath = category.BrochurePath
End If
End Sub
Protected Sub Categories_RowDeleted(sender As Object, e As GridViewDeletedEventArgs) _
Handles Categories.RowDeleted
' Delete the brochure file if there were no problems deleting the record
If e.Exception Is Nothing Then
DeleteRememberedBrochurePath()
End If
End Sub
在事件处理程序中 RowDeleting
, CategoryID
从 GridView 的 集合中抓取要删除的行的 DataKeys
,可以通过 集合在此事件处理程序中访问该 e.Keys
集合。 接下来, CategoriesBLL
调用 类 s GetCategoryByCategoryID(categoryID)
以返回有关要删除的记录的信息。 如果返回 CategoriesDataRow
的对象具有非NULL``BrochurePath
值,则它存储在页变量 deletedCategorysPdfPath
中,以便可以在事件处理程序中删除 RowDeleted
该文件。
注意
或者,我们可以将 添加到 GridView 的 DataKeyNames
属性,并通过 集合访问记录值e.Keys
,而不是检索BrochurePath
事件处理程序中RowDeleting
删除的记录的详细信息Categories
。BrochurePath
这样做会略微增加 GridView 的视图状态大小,但会减少所需的代码量,并保存数据库的行程。
调用 ObjectDataSource 的基础 delete 命令后,GridView 事件处理程序 RowDeleted
将触发。 如果删除数据时没有例外,并且 有 值, deletedCategorysPdfPath
则会从文件系统中删除 PDF。 请注意,清理与其图片关联的类别二进制数据不需要此额外代码。 这是因为图片数据直接存储在数据库中,因此删除该 Categories
行也会删除该类别的图片数据。
添加这两个事件处理程序后,请再次运行此测试用例。 删除类别时,也会删除其关联的 PDF。
更新现有记录关联的二进制数据会面临一些有趣的挑战。 本教程的其余部分将深入探讨如何向宣传册和图片添加更新功能。 步骤 6 探讨更新宣传册信息的技术,而步骤 7 则探讨更新图片。
步骤 6:更新类别手册
如插入、更新和删除数据概述教程中所述,GridView 提供内置的行级编辑支持,如果正确配置了其基础数据源,则可以通过复选框的刻度实现。 目前, CategoriesDataSource
ObjectDataSource 尚未配置为包含更新支持,因此让我们将其添加到 中。
单击 ObjectDataSource 向导中的“配置数据源”链接,然后继续执行第二个步骤。 由于 在 中使用CategoriesBLL
了 DataObjectMethodAttribute
,因此 UPDATE 下拉列表应自动填充UpdateCategory
重载,该重载接受除) (Picture
所有列的四个输入参数。 更改此项,使其使用包含五个参数的重载。
图 9:配置 ObjectDataSource 以使用 UpdateCategory
包含 Picture
参数的方法 (单击以查看全尺寸图像)
ObjectDataSource 现在将包含其 UpdateMethod
属性的值以及相应的 UpdateParameter
。 如步骤 4 中所述,使用“配置数据源”向导时,Visual Studio 将 ObjectDataSource 属性 OldValuesParameterFormatString
设置为 original_{0}
。 这将导致更新和删除方法调用出现问题。 因此,请完全清除此属性,或将其重置为默认值 {0}
。
完成向导并修复 OldValuesParameterFormatString
后,ObjectDataSource 的声明性标记应如下所示:
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="{0}" SelectMethod="GetCategories"
TypeName="CategoriesBLL" InsertMethod="InsertWithPicture"
DeleteMethod="DeleteCategory" UpdateMethod="UpdateCategory">
<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>
<DeleteParameters>
<asp:Parameter Name="categoryID" Type="Int32" />
</DeleteParameters>
<UpdateParameters>
<asp:Parameter Name="categoryName" Type="String" />
<asp:Parameter Name="description" Type="String" />
<asp:Parameter Name="brochurePath" Type="String" />
<asp:Parameter Name="picture" Type="Object" />
<asp:Parameter Name="categoryID" Type="Int32" />
</UpdateParameters>
</asp:ObjectDataSource>
若要打开 GridView 内置编辑功能,检查 GridView 智能标记中的“启用编辑”选项。 这会将 CommandField 的 ShowEditButton
属性设置为 True
,从而为) 编辑的行添加“编辑”按钮 (和“更新”和“取消”按钮。
图 10:将 GridView 配置为支持编辑 (单击以查看全尺寸图像)
通过浏览器访问页面,然后单击其中一行的“编辑”按钮。 CategoryName
和 Description
BoundField 呈现为文本框。 BrochurePath
TemplateField 缺少 ,EditItemTemplate
因此它继续显示其ItemTemplate
与宣传册的链接。 Picture
ImageField 呈现为 TextBox,其Text
属性被赋予 ImageField 值DataImageUrlField
的值,在本例CategoryID
中为 。
图 11:GridView 缺少用于 BrochurePath
(单击以查看全尺寸图像)
自定义BrochurePath
编辑界面
我们需要为 BrochurePath
TemplateField 创建一个编辑界面,该界面允许用户执行以下操作之一:
- 将类别的宣传册保留为原样,
- 通过上传新的宣传册来更新类别的宣传册,或者
- 如果类别不再具有关联的小册子) ,请完全 (删除类别的宣传册。
我们还需要更新 Picture
ImageField 的编辑界面,但在步骤 7 中将对此进行介绍。
在 GridView 智能标记中,单击“编辑模板”链接,然后从下拉列表中选择 BrochurePath
“TemplateField EditItemTemplate
”。 将 RadioButtonList Web 控件添加到此模板,将其 ID
属性设置为 BrochureOptions
,将其 AutoPostBack
属性设置为 True
。 在属性窗口中,单击 属性中的Items
省略号,这将显示ListItem
“集合”编辑器。 分别使用 Value
s 1、2 和 3 添加以下三个选项:
- 使用当前手册
- 删除当前宣传册
- 上传新宣传册
将第一个 ListItem
属性 Selected
设置为 True
。
图 12:向 RadioButtonList 添加 3 ListItem
个
在 RadioButtonList 下,添加名为 的 BrochureUpload
FileUpload 控件。 将其 Visible
属性设置为 False
。
图 13:将 RadioButtonList 和 FileUpload 控件添加到 EditItemTemplate
(单击以查看全尺寸图像)
此 RadioButtonList 为用户提供三个选项。 其思路是,仅当最后一个选项“上传新手册”处于选中状态时,才会显示 FileUpload 控件。 为此,请为 RadioButtonList 事件 SelectedIndexChanged
创建事件处理程序并添加以下代码:
Protected Sub BrochureOptions_SelectedIndexChanged _
(sender As Object, e As EventArgs)
' Get a reference to the RadioButtonList and its Parent
Dim BrochureOptions As RadioButtonList = _
CType(sender, RadioButtonList)
Dim parent As Control = BrochureOptions.Parent
' Now use FindControl("controlID") to get a reference of the
' FileUpload control
Dim BrochureUpload As FileUpload = _
CType(parent.FindControl("BrochureUpload"), FileUpload)
' Only show BrochureUpload if SelectedValue = "3"
BrochureUpload.Visible = (BrochureOptions.SelectedValue = "3")
End Sub
由于 RadioButtonList 和 FileUpload 控件位于模板中,因此我们必须编写一些代码以编程方式访问这些控件。 事件处理程序 SelectedIndexChanged
在输入参数中传递 RadioButtonList 的 sender
引用。 若要获取 FileUpload 控件,我们需要获取 RadioButtonList 的父控件,并从那里使用 FindControl("controlID")
方法。 同时引用 RadioButtonList 和 FileUpload 控件后,仅当 RadioButtonList 等于 Visible
3 时,FileUpload 控件的 SelectedValue
属性才会设置为 True
,这是Value
上传新手册ListItem
的 。
完成此代码后,请花点时间测试编辑界面。 单击行的“编辑”按钮。 最初,应选择“使用当前手册”选项。 更改所选索引会导致回发。 如果选择第三个选项,则显示 FileUpload 控件,否则隐藏。 图 14 显示了首次单击“编辑”按钮时的编辑界面;图 15 显示了选择“上传新宣传册”选项后的界面。
图 14:最初,选择“使用当前宣传册”选项 (单击以查看全尺寸图像)
图 15:选择“上传新宣传册”选项显示“文件”“上传控件” (单击以查看全尺寸图像)
保存宣传册文件并更新BrochurePath
列
单击 GridView 的“更新”按钮时,会触发其 RowUpdating
事件。 调用 ObjectDataSource 的 update 命令,然后触发 GridView 事件 RowUpdated
。 与删除工作流一样,我们需要为这两个事件创建事件处理程序。 在事件处理程序中 RowUpdating
,我们需要根据 SelectedValue
RadioButtonList 的 BrochureOptions
确定要执行的操作:
SelectedValue
如果 为 1,我们希望继续使用相同的BrochurePath
设置。 因此,我们需要将 ObjectDataSource 参数brochurePath
设置为要更新的记录的现有BrochurePath
值。 可以使用 设置e.NewValues["brochurePath"] = value
ObjectDataSource 参数brochurePath
。SelectedValue
如果 为 2,则我们希望将记录值BrochurePath
设置为NULL
。 这可以通过将 ObjectDataSource 参数brochurePath
设置为Nothing
来实现,这会导致在 语句中使用UPDATE
数据库NULL
。 如果存在正在删除的现有小册子文件,则需要删除现有文件。 但是,我们只想在更新完成且不引发异常的情况下执行此操作。SelectedValue
如果 为 3,则我们希望确保用户已上传 PDF 文件,然后将其保存到文件系统并更新记录的BrochurePath
列值。 此外,如果有正在替换的现有小册子文件,我们需要删除以前的文件。 但是,我们只想在更新完成且不引发异常的情况下执行此操作。
当 RadioButtonList 为 SelectedValue
3 时需要完成的步骤与 DetailsView 事件处理程序 ItemInserting
使用的步骤几乎相同。 从 我们在上一教程中添加的 DetailsView 控件添加新的类别记录时,将执行此事件处理程序。 因此,我们有必要将此功能重构为单独的方法。 具体而言,我已将常用功能转移到两种方法中:
ProcessBrochureUpload(FileUpload, out bool)
接受 FileUpload 控件实例和输出布尔值作为输入,该值指定删除或编辑操作是否应继续,或者是否应由于某些验证错误而取消操作。 此方法返回已保存文件的路径,如果未null
保存文件,则返回 。DeleteRememberedBrochurePath
如果 不是null
,则deletedCategorysPdfPath
删除由页变量deletedCategorysPdfPath
中的路径指定的文件。
下面介绍了这两种方法的代码。 请注意与上一教程中的 DetailsView 事件处理程序ItemInserting
之间的ProcessBrochureUpload
相似性。 在本教程中,我更新了 DetailsView 事件处理程序以使用这些新方法。 下载与本教程关联的代码,查看对 DetailsView 事件处理程序的修改。
Private Function ProcessBrochureUpload _
(BrochureUpload As FileUpload, CancelOperation As Boolean) As String
CancelOperation = False ' by default, do not cancel operation
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
CancelOperation = True
Return Nothing
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))
Return brochurePath
Else
' No file uploaded
Return Nothing
End If
End Function
Private Sub DeleteRememberedBrochurePath()
' Is there a file to delete?
If deletedCategorysPdfPath IsNot Nothing Then
System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath))
End If
End Sub
GridView 和RowUpdating
RowUpdated
事件处理程序使用 ProcessBrochureUpload
和 DeleteRememberedBrochurePath
方法,如以下代码所示:
Protected Sub Categories_RowUpdating _
(sender As Object, e As GridViewUpdateEventArgs) _
Handles Categories.RowUpdating
' Reference the RadioButtonList
Dim BrochureOptions As RadioButtonList = _
CType(Categories.Rows(e.RowIndex).FindControl("BrochureOptions"), _
RadioButtonList)
' Get BrochurePath information about the record being updated
Dim categoryID As Integer = Convert.ToInt32(e.Keys("CategoryID"))
Dim categoryAPI As New CategoriesBLL()
Dim categoriesData As Northwind.CategoriesDataTable = _
categoryAPI.GetCategoryByCategoryID(categoryID)
Dim category As Northwind.CategoriesRow = categoriesData(0)
If BrochureOptions.SelectedValue = "1" Then
' Use current value for BrochurePath
If category.IsBrochurePathNull() Then
e.NewValues("brochurePath") = Nothing
Else
e.NewValues("brochurePath") = category.BrochurePath
End If
ElseIf BrochureOptions.SelectedValue = "2" Then
' Remove the current brochure (set it to NULL in the database)
e.NewValues("brochurePath") = Nothing
ElseIf BrochureOptions.SelectedValue = "3" Then
' Reference the BrochurePath FileUpload control
Dim BrochureUpload As FileUpload = _
CType(categories.Rows(e.RowIndex).FindControl("BrochureUpload"), _
FileUpload)
' Process the BrochureUpload
Dim cancelOperation As Boolean = False
e.NewValues("brochurePath") = _
ProcessBrochureUpload(BrochureUpload, cancelOperation)
e.Cancel = cancelOperation
Else
' Unknown value!
Throw New ApplicationException( _
String.Format("Invalid BrochureOptions value, {0}", _
BrochureOptions.SelectedValue))
End If
If BrochureOptions.SelectedValue = "2" OrElse _
BrochureOptions.SelectedValue = "3" Then
' "Remember" that we need to delete the old PDF file
If (category.IsBrochurePathNull()) Then
deletedCategorysPdfPath = Nothing
Else
deletedCategorysPdfPath = category.BrochurePath
End If
End If
End Sub
Protected Sub Categories_RowUpdated _
(sender As Object, e As GridViewUpdatedEventArgs) _
Handles Categories.RowUpdated
' If there were no problems and we updated the PDF file,
' then delete the existing one
If e.Exception Is Nothing Then
DeleteRememberedBrochurePath()
End If
End Sub
请注意事件处理程序如何使用 RowUpdating
一系列条件语句根据 BrochureOptions
RadioButtonList 的 SelectedValue
属性值执行相应的操作。
完成此代码后,可以编辑类别并使其使用其当前手册、不使用小册子或上传新小册子。 继续试一试。在 和 RowUpdated
事件处理程序中RowUpdating
设置断点,了解工作流。
步骤 7:上传新图片
Picture
ImageField 的编辑界面呈现为使用其 DataImageUrlField
属性中的值填充的文本框。 在编辑工作流期间,GridView 将参数传递给 ObjectDataSource,参数 s 名称为 ImageField 属性 DataImageUrlField
的值,参数值是输入编辑界面中文本框中的值。 当图像保存为文件系统上的文件并且 DataImageUrlField
包含图像的完整 URL 时,此行为适用。 在这种情况下,编辑界面会在文本框中显示图像 URL,用户可以更改该 URL 并将其保存回数据库。 已授权,此默认接口不允许用户上传新图像,但允许用户将图像的 URL 从当前值更改为另一个值。 但是,对于本教程,ImageField 的默认编辑接口是不够的,因为 Picture
二进制数据直接存储在数据库中, DataImageUrlField
并且 属性只 CategoryID
保留 。
若要更好地了解教程中用户使用 ImageField 编辑行时会发生什么情况,请考虑以下示例:用户编辑具有 CategoryID
10 的行,导致 Picture
ImageField 呈现为值为 10 的文本框。 假设用户将此文本框中的值更改为 50,然后单击“更新”按钮。 发生回发,GridView 最初创建一个名为 CategoryID
的参数,其值为 50。 但是,在 GridView 将此参数 (且 CategoryName
和 Description
参数) 之前,它会从 DataKeys
集合中添加值。 因此,它将用当前行的基础CategoryID
值 10 覆盖 CategoryID
参数。 简言之,ImageField 的编辑界面对本教程的编辑工作流没有影响,因为 ImageField 属性 DataImageUrlField
和网格值 DataKey
的名称是同一个。
虽然 ImageField 可以轻松地基于数据库数据显示图像,但我们不希望在编辑界面中提供文本框。 相反,我们希望提供一个 FileUpload 控件,最终用户可以使用该控件来更改类别图片。 BrochurePath
与 值不同,对于这些教程,我们决定要求每个类别都必须有图片。 因此,我们不需要让用户指示没有关联的图片,用户可以上传新图片或按原样保留当前图片。
若要自定义 ImageField 的编辑界面,需要将其转换为 TemplateField。 在 GridView 智能标记中,单击“编辑列”链接,选择“ImageField”,然后单击“将此字段转换为 TemplateField”链接。
图 16:将 ImageField 转换为 TemplateField
以这种方式将 ImageField 转换为 TemplateField 会生成包含两个模板的 TemplateField。 如以下声明性语法所示, ItemTemplate
包含一个图像 Web 控件,该控件使用基于 ImageField 和 DataImageUrlField
DataImageUrlFormatString
属性的数据绑定语法分配其ImageUrl
属性。 EditItemTemplate
包含一个 TextBox,其 Text
属性绑定到 属性指定的DataImageUrlField
值。
<asp:TemplateField>
<EditItemTemplate>
<asp:TextBox ID="TextBox1" runat="server"
Text='<%# Eval("CategoryID") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Image ID="Image1" runat="server"
ImageUrl='<%# Eval("CategoryID",
"DisplayCategoryPicture.aspx?CategoryID={0}") %>' />
</ItemTemplate>
</asp:TemplateField>
我们需要更新 EditItemTemplate
以使用 FileUpload 控件。 在 GridView 智能标记中,单击“编辑模板”链接, Picture
然后从下拉列表中选择“模板字段 EditItemTemplate
”。 在模板中,应会看到 TextBox 删除此内容。 接下来,将 FileUpload 控件从“工具箱”拖动到模板中,并将其 ID
设置为 PictureUpload
。 另请添加文本 若要更改类别图片,请指定新图片。 若要使类别图片保持不变,请将字段留空到模板中。
图 17:将 FileUpload 控件添加到 EditItemTemplate
(单击以查看全尺寸图像)
自定义编辑界面后,在浏览器中查看进度。 在只读模式下查看行时,类别图像与以前一样显示,但单击“编辑”按钮会将图片列呈现为带有 FileUpload 控件的文本。
图 18:编辑界面包含 FileUpload 控件 (单击以查看全尺寸图像)
回想一下,ObjectDataSource 配置为调用 CategoriesBLL
类 s UpdateCategory
方法,该方法接受将图片的二进制数据作为 Byte
数组作为输入。 但是,如果此数组为 Nothing
,则调用备用 UpdateCategory
重载,这会发出 UPDATE
不修改列的 Picture
SQL 语句,从而使类别的当前图片保持不变。 因此,在 GridView 事件处理程序 RowUpdating
中,我们需要以编程方式引用 PictureUpload
FileUpload 控件,并确定文件是否已上传。 如果未上传一个参数,则 我们 不希望为 picture
参数指定值。 另一方面,如果文件已上传到 PictureUpload
FileUpload 控件中,我们希望确保它是 JPG 文件。 如果是,则可以通过 参数将其二进制内容发送到 ObjectDataSource picture
。
与步骤 6 中使用的代码一样,此处所需的大部分代码已存在于 DetailsView 事件处理程序 ItemInserting
中。 因此,我已将常用功能重构为新方法 ValidPictureUpload
,并更新 ItemInserting
了事件处理程序以使用此方法。
将以下代码添加到 GridView 事件处理程序的 RowUpdating
开头。 此代码必须位于保存小册子文件的代码之前,因为我们不希望在上传无效图片文件时将小册子保存到 Web 服务器的文件系统。
' Reference the PictureUpload FileUpload
Dim PictureUpload As FileUpload = _
CType(categories.Rows(e.RowIndex).FindControl("PictureUpload"), _
FileUpload)
If PictureUpload.HasFile Then
' Make sure the picture upload is valid
If ValidPictureUpload(PictureUpload) Then
e.NewValues("picture") = PictureUpload.FileBytes
Else
' Invalid file upload, cancel update and exit event handler
e.Cancel = True
Exit Sub
End If
End If
方法 ValidPictureUpload(FileUpload)
将 FileUpload 控件作为其唯一的输入参数,并检查上传的文件扩展名以确保上传的文件是 JPG;仅当上传图片文件时才会调用它。 如果未上传任何文件,则不设置图片参数,因此使用其默认值 Nothing
。 如果图片已上传并ValidPictureUpload
返回 ,则picture
为 参数分配已上传图像的二进制数据;如果 方法返回 False
,则取消更新工作流,True
并退出事件处理程序。
从 ValidPictureUpload(FileUpload)
DetailsView 事件处理程序 ItemInserting
重构的方法代码如下所示:
Private Function ValidPictureUpload(ByVal PictureUpload As FileUpload) As Boolean
' 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
Return False
Else
Return True
End If
End Function
步骤 8:将原始类别图片替换为 JPG
回想一下,原始的八个类别图片是包装在 OLE 标头中的位图文件。 添加编辑现有记录图片的功能后,请花点时间将这些位图替换为 JPG。 如果要继续使用当前类别图片,可以通过执行以下步骤将其转换为 JPG:
- 将位图图像保存到硬盘驱动器。 访问浏览器中的页面
UpdatingAndDeleting.aspx
,对于前八个类别中的每一个类别,右键单击图像并选择保存图片。 - 在所选图像编辑器中打开图像。 例如,可以使用 Microsoft 画图。
- 将位图另存为 JPG 图像。
- 使用 JPG 文件通过编辑界面更新类别图片。
编辑类别并上传 JPG 图像后,图像将不会在浏览器中呈现,因为 DisplayCategoryPicture.aspx
页面从前八个类别的图片中删除了前 78 个字节。 通过删除执行 OLE 标头去除的代码来解决此问题。 执行此操作后, DisplayCategoryPicture.aspx``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)
' 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 Sub
注意
页面 UpdatingAndDeleting.aspx
的插入和编辑界面可能需要多一点工作。 CategoryName
DetailsView 和 GridView 中的 和 Description
BoundFields 应转换为 TemplateFields。 由于 CategoryName
不允许 NULL
值,因此应添加 RequiredFieldValidator。 Description
TextBox 可能应转换为多行 TextBox。 我把这些最后的润色留给你作为练习。
总结
本教程介绍了如何使用二进制数据。 在本教程和前面的三个教程中,我们了解了如何将二进制数据存储在文件系统上或直接存储在数据库中。 用户通过从其硬盘驱动器中选择一个文件并将其上传到 Web 服务器(可在其中存储到文件系统或插入到数据库中)来向系统提供二进制数据。 ASP.NET 2.0 包含一个 FileUpload 控件,该控件使提供此类界面变得像拖放一样简单。 但是,如 上传文件 教程中所述,FileUpload 控件仅适用于相对较小的文件上传,理想情况下不超过 1 MB。 我们还探讨了如何将上传的数据与基础数据模型相关联,以及如何编辑和删除现有记录中的二进制数据。
下一组教程将探讨各种缓存技术。 缓存提供了一种提高应用程序整体性能的方法,方法是从昂贵的操作中获取结果,并将其存储在可以更快地访问的位置。
编程愉快!
关于作者
斯科特·米切尔是七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自 1998 年以来一直在使用 Microsoft Web 技术。 Scott 担任独立顾问、培训师和作家。 他的最新一本书是 山姆斯在 24 小时内 ASP.NET 2.0。 可以在 上mitchell@4GuysFromRolla.com联系他,也可以通过他的博客(可在 中找到http://ScottOnWriting.NET)。
特别感谢
本教程系列由许多有用的审阅者审阅。 本教程的首席审阅者是 Teresa Murphy。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处mitchell@4GuysFromRolla.com放置一行。