添加新纪录时增加文件上载选项 (VB)

作者 :斯科特·米切尔

下载 PDF

本教程演示如何创建一个 Web 界面,允许用户输入文本数据和上传二进制文件。 为了说明可用于存储二进制数据的选项,一个文件将保存在数据库中,另一个文件存储在文件系统中。

介绍

在前面的两个教程中,我们探讨了存储与应用程序数据模型关联的二进制数据的技术,并介绍了如何使用 FileUpload 控件将文件从客户端发送到 Web 服务器,以及如何在数据 Web 控件中显示此二进制数据。 不过,我们尚未讨论如何将上传的数据与数据模型相关联。

在本教程中,我们将创建一个网页来添加新类别。 除了类别名称和说明的 TextBoxes 外,此页面还需要包含两个 FileUpload 控件,一个用于新类别图片,另一个用于小册子。 上传的图片将直接存储在新记录的 Picture 列中,而小册子将保存到 ~/Brochures 文件夹中,其中包含新记录列中 BrochurePath 保存的文件的路径。

在创建新网页之前,需要更新体系结构。 主 CategoriesTableAdapter 查询不会检索 Picture 列。 因此,自动生成Insert的方法仅包含CategoryNameDescriptionBrochurePath字段的输入。 因此,我们需要在 TableAdapter 中创建一个附加方法,以提示输入所有四 Categories 个字段。 业务逻辑层中的CategoriesBLL类也需要更新。

步骤 1:将方法InsertWithPicture添加到CategoriesTableAdapter

CategoriesTableAdapter”教程中创建后,我们将其配置为基于主查询自动生成INSERTUPDATEDELETE语句。 此外,我们指示 TableAdapter 使用 DB Direct 方法,这创建了方法 InsertUpdateDelete。 这些方法执行自动生成的语句 INSERTUPDATEDELETE,因此,根据主查询返回的列来接受输入参数。 在“上传文件”教程中,我们对CategoriesTableAdapter的主要查询进行了增强,以使用BrochurePath列。

由于CategoriesTableAdapter的主查询不引用Picture列,因此既不能添加新记录,也不能更新现有记录使用Picture列的值。 为了捕获此信息,我们可以在 TableAdapter 中创建一个新方法,该方法专门用于插入包含二进制数据的记录,也可以自定义自动生成的 INSERT 语句。 自定义自动生成的 INSERT 语句的问题在于,我们自定义的内容可能会被向导覆盖。 例如,假设我们自定义了 INSERT 语句,以便包含使用 Picture 列。 这将更新 TableAdapter 的 Insert 方法,以增加一个类别图片的二进制数据输入参数。 然后,我们可以在业务逻辑层中创建一个方法,以使用此 DAL 方法并通过呈现层调用此 BLL 方法,并且一切都会非常出色。 也就是说,直到我们下次通过 TableAdapter 配置向导再次配置 TableAdapter。 向导完成后,我们对 INSERT 语句的自定义将被覆盖,Insert 方法将恢复为旧形式,并且代码将无法编译!

注释

使用存储过程而不是即席 SQL 语句时,这种烦恼将不再成为问题。 将来的教程将探讨如何使用存储过程代替数据访问层中的临时 SQL 语句。

为了避免这种潜在的头痛,而不是自定义自动生成的 SQL 语句,让我们改为为 TableAdapter 创建新的方法。 此方法(命名InsertWithPicture)将接受值CategoryNameDescriptionBrochurePath列和Picture列,并执行一个语句,该语句将所有四个INSERT值存储在新记录中。

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

选择 INSERT 选项

