上载和删除现有的二进制数据 (VB)

作者 :Scott Mitchell

下载 PDF

在前面的教程中,我们了解了 GridView 控件如何使编辑和删除文本数据变得简单。 在本教程中,我们将了解 GridView 控件如何使编辑和删除二进制数据成为可能,无论二进制数据是保存在数据库中还是存储在文件系统中。

简介

在过去的三个教程中,我们添加了大量用于处理二进制数据的功能。 我们首先向表中添加列 BrochurePathCategories 并相应地更新了体系结构。 我们还添加了数据访问层和业务逻辑层方法,用于处理类别表的现有 Picture 列,这些列保存图像文件的二进制内容。 我们已构建网页以在 GridView 中显示二进制数据,并提供了小册子的下载链接,其中类别的图片显示在 元素 <img> 中,并添加了 DetailsView 以允许用户添加新类别并上传其小册子和图片数据。

要实现的只是编辑和删除现有类别的功能,我们将在本教程中使用 GridView 的内置编辑和删除功能完成此操作。 编辑类别时,用户可以选择上传新图片或让类别继续使用现有图片。 对于小册子,他们可以选择使用现有小册子,上传新的小册子,或指示该类别不再具有与之关联的小册子。 让我们开始吧!

步骤 1:更新数据访问层

DAL 具有自动生成的 InsertUpdateDelete 方法,但这些方法是基于CategoriesTableAdapter不包含 列的 main 查询Picture生成的。 因此, InsertUpdate 方法不包括用于指定类别图片的二进制数据的参数。 与 前面的教程中一样,我们需要创建一个新的 TableAdapter 方法,用于在指定二进制数据时更新 Categories 表。

打开“类型化数据集”,然后从Designer右键单击CategoriesTableAdapter标头,然后从上下文菜单中选择“添加查询”以启动 TableAdapter 查询配置向导。 此向导首先询问 TableAdapter 查询应如何访问数据库。 选择“使用 SQL 语句”,然后单击“下一步”。 下一步将提示要生成的查询类型。 由于我们创建了一个查询来向表添加新记录 Categories ,因此请选择“更新”,然后单击“下一步”。

选择“更新”选项

图 1:选择“更新选项” (单击以查看全尺寸图像)

我们现在需要指定 UPDATE SQL 语句。 向导会自动建议一个UPDATE与 TableAdapter main 查询对应的语句, (更新 CategoryNameDescription、 和 BrochurePath 值) 。 更改 语句,以便将 Picture 列与 参数一起包含在内 @Picture ,如下所示:

UPDATE [Categories] SET 
    [CategoryName] = @CategoryName, 
    [Description] = @Description, 
    [BrochurePath] = @BrochurePath ,
    [Picture] = @Picture
WHERE (([CategoryID] = @Original_CategoryID))

向导的最后一个屏幕要求我们命名新的 TableAdapter 方法。 输入 UpdateWithPicture 并单击“完成”。

将 New 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接受 、 DescriptionBrochurePath 值并使用 CategoriesTableAdapter 类自动生成的 Update 语句。 使用两种方法背后的理由是,在某些情况下,用户可能需要更新类别图片及其其他字段,在这种情况下,用户必须上传新图片。 然后,可以在 语句中使用 UPDATE 上传的图片的二进制数据。 在其他情况下,用户可能只对更新名称和说明感兴趣。 但是, UPDATE 如果 语句也需要列的 Picture 二进制数据,那么我们也需要提供该信息。 这需要额外的数据库行程,以恢复正在编辑的记录的图片数据。 因此,我们需要两 UPDATE 种方法。 业务逻辑层将根据更新类别时是否提供图片数据来确定要使用哪一个。

为方便执行此操作,请向 CategoriesBLL 类添加两个方法,这两个方法都名为 UpdateCategory。 第一个应接受三 String 个 、一个 Byte 数组和 一个 Integer 作为其输入参数;第二个应接受三 String 个 和一个 Integer。 输入String参数用于类别的名称、说明和小册子文件路径,Byte数组用于类别图片的二进制内容,标识IntegerCategoryID要更新的记录的 。 请注意,如果传入 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

从 UploadInDetailsView.aspx 复制声明性标记

图 3:从 UploadInDetailsView.aspx (复制声明性标记 单击以查看全尺寸图像)

复制声明性标记和代码后,请访问 UpdatingAndDeleting.aspx。 应会看到与上一教程中的页面相同的输出和相同的用户体验 UploadInDetailsView.aspx

