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

作者 :Scott Mitchell

下载 PDF

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

简介

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

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

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

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

在创建CategoriesTableAdapter数据访问层教程中创建 返回时,我们将其配置为基于main查询自动生成 INSERTUPDATEDELETE 语句。 此外,我们指示 TableAdapter 使用 DB Direct 方法,该方法创建了方法 InsertUpdateDelete。 这些方法执行自动生成INSERT的 、 UPDATEDELETE 语句,因此,根据main查询返回的列接受输入参数。 在上传文件教程中,CategoriesTableAdapter我们扩充了 main 查询以使用 BrochurePath 列。

CategoriesTableAdapter由于 main 查询不引用列Picture,因此既不能添加新记录,也不能使用列的值Picture更新现有记录。 为了捕获此信息,我们可以在 TableAdapter 中创建一个专门用于插入包含二进制数据的记录的新方法,也可以自定义自动生成 INSERT 的语句。 自定义自动生成 INSERT 的语句的问题在于,我们冒着向导覆盖自定义项的风险。 例如,假设我们自定义 了 INSERT 语句以包含列的使用 Picture 。 这会更新 TableAdapter 方法 Insert ,以包含类别图片的二进制数据的其他输入参数。 然后,我们可以在业务逻辑层中创建一个方法来使用此 DAL 方法,并通过表示层调用此 BLL 方法,一切都将出色地工作。 也就是说,直到我们下次通过 TableAdapter 配置向导配置 TableAdapter 为止。 向导完成后,语句的INSERT自定义项将被覆盖,Insert方法将还原为其旧格式,并且代码将不再编译!

注意

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

为了避免这种潜在的麻烦,与其自定义自动生成的 SQL 语句,不如为 TableAdapter 创建新方法。 此方法名为 InsertWithPicture,将接受 、DescriptionBrochurePathPicture 列的值CategoryName,并执行将所有四个INSERT值存储在新记录中的语句。

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

选择 INSERT 选项

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

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

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

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

将 New TableAdapter 方法命名为 InsertWithPicture

图 2:将 New TableAdapter 方法 InsertWithPicture 命名为 (单击 以查看全尺寸图像)

步骤 2:更新业务逻辑层

由于表示层应仅与业务逻辑层进行交互,而不是绕过它直接转到数据访问层,因此我们需要创建一个 BLL 方法,以调用刚刚创建的 DAL 方法 (InsertWithPicture) 。 在本教程中,在名为 InsertWithPictureCategoriesBLL类中创建一个接受作为输入 3 StringByte数组的方法。 输入 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

注意

在将 方法添加到 BLL 之前, InsertWithPicture 请确保已保存类型化数据集。 CategoriesTableAdapter由于类代码是基于类型化数据集自动生成的,因此如果不首先保存对类型化数据集所做的更改,属性Adapter将不知道InsertWithPicture方法。

步骤 3:列出现有类别及其二进制数据

在本教程中,我们将创建一个页面,允许最终用户向系统添加新类别,为新类别提供图片和宣传册。 在 前面的教程中 ,我们使用 GridView 和 TemplateField 和 ImageField 来显示每个类别的名称、说明、图片和下载其手册的链接。 让我们复制本教程的该功能,创建一个既列出所有现有类别又允许创建新类别的页面。

首先, DisplayOrDownload.aspxBinaryData 文件夹中打开页面。 转到“源”视图,复制 GridView 和 ObjectDataSource 的声明性语法,将其粘贴到 <asp:Content> 中的 元素中 UploadInDetailsView.aspx。 此外,不要忘记将 方法从 的代码隐藏类DisplayOrDownload.aspxUploadInDetailsView.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 中添加的 方法。 InsertWithPicture

首先,单击 ObjectDataSource 智能标记中的“配置数据源”链接。 第一个屏幕显示数据源配置为使用 CategoriesBLL的对象 。 将此设置保留为原样,然后单击“下一步”转到“定义数据方法”屏幕。 移动到“插入”选项卡,然后从下拉列表中选择 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 上方的Designer,将其ID属性设置为 NewCategory 并清除 HeightWidth 属性值。 在 DetailsView 的智能标记中,将其绑定到现有CategoriesDataSource,然后检查“启用插入”复选框。

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

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

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

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

