添加新记录时包含文件上载选项55
简介
在前面的两个教程中 , 我们探讨了用于存储与应用程序的数据模型相关联的二进制数据的方法 , 了解了怎样使用 FileUpload 从客户端向 Web 服务器发送文件 , 以及怎样在Web 数据控件中呈现此二进制数据。但我们还未讨论如何将上载的数据与数据模型相关联。
本教程中 , 我们将创建一个用来添加新类别的网页。除了用于类别的名称和描述的 TextBox ,此网页还需要包含两个 FileUpload 控件 – 一个用于新类别的图片,另一个用于小册子。上载的图片将直接存储在新记录的Picture 列 , 而小册子将保存在 ~/Brochures 文件夹并且文件的路径保存在新记录的BrochurePath 列。
在创建此新网页之前 , 我们需要更新架构。CategoriesTableAdapter 的主查询目前不检索 Picture 列。因此 , 自动生成的Insert 方法只有 CategoryName 、Description 和 BrochurePath 字段的输入。因此 , 我们需要在 TableAdapter 中创建另外一个方法 ,它将 提示要求所有四个 Categories 字段。另外还需要更新业务逻辑层中的 CategoriesBLL 类。
步骤1 : 向CategoriesTableAdapter 添加InsertWithPicture 方法
我们在前面的 创建数据访问层 教程中创建 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 Configuration 向导配置 TableAdapter 之前 ,一直 都很正常。但是,当下次配置向导完成之后 , 我们对INSERT 语句进行的自定义就会被覆盖 ,Insert 方法将会恢复到原来的形式 , 我们的代码将不再编译。
注意 : 当使用存储过程代替 ad-hoc SQL 语句时,这将不再是一个问题。后面的一个教程将探讨在数据访问层使用存储过程代替 ad-hoc SQL 语句。
为避免此潜在的令人头痛的问题 , 我们将不去自定义自动生成的 SQL 语句 , 而是改为为 TableAdapter 创建一个新方法。此方法名称为InsertWithPicture , 将接受 CategoryName 、Description 、BrochurePath 和 Picture 列的值,并执行可以将所有四个值存储到新的记录中的INSERT 语句。
打开 强类型DataSet , 在设计器上右键单击 CategoriesTableAdapter 的标题 , 然后从上下文菜单中选择Add Query 。这会启动 TableAdapter Query Configuration 向导 , 向导首先询问用户 TableAdapter 查询访问数据库的方式。选择“Use SQL statements” , 然后单击 Next 。下一步提示选择要生成的查询类型。由于我们要创建的是向 Categories 表添加新记录的查询,因此选择 “INSERT” ,然后单击 Next 。
.gif)
图1 : 选择 “INSERT” 选项
现在我们需要指定 INSERT SQL 语句。向导自动为用户推荐与 TableAdapter 的主查询相应的 INSERT 语句。在此例中 , 是插入 CategoryName 、Description 和 BrochurePath 的值的 INSERT 语句。对语句进行更新以便包含Picture 列 和@Picture 参数 , 如下所示 :
INSERT INTO [Categories]
([CategoryName], [Description], [BrochurePath], [Picture])
VALUES
(@CategoryName, @Description, @BrochurePath, @Picture)
向导的最后一个屏幕要求我们为新的 TableAdapter 方法命名。输入InsertWithPicture , 然后单击 Finish 。
.gif)
图2 : 将新的 TableAdapter 方法命名为InsertWithPicture
步骤2:更新业务逻辑层
由于表示层应该只与业务逻辑层交互 , 而不是跳过业务逻辑层直接访问数据访问层 , 因此我们需要创建 BLL 方法来调用我们刚才创建的 DAL 方法。对于本教程,在 CategoriesBLL 类中创建名称为 InsertWithPicture 的方法,该方法接受三个字符串和一个字节数组作为输入。字符串输入参数用于类别的名称、描述和小册子文件的路径,而字节数组用于类别图片的二进制内容。如以下代码所示 , 此BLL 方法调用相应的 DAL 方法
[System.ComponentModel.DataObjectMethodAttribute
(System.ComponentModel.DataObjectMethodType.Insert, false)]
public void InsertWithPicture(string categoryName, string description,
string brochurePath, byte[] picture)
{
Adapter.InsertWithPicture(categoryName, description, brochurePath, picture);
}
注意 : 确保在向 BLL 添加InsertWithPicture 方法之前已经保存了 强类型DataSet 。由于 CategoriesTableAdapter 类的代码是根据 强类型DataSet 自动生成的 , 如果没有首先将更改保存到 强类型DataSet ,Adapter 属性将不能识别 InsertWithPicture 方法。
步骤3:列出现有的类别和它们的二进制数据
本教程中 , 我们将创建一个允许最终用户向系统添加新的类别的网页 , 并可以为新的类别提供图片和小册子。在 上一篇教程 中 , 我们使用一个带有 TemplateField 和 ImageField 的 GridView 来显示每种类别的名称、描述、图片和下载其小册子的链接。我们将该功能重用于本教程,创建一个既可以列出所有现有的类别又允许创建新类别的网页。
首先 , 打开BinaryData 文件夹中的 DisplayOrDownload.aspx 页。转到Source 视图并复制 GridView 和ObjectDataSource 的声明语法 ,将其 粘贴到UploadInDetailsView.aspx 中的 <asp:Content> 元素内。此外,不要忘记将 GenerateBrochureLink 方法从 DisplayOrDownload.aspx 的代码文件类复制到 UploadInDetailsView.aspx 。
.gif)
图3 : 从 DisplayOrDownload.aspx 将声明语法复制并粘贴到UploadInDetailsView.aspx
将声明语法和GenerateBrochureLink 方法复制到UploadInDetailsView.aspx 页之后 , 通过浏览器查看该页以确保所需的都已正确复制。应该看到包括用于下载小册子的链接和类别图片的 GridView 列中出了八种类别。
.gif)
图4 : 现在可以看到每种类别和它的二进制数据
步骤4 : 配置CategoriesDataSource 以支持插入
Categories GridView 使用的 CategoriesDataSource ObjectDataSource 目前不提供插入数据的功能。为支持通过此数据源控件插入数据 , 我们需要将它的 Insert 方法映射到它的基础对象中的方法,即CategoriesBLL 。具体来说 , 我们要将它映射到在前面的第 2 步中添加的 CategoriesBLL 方法即InsertWithPicture 。
首先 , 单击ObjectDataSource 的智能标记上的 Configure Data Source 链接。第一个屏幕显示配置的数据源使用的对象 , 即 CategoriesBLL 。使此设置保持原样 , 然后单击Next 进入到 Define Data Methods 屏幕。移动到INSERT 选项卡,并从下拉列表中选择 InsertWithPicture 方法。单击 Finish 完成向导。
.gif)
图5 : 将 ObjectDataSource 配置为使用InsertWithPicture 方法
注意 : 完成向导后 ,Visual Studio 可能询问是否 “Refresh Fields and Keys,” , 这将重新生成 Web 数据控件字段。选择 No ,因为选择 Yes 将覆盖您对字段所做的任何自定义。
完成向导后 ,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 , 然后选中 “Enable Inserting” 复选框。
.gif)
图6 : 将 DetailsView 绑定到 CategoriesDataSource 并启用插入
为使DetailsView 永久呈现为它的插入界面 , 将其 DefaultMode 属性设置为Insert 。
请注意 ,DetailsView 有五个 BoundField – CategoryID 、CategoryName 、Description 、NumberOfProducts 和BrochurePath – 尽管 CategoryID BoundField 未 呈现在插入界面上 , 因为它的 InsertVisible 属性设置为 False 。这些 BoundField 存在,是因为它们是由 GetCategories() 方法返回的列 ,ObjectDataSource 调用GetCategories() 方法来检索它的数据。但是对于插入,我们不想让用户为 NumberOfProducts 指定值。此外,我们需要允许用户为新的类别上载图片和 PDF 小册子。
从DetailsView 中完全删除 NumberOfProducts BoundField , 然后将CategoryName 和 BrochurePath 的 BoundField 的 HeaderText 属性分别更新 “Category” 和 “Brochure” 。接下来 , 将 BrochurePath BoundField 转换为TemplateField 并为图片添加一个新的 TemplateField , 将此新的 TemplateField 的HeaderText 值设置为 “Picture” 。移动 Picture TemplateField 使其位于 BrochurePath TemplateField 和 CommandField 之间。
.gif)
图7 : 将 DetailsView 绑定到 CategoriesDataSource 并启用插入
如果使用 Edit Fields 对话框将 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>
为Brochure 和Picture 字段添加FileUpload 控件
目前 ,BrochurePath TemplateField 的 InsertItemTemplate 包含一个TextBox , 而 Picture TemplateField 不包含任何模板。我们需要更新这两个TemplateField 的 InsertItemTemplates 以便使用FileUpload 控件。
从DetailsView 的智能标记 , 选择 “Edit Templates” 选项 , 然后从下拉菜单中选择 BrochurePath TemplateField 的 InsertItemTemplate 。删除 TextBox , 然后从工具箱将一个 FileUpload 拖放到模板中。将 FileUpload 控件的 ID 设置为 BrochureUpload 。同样地 , 将一个FileUpload 控件添加到 Picture TemplateField 的InsertItemTemplate 。将此 FileUpload 控件的 ID 设置为 PictureUpload 。
.gif)
图8 : 向 InsertItemTemplate 添加一个 FileUpload 控件
进行这些添加之后 , 两个 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 schema 来包括可以捕获文件类型的列,以便通过DisplayCategoryPicture.aspx 中的 Response.ContentType 将此类型发送到客户端。由于我们没有这一列,因此限制用户只提供特定类型的图像文件是有好处的。Categories 表的现有图像类型是位图 , 但是JPG 是在 Web 上提供图像的更合适的文件格式。
如果用户上载了错误类型的文件 , 我们需要取消插入并显示一条说明问题的消息。在DetailsView 下方添加一个 Web 标签控件。将其 ID 属性设置为 UploadWarning , 清除它的 Text 属性 , 将CssClass 属性设置为 “Warning” , 将 Visible 和 EnableViewState 属性都设置为 False 。在 Styles.css 中定义了 Warning CSS 类,并以大号、红色、斜体和加粗字体显示文本。
注意 : 理想情况下 ,CategoryName 和 Description 的 BoundField 将被转换为TemplateField , 并且对它们的插入界面进行自定义。例如 , 多行文本框可能更适合 Description 的插入界面。由于 CategoryName 列不接受 NULL 值 , 必须添加RequiredFieldValidator 以确保用户为新类别的名称提供值。这些步骤留给读者作为练习。有关扩展数据修改界面的更深入的探讨,请参阅前面的 自定义数据修改界面。
步骤6 :将上载的小册子保存到 Web 服务器的文件系统
当用户为新的类别输入值并单击 Insert 按钮时 , 发生回传并且开始插入工作流。首先 , 引发DetailsView 的 ItemInserting 事件。接下来 , 调用ObjectDataSource 的 Insert() 方法 , 这会导致向Categories 表添加一条新的记录。随后 , 引发 DetailsView 的 ItemInserted 事件 。
在调用ObjectDataSource 的 Insert() 方法之前 , 我们首先必须确保用户上载了正确类型的文件 , 然后将小册子PDF 保存到 Web 服务器的文件系统。为DetailsView 的 ItemInserting 事件创建一个 Event Handler 并添加以下代码 :
// Reference the FileUpload control
FileUpload BrochureUpload =
(FileUpload)NewCategory.FindControl("BrochureUpload");
if (BrochureUpload.HasFile)
{
// Make sure that a PDF has been uploaded
if (string.Compare(System.IO.Path.GetExtension
(BrochureUpload.FileName), ".pdf", true) != 0)
{
UploadWarning.Text =
"Only PDF documents may be used for a category's brochure.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
}
Event Handler 首先从 DetailsView 的模板引用 BrochureUpload FileUpload 控件。然后 , 如果已经上载了小册子 , 则检查文件的扩展名。如果扩展名不是“.PDF” , 则显示警告 , 取消插入 ,Event Handler 的执行结束。
注意 : 依靠检查上载文件的扩展名不是确保上载的文件是PDF 文档的可靠方法。用户可能有一个具有 .Brochure 扩展名的有效 PDF 文档,或者有一个非 PDF 文档而为其提供一个 .pdf 扩展名。为了更准确地验证文件的类型,需要通过编码来检查文件的二进制内容。虽然这样的方法很彻底,但通常过于严格;对于大多数情况检查扩展名就足够了。
正如在 上载文件 教程所介绍的 , 将文件保存到文件系统时必须小心 , 以便一个用户的上载不会覆盖另一个用户的上载。对于本教程,我们将尝试使用相同的名称作为上载的文件名。但是,如果在 ~/Brochures 目录中已经有一个文件具有和上载的文件相同的文件名,我们将在文件名的后面添加一个数字,直到为唯一的文件名。例如 , 如果用户上载名称为 Meats.pdf 的小册子文件 , 但是在 ~/Brochures 文件夹中已经有一个名称为Meats.pdf 的文件 , 我们将把保存的文件名更改为 Meats-1.pdf 。如果还存在该文件名的文件,我们将尝试使用 Meats-2.pdf ,依此类推,直到找到一个唯一的文件名。
以下代码使用 File.Exists(path)方法 来确定一个指定文件名的文件是否存在。如果存在,它继续为小册子尝试新的文件名,直到没有冲突时为止。
const string BrochureDirectory = "~/Brochures/";
string brochurePath = BrochureDirectory + BrochureUpload.FileName;
string fileNameWithoutExtension =
System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName);
int iteration = 1;
while (System.IO.File.Exists(Server.MapPath(brochurePath)))
{
brochurePath = string.Concat(BrochureDirectory,
fileNameWithoutExtension, "-", iteration, ".pdf");
iteration++;
}
一旦找到有效的文件名 ,则 需要将文件保存到文件系统 , 并需要更新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 的 ItemInserting 事件中将上载的二进制内容赋值给ObjectDataSource 的 picture 参数。但是,在我们进行此赋值之前,需要首先确保上载的图片是 JPG 格式,而不是其他图像类型。同第 6 步的方法一样,我们根据上载图片的文件扩展名来确定它的类型。
虽然Categories 表允许 Picture 列的值为 NULL , 但是现在所有的类别都有图片。当用户通过此页添加新的类别时,我们强制用户提供题片。下面的代码进行检查确认已上载了图片并且其扩展名正确。
// Reference the FileUpload controls
FileUpload PictureUpload = (FileUpload)NewCategory.FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
// Make sure that a JPG has been uploaded
if (string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpg", true) != 0 &&
string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpeg", true) != 0)
{
UploadWarning.Text =
"Only JPG documents may be used for a category's picture.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
}
else
{
// No picture uploaded!
UploadWarning.Text =
"You must provide a picture for the new category.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
上面的代码应该放在第 6 步的代码之前 , 以便当图片的上载存在问题时 ,Event Handler 在将小册子保存到文件系统之前就会终止。
假定已经上载了正确的文件 , 使用以下的代码行将上载的二进制内容赋值给picture 参数 :
// Set the value of the picture parameter
e.Values["picture"] = PictureUpload.FileBytes;
完整的ItemInserting Event Handler
为了完整性 , 下面给出完整的 ItemInserting Event Handler 代码 :
protected void NewCategory_ItemInserting(object sender, DetailsViewInsertEventArgs e)
{
// Reference the FileUpload controls
FileUpload PictureUpload = (FileUpload)NewCategory.FindControl("PictureUpload");
if (PictureUpload.HasFile)
{
// Make sure that a JPG has been uploaded
if (string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpg", true) != 0 &&
string.Compare(System.IO.Path.GetExtension(PictureUpload.FileName),
".jpeg", true) != 0)
{
UploadWarning.Text =
"Only JPG documents may be used for a category's picture.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
}
else
{
// No picture uploaded!
UploadWarning.Text =
"You must provide a picture for the new category.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
// Set the value of the picture parameter
e.Values["picture"] = PictureUpload.FileBytes;
// Reference the FileUpload controls
FileUpload BrochureUpload =
(FileUpload)NewCategory.FindControl("BrochureUpload");
if (BrochureUpload.HasFile)
{
// Make sure that a PDF has been uploaded
if (string.Compare(System.IO.Path.GetExtension(BrochureUpload.FileName),
".pdf", true) != 0)
{
UploadWarning.Text =
"Only PDF documents may be used for a category's brochure.";
UploadWarning.Visible = true;
e.Cancel = true;
return;
}
const string BrochureDirectory = "~/Brochures/";
string brochurePath = BrochureDirectory + BrochureUpload.FileName;
string fileNameWithoutExtension =
System.IO.Path.GetFileNameWithoutExtension(BrochureUpload.FileName);
int iteration = 1;
while (System.IO.File.Exists(Server.MapPath(brochurePath)))
{
brochurePath = string.Concat(BrochureDirectory, fileNameWithoutExtension,
"-", iteration, ".pdf");
iteration++;
}
// Save the file to disk and set the value of the brochurePath parameter
BrochureUpload.SaveAs(Server.MapPath(brochurePath));
e.Values["brochurePath"] = brochurePath;
}
}
步骤8 : 修复DisplayCategoryPicture.aspx 页
让我们花些时间来测试插入界面和在上面几步中创建的ItemInserting Event Handler 。通过浏览器访问 UploadInDetailsView.aspx 页面,尝试添加一个类别,但或者省略图片,或者指定一个非 JPG 图片或非 PDF 小册子。在上述任何一种情况下,都会显示错误消息并取消插入工作流。
.gif)
图9 : 如果上载的文件类型无效 , 则显示警告消息
一旦验证了该页面请求上载一幅图片 , 并且不接受非PDF 或非 JPG 文件后 ,就 添加一个带有有效 JPG 图片的新类别 , 让Brochure 字段保留为空。单击 Insert 按钮之后,页面将回传,一条新的记录将添加到 Categories 表,并且上载的图像的二进制内容直接存储在数据库中。 GridView 更新后将显示一行新添加的类别,但是,如图 10 所示,新类别的图片并没有正确呈现出来。
.gif)
图10 : 新类别的图片没有显示出来
未显示新图片的原因是 : 返回指定类别的图片的 DisplayCategoryPicture.aspx 页被配置为处理包含 OLE 标头的位图。在将图片发回到客户端之前,会从 Picture 列的二进制内容中去除 78 字节的标头。但是,我们刚才为新类别上载的 JPG 文件并不包括 OLE 标头;这样,就从图像的二进制内容中去除了有效的、必须的字节。
由于 Categories 表中目前既有带有 OLE 标头的位图又有 JPG , 因此我们需要更新DisplayCategoryPicture.aspx , 使其只去除原来八种类别的图像的OLE 标头 , 而对新类别记录则不进行去除。在后面的教程中,我们将介绍怎样更新现有记录的图像,并将原有类别的图片都更新为 JPG 格式。但是 , 现在使用 DisplayCategoryPicture.aspx 页中的下列代码只去除原来八种类别的OLE 标头 :
protected void Page_Load(object sender, EventArgs e)
{
int categoryID = Convert.ToInt32(Request.QueryString["CategoryID"]);
// Get information about the specified category
CategoriesBLL categoryAPI = new CategoriesBLL();
Northwind.CategoriesDataTable categories =
categoryAPI.GetCategoryWithBinaryDataByCategoryID(categoryID);
Northwind.CategoriesRow category = categories[0];
if (categoryID <= 8)
{
// For older categories, we must strip the OLE header... images are bitmaps
// 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 int OleHeaderLength = 78;
int strippedImageLength = category.Picture.Length - OleHeaderLength;
byte[] strippedImageData = new byte[strippedImageLength];
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);
}
}
进行上面的更改后 , 现在GridView 中可以正确呈现 JPG 图像了。
.gif)
图11 : 正确地呈现新类别的 JPG 图像
步骤9 : 出现异常时删除小册子
将二进制数据存储在 Web 服务器的文件系统上存在一个问题 , 这就是数据模型与其二进制数据之间是断开的。因此,每当删除记录时,还必须删除文件系统上相应的二进制数据。插入时同样也是如此。考虑以下情况:用户添加一种新的类别,指定有效的图片和小册子。单击Insert 按钮后 , 出现回传并引发 DetailsView 的 ItemInserting 事件 , 将小册子保存到 Web 服务器的文件系统。接下来 , 调用 ObjectDataSource 的Insert() 方法 , 这将调用 CategoriesBLL 类的 InsertWithPicture 方法 , 然后调用CategoriesTableAdapter 的InsertWithPicture 方法。
此时 , 如果数据库脱机或者 INSERT SQL 语句中存在错误 , 将会怎样 ? 显然 ,INSERT 将失败 , 因此没有将新类别行添加到数据库。但是,上载的小册子文件仍然位于 Web 服务器的文件系统上!在插入工作流期间如果出现异常需要删除此文件。
正如在前面的 在 ASP.NET 页面中处理 BLL 与 DAL 级别的异常 教程中介绍的 , 当在架构的深处抛出异常时 , 异常通过各层冒泡向上传递。在表示层 , 我们可以从 DetailsView 的 ItemInsert 事件确定是否出现了异常。此Event Handler 还提供 ObjectDataSource 的 InsertParameters 的值。因此 , 我们可以为 ItemInserted 事件创建一个Event Handler , 检查是否出现异常 , 如果出现 , 则删除ObjectDataSource 的brochurePath 参数指定的文件 :
protected void NewCategory_ItemInserted
(object sender, DetailsViewInsertedEventArgs e)
{
if (e.Exception != null)
{
// Need to delete brochure file, if it exists
if (e.Values["brochurePath"] != null)
System.IO.File.Delete(Server.MapPath(
e.Values["brochurePath"].ToString()));
}
}
小结
为了提供用来添加包含二进制数据的记录的基于Web 的界面 , 必须执行许多步骤。如果二进制数据直接存储在数据库中,可能需要更新架构,添加指定的方法来处理插入二进制数据的情况。一旦更新了架构 , 下一步是创建插入界面 , 这可以通过使用经过自定义的、每种二进制数据字段都包括FileUpload 控件的 DetailsView 来完成。然后 , 上载的数据可以保存到 Web 服务器的文件系统或赋值给 DetailsView 的 ItemInserting Event Handler 中的数据源参数。
将二进制数据保存到文件系统比直接保存到数据库中需要更多的规划。为了避免一个用户的上载覆盖另一个用户的上载,必须选择命名方案。此外,如果数据库插入失败,则必须采取额外的步骤来删除已上载的文件。
现在我们可以向系统添加带有小册子和图片的新类别了 , 但是我们还未了解怎样更新已有类别的二进制数据 , 以及怎样正确删除某个已删除类别的二进制数据。在后面的教程中,我们将介绍这两方面的主题。
快乐编程!

浙公网安备 33010602011771号