步骤 4:向 ObjectDataSource 和 GridView 添加删除支持

正如我们在 插入、更新和删除数据概述 教程中所述,GridView 提供了内置的删除功能,如果网格的基础数据源支持删除,则可以在复选框的刻度处启用这些功能。 目前,GridView 绑定到的 ObjectDataSource (CategoriesDataSource) 不支持删除。

若要解决此问题,请单击 ObjectDataSource 智能标记中的“配置数据源”选项以启动向导。 第一个屏幕显示 ObjectDataSource 已配置为使用 CategoriesBLL 类。 点击“下一步”。 目前,仅指定 ObjectDataSource 和InsertMethodSelectMethod属性。 但是,向导会分别使用 UpdateCategoryDeleteCategory 方法自动填充“更新”和“删除”选项卡中的下拉列表。 这是因为在 CategoriesBLL 类中,我们使用 作为更新和删除的默认方法标记这些 DataObjectMethodAttribute 方法。

现在,请将“更新”选项卡下拉列表设置为“ (无”) ,但将“删除”选项卡下拉列表设置为 DeleteCategory。 我们将在步骤 6 中返回到此向导,以添加更新支持。

将 ObjectDataSource 配置为使用 DeleteCategory 方法

图 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

在 GridView 中启用对删除的支持

图 5:在 GridView 中启用对删除的支持 (单击以查看全尺寸图像)

花点时间测试删除功能。 表 和 CategoriesCategoryIDCategoryID 之间Products有一个外键,因此,如果尝试删除前八个类别中的任何一个类别,则会出现外键约束冲突异常。 若要测试此功能,请添加新类别,同时提供小册子和图片。 我的测试类别如图 6 所示,包括名为 Test.pdf 的测试手册文件和测试图片。 图 7 显示了添加测试类别后的 GridView。

添加包含小册子和图像的测试类别

图 6:使用宣传册和图像添加测试类别 (单击以查看全尺寸图像)

插入测试类别后,它将显示在 GridView 中

图 7:插入测试类别后,它显示在 GridView 中 (单击以查看全尺寸图像)

在 Visual Studio 中,刷新解决方案资源管理器。 现在应会在 文件夹中看到一个新文件 ~/BrochuresTest.pdf (请参阅图 8) 。

接下来,单击“测试类别”行中的“删除”链接,导致页面回发并 CategoriesBLL 触发类 s DeleteCategory 方法。 这将调用 DAL s Delete 方法,导致将相应的 DELETE 语句发送到数据库。 然后,数据将反弹到 GridView,标记将发送回客户端,但测试类别不再存在。

虽然删除工作流已成功从 Categories 表中删除了测试类别记录,但它没有从 Web 服务器的文件系统中删除其手册文件。 刷新解决方案资源管理器,你将看到Test.pdf它仍然位于 文件夹中~/Brochures

未从 Web 服务器的文件系统中删除 Test.pdf 文件

图 8Test.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

在事件处理程序中 RowDeletingCategoryID 从 GridView 的 集合中抓取要删除的行的 DataKeys ,可以通过 集合在此事件处理程序中访问该 e.Keys 集合。 接下来, CategoriesBLL 调用 类 s GetCategoryByCategoryID(categoryID) 以返回有关要删除的记录的信息。 如果返回 CategoriesDataRow 的对象具有非NULL``BrochurePath 值,则它存储在页变量 deletedCategorysPdfPath 中,以便可以在事件处理程序中删除 RowDeleted 该文件。

注意

或者,我们可以将 添加到 GridView 的 DataKeyNames 属性,并通过 集合访问记录值e.Keys,而不是检索BrochurePath事件处理程序中RowDeleting删除的记录的详细信息CategoriesBrochurePath 这样做会略微增加 GridView 的视图状态大小,但会减少所需的代码量,并保存数据库的行程。

调用 ObjectDataSource 的基础 delete 命令后,GridView 事件处理程序 RowDeleted 将触发。 如果删除数据时没有例外,并且 有 值, deletedCategorysPdfPath则会从文件系统中删除 PDF。 请注意,清理与其图片关联的类别二进制数据不需要此额外代码。 这是因为图片数据直接存储在数据库中,因此删除该 Categories 行也会删除该类别的图片数据。

添加这两个事件处理程序后,请再次运行此测试用例。 删除类别时,也会删除其关联的 PDF。