图 1:选择 INSERT 选项(单击以查看全尺寸图像

现在需要指定 INSERT SQL 语句。 向导会自动建议与 INSERT TableAdapter 主查询对应的语句。 在本例中,它是一个INSERT插入CategoryNameDescriptionBrochurePath值的语句。 更新语句,使 Picture 列与 @Picture 参数一起包含,如下所示:

INSERT INTO [Categories] 
    ([CategoryName], [Description], [BrochurePath], [Picture]) 
VALUES 
    (@CategoryName, @Description, @BrochurePath, @Picture)

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

将 New 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

将声明性语法从DisplayOrDownload.aspx复制并粘贴到UploadInDetailsView.aspx

图 3:复制并粘贴声明性语法(DisplayOrDownload.aspxUploadInDetailsView.aspx单击以查看全尺寸图像

将声明性语法和 GenerateBrochureLink 方法复制到 UploadInDetailsView.aspx 页面后,通过浏览器查看页面,以确保正确复制所有内容。 您应该会看到一个 GridView,上面列出八个类别,并包括下载小册子的链接,以及类别的图片。

现在应会看到每个类别及其二进制数据

图 4:现在应看到每个类别及其二进制数据(单击以查看全尺寸图像

步骤 4:配置CategoriesDataSource以支持插入

CategoriesDataSource GridView 使用的 Categories ObjectDataSource 当前不提供插入数据的功能。 为了支持通过此数据源控件插入,我们需要将其方法映射到其 Insert 基础对象 CategoriesBLL中的方法。 具体而言,我们希望将其映射到 CategoriesBLL 方法,这个方法是我们早在步骤 2 中添加的。

首先单击 ObjectDataSource 智能标记中的“配置数据源”链接。 第一个屏幕显示数据源配置为使用的对象。 CategoriesBLL 保留此设置 as-is 并单击“下一步”转到“定义数据方法”屏幕。 移动到 INSERT 选项卡,然后从下拉列表中选择 InsertWithPicture 方法。 单击“完成”以完成向导。

将 ObjectDataSource 配置为使用 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,并清除HeightWidth属性值。 从 DetailsView 智能标记中,将其绑定到现有 CategoriesDataSource 标记,然后选中“启用插入”复选框。

DetailsView 的屏幕截图,其中 CategoryID 属性设置为 NewCategory、Height 和 Width 属性值为空,并且选中了“启用插入”复选框。

图 6:将 DetailsView 绑定到 CategoriesDataSource,并启用插入功能(单击以查看全尺寸图像

若要在其插入接口中永久呈现 DetailsView,请将其 DefaultMode 属性设置为 Insert

请注意,DetailsView 具有五个 BoundFields CategoryIDCategoryNameDescriptionNumberOfProductsBrochurePath,尽管 CategoryID BoundField 未在插入接口中呈现,因为其 InsertVisible 属性设置为 False。 这些 BoundFields 之所以存在,是因为它们是GetCategories()方法返回的列,而这些列是 ObjectDataSource 调用以检索数据的。 对于插入,我们不希望用户指定一个 NumberOfProducts 值。 此外,我们需要允许他们上传新类别的图片,以及为小册子上传 PDF。

从 DetailsView 中删除NumberOfProducts BoundField,然后分别将HeaderTextCategoryName BoundFields 的BrochurePath属性更新为 Category 和 Brochure。 接下来,将 BrochurePath BoundField 转换为 TemplateField,并添加一个用于图片的新 TemplateField,将其值设为 HeaderText 图片。 将 Picture TemplateField 移动到 BrochurePath TemplateField 和 CommandField 之间。

显示字段窗口的屏幕截图,其中突出显示了 TemplateField、Picture 和 HeaderText。

图 7:将 DetailsView 绑定到 CategoriesDataSource 并启用插入

如果通过“编辑字段”对话框将 BrochurePath BoundField 转换为 TemplateField,则 TemplateField 包括ItemTemplateEditItemTemplateInsertItemTemplate。 只需要 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 的 BrochurePathInsertItemTemplate。 删除 TextBox,然后将 FileUpload 控件从工具箱拖动到模板中。 将 FileUpload 控件 ID 设置为 BrochureUpload. 同样,向 TemplateField s Picture添加 FileUpload 控件InsertItemTemplate。 将此 FileUpload 控件 ID 设置为 PictureUpload.

将 FileUpload 控件添加到 InsertItemTemplate

图 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.ContentTypeDisplayCategoryPicture.aspx中发送到客户端。 由于我们没有此类列,因此应谨慎地限制用户仅提供特定的图像文件类型。 Categories表的现有图像是位图,但 JPG 是 Web 上提供的图像更合适的文件格式。

如果用户上传了不正确的文件类型,则需要取消插入并显示指示问题的消息。 在 DetailsView 下添加标签 Web 控件。 将其ID属性设置为UploadWarning,清除其Text属性,将CssClass属性设置为“警告”,并将VisibleEnableViewState属性都设置为FalseWarning CSS 类定义在 Styles.css 中,并以大号红色斜体粗体字体呈现文本。

注释

理想情况下,CategoryNameDescription 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 pictureItemInserting 参数。 但是,在进行此分配之前,首先需要确保上传的图片是 JPG,而不是其他一些图像类型。 与步骤 6 中一样,让我们使用上传的图片文件扩展名来确定其类型。

Categories虽然该表允许NULLPicture的值,但所有类别当前都有图片。 让我们强制用户在通过此页面添加新类别时提供图片。 以下代码检查以确保图片已上传,并且具有适当的扩展。

' 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 中正确呈现。

新类别的 JPG 图像已正确呈现

图 11:正确呈现新类别的 JPG 图像(单击以查看全尺寸图像

步骤 9:在遇到异常时删除小册子

在 Web 服务器的文件系统上存储二进制数据的挑战之一是,它造成了数据模型与其二进制数据之间的断开。 因此,每当删除记录时,还必须删除文件系统上的相应二进制数据。 插入时可能涉及此问题。 请考虑以下方案:用户添加新类别,并指定有效的图片和小册子。 单击“插入”按钮时,会发生回发,并触发 DetailsView 的 ItemInserting 事件,将小册子保存到 Web 服务器的文件系统中。 接下来,调用 ObjectDataSource 的 Insert() 方法,该方法调用 CategoriesBLL 类的 InsertWithPicture 方法,而该方法调用 CategoriesTableAdapterInsertWithPicture 方法。

现在,如果数据库处于脱机状态,或者 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