NumberOfProducts完全从 DetailsView 中删除 BoundField,然后将 和 BrochurePath BoundFields 的属性CategoryName分别更新HeaderText为 Category 和 Brochure。 接下来,将 BrochurePath BoundField 转换为 TemplateField,并为图片添加新的 TemplateField,为此新 TemplateField 提供 HeaderText 图片值。 移动 Picture TemplateField,使其位于 TemplateField 和 CommandField 之间 BrochurePath

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

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

如果通过“编辑字段”对话框将 BoundField 转换为 BrochurePath 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 不包含任何模板。 我们需要更新这两个 TemplateField s InsertItemTemplate 以使用 FileUpload 控件。

在“详细信息”“查看”智能标记中,选择“编辑模板”选项,然后从下拉列表中选择“ BrochurePath TemplateField InsertItemTemplate ”。 删除 TextBox,然后将 FileUpload 控件从工具箱拖到模板中。 将 FileUpload 控件 ID 设置为 BrochureUpload。 同样,将 FileUpload 控件添加到 Picture TemplateField s 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架构以包括一个捕获文件类型的列,以便可以通过 在 中DisplayCategoryPicture.aspx将此类型发送到客户端Response.ContentType。 由于我们没有此类列,因此最好将用户限制为仅提供特定图像文件类型。 表 Categories 的现有图像是位图,但 JPG 是更适合通过 Web 提供的图像的文件格式。

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

注意

理想情况下, CategoryNameDescription BoundFields 将转换为 TemplateFields 及其自定义的插入接口。 Description例如,插入界面可能更适合通过多行文本框。 由于列 CategoryName 不接受 NULL 值,因此应添加 RequiredFieldValidator 以确保用户为新类别名称提供值。 这些步骤作为练习留给读者。 有关扩充 数据修改接口 的详细信息,请参阅自定义数据修改接口。

步骤 6:将上传的宣传册保存到 Web 服务器的文件系统

当用户输入新类别的值并单击“插入”按钮时,将发生回发并展开插入工作流。 首先,将触发 DetailsView 事件ItemInserting。 接下来,调用 ObjectDataSource s Insert() 方法,这会导致将新记录添加到 Categories 表中。 之后,将触发 DetailsView 事件ItemInserted

在调用 ObjectDataSource 方法 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 文档的一种肯定方法。 用户可以拥有扩展名为 的有效 PDF 文档 .Brochure,或者可以获取非 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 参数 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 s picture 事件中的 ObjectDataSource 参数 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 中的代码 之前 ,以便在上传图片时出现问题,事件处理程序将在将手册文件保存到文件系统之前终止。

假设已上传适当的文件,请使用以下代码行将上传的二进制内容分配给 picture 参数值:

' 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:新类别的图片未显示 (单击以查看全尺寸图像)

未显示 DisplayCategoryPicture.aspx 新图片的原因是,返回指定类别的图片的页面配置为处理具有 OLE 标头的位图。 此 78 字节标头在发送回客户端之前从 Picture 列的二进制内容中去除。 但是我们刚刚为新类别上传的 JPG 文件没有此 OLE 标头;因此,将从映像的二进制数据中删除有效的必要字节。

由于表中现在有包含 OLE 标头和 JPG Categories 的位图,因此我们需要更新 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 类 s InsertWithPicture 方法,后者调用 CategoriesTableAdapter s InsertWithPicture 方法。

现在,如果数据库处于脱机状态,或者 SQL 语句中 INSERT 存在错误,会发生什么情况? 显然,INSERT 会失败,因此不会向数据库添加新的类别行。 但是,我们仍然将上传的宣传册文件放在 Web 服务器的文件系统上! 在插入工作流期间遇到异常时,需要删除此文件。

如前面在 ASP.NET 页中处理 BLL 和 DAL-Level 异常教程中所述 ,当从体系结构的深度引发异常时,它会在各个层中浮升。 在表示层,我们可以确定 DetailsView s ItemInserted 事件是否发生了异常。 此事件处理程序还提供 ObjectDataSource s 的值 InsertParameters。 因此,我们可以为 ItemInserted 事件创建事件处理程序,用于检查是否存在异常,如果是,则删除 ObjectDataSource 参数 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 技术。 Scott 担任独立顾问、培训师和作家。 他的最新一本书是 山姆斯在 24 小时内 ASP.NET 2.0。 可以在 上mitchell@4GuysFromRolla.com联系他,也可以通过他的博客(可在 中找到http://ScottOnWriting.NET)。

特别感谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是 Dave Gardner、Teresa Murphy 和 Bernadette Leigh。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处mitchell@4GuysFromRolla.com放置一行。