更新现有记录关联的二进制数据会面临一些有趣的挑战。 本教程的其余部分将深入探讨如何向宣传册和图片添加更新功能。 步骤 6 探讨更新宣传册信息的技术,而步骤 7 则探讨更新图片。

步骤 6:更新类别手册

如插入、更新和删除数据概述教程中所述,GridView 提供内置的行级编辑支持,如果正确配置了其基础数据源,则可以通过复选框的刻度实现。 目前, CategoriesDataSource ObjectDataSource 尚未配置为包含更新支持,因此让我们将其添加到 中。

单击 ObjectDataSource 向导中的“配置数据源”链接,然后继续执行第二个步骤。 由于 在 中使用CategoriesBLLDataObjectMethodAttribute ,因此 UPDATE 下拉列表应自动填充UpdateCategory重载,该重载接受除) (Picture 所有列的四个输入参数。 更改此项,使其使用包含五个参数的重载。

将 ObjectDataSource 配置为使用包含图片参数的 UpdateCategory 方法

图 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,从而为) 编辑的行添加“编辑”按钮 (和“更新”和“取消”按钮。

配置 GridView 以支持编辑

图 10:将 GridView 配置为支持编辑 (单击以查看全尺寸图像)

通过浏览器访问页面,然后单击其中一行的“编辑”按钮。 CategoryNameDescription BoundField 呈现为文本框。 BrochurePath TemplateField 缺少 ,EditItemTemplate因此它继续显示其ItemTemplate与宣传册的链接。 Picture ImageField 呈现为 TextBox,其Text属性被赋予 ImageField 值DataImageUrlField的值,在本例CategoryID中为 。

GridView 缺少宣传册路径的编辑界面

图 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

将三个 ListItems 添加到 RadioButtonList

图 12:向 RadioButtonList 添加 3 ListItem

在 RadioButtonList 下,添加名为 的 BrochureUploadFileUpload 控件。 将其 Visible 属性设置为 False

将 RadioButtonList 和 FileUpload 控件添加到 EditItemTemplate

图 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"] = valueObjectDataSource 参数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 和RowUpdatingRowUpdated事件处理程序使用 ProcessBrochureUploadDeleteRememberedBrochurePath 方法,如以下代码所示:

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 将此参数 (且 CategoryNameDescription 参数) 之前,它会从 DataKeys 集合中添加值。 因此,它将用当前行的基础CategoryID值 10 覆盖 CategoryID 参数。 简言之,ImageField 的编辑界面对本教程的编辑工作流没有影响,因为 ImageField 属性 DataImageUrlField 和网格值 DataKey 的名称是同一个。

虽然 ImageField 可以轻松地基于数据库数据显示图像,但我们不希望在编辑界面中提供文本框。 相反,我们希望提供一个 FileUpload 控件,最终用户可以使用该控件来更改类别图片。 BrochurePath与 值不同,对于这些教程,我们决定要求每个类别都必须有图片。 因此,我们不需要让用户指示没有关联的图片,用户可以上传新图片或按原样保留当前图片。

若要自定义 ImageField 的编辑界面,需要将其转换为 TemplateField。 在 GridView 智能标记中,单击“编辑列”链接,选择“ImageField”,然后单击“将此字段转换为 TemplateField”链接。

将 ImageField 转换为 TemplateField

图 16:将 ImageField 转换为 TemplateField

以这种方式将 ImageField 转换为 TemplateField 会生成包含两个模板的 TemplateField。 如以下声明性语法所示, ItemTemplate 包含一个图像 Web 控件,该控件使用基于 ImageField 和 DataImageUrlFieldDataImageUrlFormatString 属性的数据绑定语法分配其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。 另请添加文本 若要更改类别图片,请指定新图片。 若要使类别图片保持不变,请将字段留空到模板中。

将 FileUpload 控件添加到 EditItemTemplate

图 17:将 FileUpload 控件添加到 EditItemTemplate (单击以查看全尺寸图像)

自定义编辑界面后,在浏览器中查看进度。 在只读模式下查看行时,类别图像与以前一样显示,但单击“编辑”按钮会将图片列呈现为带有 FileUpload 控件的文本。

编辑界面包括 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:

  1. 将位图图像保存到硬盘驱动器。 访问浏览器中的页面 UpdatingAndDeleting.aspx ,对于前八个类别中的每一个类别,右键单击图像并选择保存图片。
  2. 在所选图像编辑器中打开图像。 例如,可以使用 Microsoft 画图。
  3. 将位图另存为 JPG 图像。
  4. 使用 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放置一行。