1、56添加新记录时包含一个文件上传选项在前面2节教程,我们探讨了如何使用FileUpload控件从客户端向服务器上传文件,以及如何在数据Web控件里显示二进制数据。在本节,我们将创建一个web页面以添加新的种类。除了为类的name和description属性添加TextBoxes控件外,我们还要在页面上添加2个FileUpload控件一个用来上传新类的图片,另一个用来上传类的小说明册子。上传的图片将直接存储在新记录的Picture列。与此相反,小册子将存储在/Brochures 文件夹,同时将文件路径存储在新记录的BrochurePath列。在创建页面之前,我们需要更新体系结构。由于Catego
2、riesTableAdapter的主查询并不返回Picture列,因此自动生产的Insert方法只包含了CategoryName, Description和BrochurePath列。我们需要在TableAdapter里创建新的方法以包括Categories的4个列。同时业务逻辑层的的CategoriesBLL类也需要更新。第1步:在CategoriesTableAdapter添加一个InsertWithPicture方法在前面的教程创建一个数据访问层里我们创建了CategoriesTableAdapter,并设置其自动生成了基于主查询的INSERT, UPDATE和DELETE命令。此外,我
3、们设置该TableAdapter启用DB Direct方法,它将创建Insert, Update和Delete方法。这些方法执行自动生成的INSERT, UPDATE和DELETE命令,自然而然的,其接受的输入参数基于主查询所返回的那些列。在教程上传文件里,我们扩展了 CategoriesTableAdapter的主查询以包含BrochurePath列。因为CategoriesTableAdapter的主查询并为引用Picture,在添加新记录或更新记录时不能涉及Picture值。为了获取Picture信息,我们要么在TableAdapter里创建一个新方法以插入Picture的二进制数据;要
4、么定制自动生成的INSERT命令。但定制自动生成的INSERT命令有一个风险,即定制的INSERT命令有可能被向导覆盖。比如,假设我们定制INSERT命令使用Picture列,更新TableAdapter的Insert方法,使之多包含一个对应picture二进制数据的参数。然后在业务逻辑层创建一个方法使用该 DAL方法,再在表现层调用该业务逻辑层方法。现在一切工作正常,但当下一次在TableAdapter设置向导里设置TableAdapter完成后,我们定制的INSERT命令马上就会被向导重写,回归到定制前的状态。其结果是我们的代码将无法编译!注意: 如果使用存储过程而不用SQL语句的话,就不
5、存在这个问题。在以后的教程里,我们将探讨在数据访问层用存储过程替代SQL语句。为避免这个头痛的问题,我们为TableAdapter添加新的方法,而不定制自动生成的SQL命令。我们为添加的方法命名为InsertWithPicture,它接受 CategoryName, Description, BrochurePath和Picture值;执行INSERT命令将上述值添加进一条记录。在CategoriesTableAdapter的顶部点右键,选择“添加查询”。进入TableAdapter 查询设置向导,首先询问我们TableAdapter查询如何访问数据库,选择“使用SQL语句”,点Next,因为
6、我们要为表Categories添加新记录,选“INSERT”,点Next。图1:选“INSERT”选项现在,我们需要指定INSERT SQL语句。向导自动地生成一个基于主查询的INSERT语句。此时,它只插入CategoryName, Description和BrochurePath值。对其更新,包括Picture列和参数Picture ,如下: INSERT INTO Categories (CategoryName, Description, BrochurePath, Picture) VALUES (CategoryName, Description, BrochurePath, Pi
7、cture)最后,向导要我们为方法命名,取名为InsertWithPicture,点Finish。图2:为新方法命名为InsertWithPicture 第2步:更新业务逻辑层由于一般来说表现层将引用业务逻辑层,而不是绕过它直接引用数据访问层,我们需要创建一个业务逻辑层方法,以调用刚才创建的数据访问层方法(InsertWithPicture),本节,我们在CategoriesBLL里创建一个名为InsertWithPicture方法,它接受3个字符串和一个byte数组,字符串参数对应name, description和brochure文件地址;byte数组对应于图片的二进制内容。就像下面的代码
8、所显示的那样,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
9、, picture);注意: 在为BLL添加InsertWithPicture方法前,确保已经保存了数据集(Typed DataSet ),因为CategoriesTableAdapter类的代码是基于Typed DataSet自动生成的。如果最开始没有把对Typed DataSet所进行的修改保存的话,Adapter属性将不认同InsertWithPicture方法。第3步:列出现有的种类及其二进制数据本教程我们将创建一个页面,允许用户添加新的类,包含其图片和说明小册子。在上一节,我们用一个包含TemplateField和ImageField的GridView控件来展示每个类的名称、描述,并
10、包含一个下载说明小册子的链接。在本教程,我们实现相同的功能,创建一个页面,即展示现有的类,还可以添加新的类。打开BinaryData文件夹的DisplayOrDownload.aspx页面,切换到源模式,复制GridView和ObjectDataSource控件的声明代码,粘贴在UploadInDetailsView.aspx页面的元素里。同时不要忘记将后台代码类的GenerateBrochureLink方法拷贝到UploadInDetailsView.aspx的后台代码类里。图3:将DisplayOrDownload.aspx页面的声明代码拷贝到页面UploadInDetailsView.a
11、spx 完成以后,在浏览器里查看该页面,确保一切正常。GridView控件里列出了8个类,每个类包含一张图片以及一个下载说明小册子的链接。图4:你应该看到每个类及其相应二进制数据第4步:设置CategoriesDataSource以支持添加功能那个ID为Categories的GridView控件所使用的名为CategoriesDataSource的 ObjectDataSource控件目前还不支持添加数据。为实现该功能,我们要设置该控件的Insert方法引用类CategoriesBLL的某个方法。具体的讲,我们要用到在第2步里添加的InsertWithPicture方法。在ObjectData
12、Source控件的智能标签里,点“设置数据源”。照原样一直点到“Define Data Methods”界面。再点INSERT标签,从下拉列表里选方法“InsertWithPicture”,点Finish完成设置。图5:设置ObjectDataSource控件使用InsertWithPicture方法注意: 当完成设置后,Visual Studio会问你是否“刷新Fields and Keys”,选择No,因为如果选Yes的话,将重新构造data Web controls fields,那样将重写所有我们已经定制好的列(field)。完成设置后,ObjectDataSource控件将会为Ins
13、ertMethod属性赋值,同时包含一个,如下面的声明代码所示: 第5步:创建一个插入界面在教程16概述插入、更新和删除数据里我们谈到,当DetailsView控件的数据源控件支持添加功能时,便可以启用DetailsView内置的添加界面。让我们在页面上添加一个DetailsView控件,置于GridView控件之上,并处于添加模式。当在DetailsView控件里添加一个新种类时,其下的GridView控件将自动发生刷新,并将刚添加的类显示出来。从工具箱拖一个DetailsView控件到页面,置于GridView之上,设其ID为NewCategory,清空其Height和Width属性。 其
14、智能标签里,设置它绑定到名为CategoriesDataSource的数据源,并启用“插入”功能。图6:将DetailsView控件绑定到CategoriesDataSource,并启用插入功能。 为使DetailsView呈现为插入界面,设其DefaultMode属性为Insert我们注意到,尽管DetailsView控件有5个BoundFieldsCategoryID, CategoryName, Description, NumberOfProducts和BrochurePath,但插入界面并不包含CategoryID,因为CategoryID列的InsertVisible属性为fals
15、e。为什么会显示这4个列呢?因为ObjectDataSource调用的GetCategories()方法返回的就是这些列。当添加新类时,我们不希望用户为NumberOfProducts列指定值,此外,我们还希望让用户为新类上传图片和相关的PDF小册子。在DetailsView里将NumberOfProducts列完成删除,再分别CategoryName列和BrochurePath列的HeaderText属性设置为“Category”和“Brochure”。将BrochurePath 转换为TemplateField,再添加一个TemplateField,设其HeaderText属性为“Pict
16、ure”,把它放置在BrochurePath列和CommandField列之间。图7:将DetailsView控件绑定到CategoriesDataSource,并启用插入功能(注:图片说明有误)当你在“编辑列”对话框里将BrochurePath BoundField 转换为一个TemplateField后,该TemplateField将包含3个模板:ItemTemplate,EditItemTemplate和InsertItemTemplate,由于我们只需要InsertItemTemplate模板,将另外2个模板删除。如此,你的DetailsView控件的声明代码看起来应该像下面的这样:
17、asp:TextBox ID=TextBox1 runat=server Text= 为Brochure和Picture Fields添加FileUpload控件当前,BrochurePath TemplateField的InsertItemTemplate模板包含一个TextBox,而Picture TemplateField并不包含任何的模板,我们为这2个TemplateField的InsertItemTemplate模板模板添加FileUpload控件。从DetailsView控件的智能标签选择“编辑模板”,从下拉列表选择BrochurePath TemplateField的Insert
18、ItemTemplate模板,将模板里的TextBox删除,从工具箱拖一个FileUpload控件到页面,设其ID为BrochureUpload。类似的,为Picture TemplateField的InsertItemTemplate模板添加一个ID为PictureUpload的FileUpload控件。图8:在InsertItemTemplate模板里添加一个FileUpload控件完成添加后,这2个TemplateField的声明代码应该和下面的差不多: 当用户添加一个新类时,我们希望确保上传的图片和说明小册子是恰当的文件类型。对说明小册子,必须是PDF类型;对图片,我们需要用户上传一个
19、image文件。那是不是image文件必须是某个特定的类型呢,比如GIF或JPG?考虑到其它不同类型的文件,我们需要扩展表Categories的列以包含这些类型的文件,同时我们可以在页面DisplayCategoryPicture.aspx里通过Response.ContentType将这些文件发送到客户端。由于表Categories现在并没有这样的列,我们只有限制用户上传指定为某种类型的image文件。表Categories里现有的images为位图,不过使用JPG类型或许更恰当。当用户上传的文件类型不正确时,我们将取消插入操作,并显示一个提示信息。在DetailsView控件下添加一个La
20、bel Web控件,设ID为UploadWarning,清除Text属性,设CssClass属性为“Warning”, 再将Visible和EnableViewState属性都设为false。Warning CSS定义在Styles.css里,作用是将文字显示为粗斜体,红色大号字。注意: 最理想的情况是将CategoryName和Description BoundFields都转换为TemplateFields,达到定制插入界面的目的。比如,对Description插入界面来说,使用一个允许分行的文本框或许更好;对CategoryName插入界面,因为CategoryName不允许为NULL值
21、,我们应该添加一个RequiredFieldValidator控件,以确保输入类的名称。这些步骤都留给读者做练习,更深入的探讨请参考前面的教程之20定制数据修改界面第6步:将上传的小册子保存在服务器的文件系统但用户键入相关的类别信息,点Insert按钮后,发生页面回传,接着发生一连串的插入流程。首先,DetailsView控件的ItemInserting event事件发生;接着,调用ObjectDataSource控件的Insert()方法,它将导致Categories表添加新记录;最后,发生DetailsView控件的ItemInserted event事件。在调用ObjectDataSo
22、urce控件的Insert()方法以前,我们必须确保用户已经上传了恰当的文件并保存在服务器的文件系统。为此,我们为DetailsView控件的ItemInserting事件创建一个事件处理器,添加如下的代码:/ Reference the FileUpload controlFileUpload BrochureUpload = (FileUpload)NewCategory.FindControl(BrochureUpload);if (BrochureUpload.HasFile) / Make sure that a PDF has been uploaded if (string.Co
23、mpare(System.IO.Path.GetExtension (BrochureUpload.FileName), .pdf, true) != 0) UploadWarning.Text = Only PDF documents may be used for a categorys brochure.; UploadWarning.Visible = true; e.Cancel = true; return; 代码首先引用DetailsView控件模板里名为BrochureUpload的FileUpload控件,如果已经上传了文件,就检查FileUpload控件的extension
24、是否为“.PDF”, 如果不是则取消插入操作并退出。注意: 通过检查文件的扩展名(extension)来确保用户上传的为PDF文件的做法并不是万全之策。比如,可能用户的确上传的是PDF文件,只不过其扩展名为.Brochure;或者用户提供的并不是PDF文件,却使用.pdf的扩展名。保险的做法是通过编程对文件内容做最后一次检查。如此一来,虽然彻底,但稍嫌过头(overkill)。在绝大多数情况下,检查文件扩展名就已经足够了。就像在教程上传文件里讨论的那样,将文件保存在文件系统里时要特别小心,以免覆盖别人上传的文件。本节,我们尝试对上传文件使用一个已经使用的名字,在名字末尾添加一个数字,以示区别。
25、举例,如果在文件夹/Brochures里存在一个名为Meats.pdf的文件,上传文件时我们取名为Meats-1.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;
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1