在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
导言: 在前面的3章里我们为处理二进制数据添加了很多的功能。我们首先在表Categories里添加BrochurePath列,并更新了体系结构。同样,为了处理表Categorie里现有的Picture列,我们在数据访问层和业务逻辑层里增加了相应的方法。同时我们创建一个页面,在GridView控件里显示二进制数据——包含一个指向说明小册子的下载链接,并将每个类的图片显示在<img>元素里。同时我们添加一个DetailsView控件,供用户添加新的类,并上传其图片和小册子数据。 剩下的就是添加编辑和删除功能,本章我们将通过GridView控件内建的编辑和删除功能来实现。当编辑一个类时,我们允许用户用任意指定的图片将原来的换掉;也可以用新的小册子将现有的替换掉,甚至不再包含小册子文件。让我们开始吧! 第1步:更新数据访问层 虽然数据访问层包含自动生成的Insert, Update和Delete方法,但它们都基于CategoriesTableAdapter的主查询,因此并不包含Picture列。自然,Insert和Update方法也不包含picture列的相应参数。就像56章做的那样,我们需要为更新Categories表而创建新的TableAdapter方法。 右键点击CategoriesTableAdapter的顶部,选择“添加查询”,打开TableAdapter查询设置向导,我们首先选择“使用SQL语句”,点Next,再选“UPDATE”,再点Next.
我们现在需要指定UPDATE SQL语句。向导自动创建一个基于TableAdapter主查询的UPDATE语句(它更新CategoryName, Description和BrochurePath值)。更新该语句以包含Picture列,以及@Picture参数,像如下这样: UPDATE [Categories] SET [CategoryName] = @CategoryName, [Description] = @Description, [BrochurePath] = @BrochurePath , [Picture] = @Picture WHERE (([CategoryID] = @Original_CategoryID)) 最后,向导要求我们为新的TableAdapter方法命名,我们取为UpdateWithPicture,再点Finish。
第2步:添加新的业务逻辑方法 除了更新DAL外,我们需要更新BLL以包含更新、删除类的方法。以下是表现层需要调用的方法: 为了删除一个类,我们使用CategoriesTableAdapter的自动生成的Delete方法,在类CategoriesBLL里添加如下的方法: [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Delete, true)] public bool DeleteCategory(int categoryID) { int rowsAffected = Adapter.Delete(categoryID); // Return true if precisely one row was deleted, otherwise false return rowsAffected == 1; } 本教程,为了更新一个类,我们将创建2个方法。一个方法接受picture值,并调用我们刚刚添加到CategoriesTableAdapter里的UpdateWithPicture方法。另一个方法只接受CategoryName, Description和BrochurePath值, 并调用CategoriesTableAdapter类里自动生成的Update语句。为什么要使用2种方法呢?某些情况下,用户更新类时同时更新其图片,这时就需要上传一张新图片。上传图片的数据将在UPDATE语句里用到;另一种情况,用户只想更新类的name和description信息,因此我们需要使用2种更新方法。业务逻辑层会根据是否传入picture值来判断使用哪种方法。 为达该目的,我们要在CategoriesBLL类里添加2个方法,名字都是UpdateCategory,第一个方法接受的参数包括3个string,1个byte数组和1个int;第二个方法接受的参数包括3个string和1个int。3个字符串参数代表类的name, description和brochure文件路径,byte数组包含的是类的picture数据,int代表类记录的CategoryID,我们注意到,当传入的byte数组为null时,第一个方法将调用第二个方法。 [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Update, false)] public bool UpdateCategory(string categoryName, string description, string brochurePath, byte[] picture, int categoryID) { // If no picture is specified, use other overload if (picture == null) return UpdateCategory(categoryName, description, brochurePath, categoryID); // Update picture, as well int rowsAffected = Adapter.UpdateWithPicture (categoryName, description, brochurePath, picture, categoryID); // Return true if precisely one row was updated, otherwise false return rowsAffected == 1; } [System.ComponentModel.DataObjectMethodAttribute (System.ComponentModel.DataObjectMethodType.Update, true)] public bool UpdateCategory(string categoryName, string description, string brochurePath, int categoryID) { int rowsAffected = Adapter.Update (categoryName, description, brochurePath, categoryID); // Return true if precisely one row was updated, otherwise false return rowsAffected == 1; } 第3步:拷贝功能 在上一章里,我们创建了一个UploadInDetailsView.aspx页面,在一个GridView控件列出所有的类,再通过一个DetailsView控件来添加新的类。在本教程,我们将扩展GridView控件以支持编辑和删除功能。不过我们不再使用UploadInDetailsView.aspx页面,让我们在~/BinaryData文件夹里创建一个新页面,UpdatingAndDeleting.aspx,将UploadInDetailsView.aspx页面的声明代码复制并粘贴到页面UpdatingAndDeleting.aspx. 打开UploadInDetailsView.aspx页面,将其<asp:Content>元素里的声明代码复制下来,就像图3那样。接下来,打开UpdatingAndDeleting.aspx页面,把代码粘贴在<asp:Content>元素里。同样的,将UploadInDetailsView.aspx页面的后台代码拷贝到UpdatingAndDeleting.aspx。
完成后,登录UpdatingAndDeleting.aspx页面,你将会看到相同的输出效果。感觉用起来和UploadInDetailsView.aspx页面一样。 第4步:添加ObjectDataSource和GridView的删除功能 就像在教程16《概述插入、更新和删除数据》里探讨的一样,只要GridView控件绑定的数据源支持“删除”功能,我们就可以为GridView控件启用删除功能。不过,GridView控件绑定的ObjectDataSource(也就是CategoriesDataSource)目前并不支持删除。 为支持删除,在ObjectDataSource的智能标签里点“配置数据源”,一直点到“定义数据方法”界面。虽然当前只指定了ObjectDataSource控件InsertMethod属性和SelectMethod属性,但向导自动地分别为UPDATE标签和DELETE标签指定UpdateCategory方法和DeleteCategory方法。为什么呢?因为我们在CategoriesBLL类里为上述2种方法使用了DataObjectMethodAttribute属性,作用是分别使其成为默认的“更新”和“删除”方法。 不过现在我们在UPDATE标签的下拉列表里选“(None)”, 而 DELETE标签里仍然为DeleteCategory方法。我们将在第6步添加更新功能。
注意:完成设置后,Visual Studio会问你是否“刷新列和主键”,选择No,因为选择Yes将会把我们自己定制的任何列覆盖掉。 现在,ObjectDataSource控件将包含DeleteMethod属性和对应的DeleteParameter参数。我们记得在以前的教程提到过,当使用向导指定方法时,Visual Studio会自动的将ObjectDataSource控件的OldValuesParameterFormatString属性设置为original_{0},这将导致更新和删除时出现问题。为此,要么将清除该属性,要么将其设置为默认的{0}值。对该属性的更详细讨论见教程16《概述插入、更新和删除数据》 完成后,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,其ShowDeleteButton属性为true。
花几分钟测试删除功能。由于表Products和表Categories之间有一个外键CategoryID,当你删除现有的8个类中的任何一个时,你会得到一个外键约束冲突异常。为顺利的实现测试,我们需要添加一个附带图片和说明小册子的新类,如图6所示,小册子为Test.pdf,图7为添加了测试类的GridView控件界面。
在Visual Studio里刷新解决资源管理器,你会在文件夹~/Brochures里看到Test.pdf文件(见图8) 下一步,点击Test类的Delete链接,页面回传,引发CategoriesBLL的DeleteCategory 方法,该方法又调用DAL层的Delete方法,向数据库发送适当的DELETE命令。最后数据重新绑定到GridView控件,Test类将不再显示出来。 虽然已经成功地将Test类从Categories表删除,但存储在文件系统的对应小册子仍旧存在,刷新解决资源管理器,你将发现Test.pdf依然放在~/Brochures文件夹里。
第5步:删除残存的Brochure文件 未将二进制数据存储进数据库时面临的一个问题便是:当删除一条数据库记录时,我们需要另外采取步骤来删除该记录对应的二进制数据文件。当执行delete命令时,会发生一些事前事件和事后事件(pre- and post-action events),我们需要创建对应的事件处理器。在Categories表的记录被删除之前,我们需要确定对应PDF文件的路径,但在删除记录之前我们不会删除其对应的PDF文件,以防发生异常或记录最终未被删除的情况。 从事件发生的时间先后顺序来看,GridView控件的RowDeleting事件在调用ObjectDataSource控件的delete命令前发生;而RowDeleted事件在调用ObjectDataSource控件的delete命令之后再发生。创建这2个事件处理器,代码如下: // A page variable to "remember" the deleted category's BrochurePath value string deletedCategorysPdfPath = null; protected void Categories_RowDeleting(object sender, GridViewDeleteEventArgs e) { // Determine the PDF path for the category being deleted... int categoryID = Convert.ToInt32(e.Keys["CategoryID"]); CategoriesBLL categoryAPI = new CategoriesBLL(); Northwind.CategoriesDataTable categories = categoryAPI.GetCategoryByCategoryID(categoryID); Northwind.CategoriesRow category = categories[0]; if (category.IsBrochurePathNull()) deletedCategorysPdfPath = null; else deletedCategorysPdfPath = category.BrochurePath; } protected void Categories_RowDeleted(object sender, GridViewDeletedEventArgs e) { // Delete the brochure file if there were no problems deleting the record if (e.Exception == null) { // Is there a file to delete? if (deletedCategorysPdfPath != null) { System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath)); } } } 在RowDeleting事件处理器里,从GridView控件的DataKeys集合里获取被删记录的CategoryID值,而在这里,我们通过e.Keys来访问DataKeys集合。接着,调用类CategoriesBLL的GetCategoryByCategoryID(categoryID)方法来返回被删记录的信息,若返回的BrochurePath值不为NULL,那么将其赋值给页面参数deletedCategorysPdfPath,再在RowDeleted事件处理器里删除文件。 注意:在RowDeleting事件处理器里,我们没有返回被删记录的BrochurePath信息,而是将BrochurePath添加到GridView的DataKeyNames属性,再通过访问e.Keys来获取该记录的值。这样做虽然稍微增大了GridView的视图状态,但减少了必要的代码,也省了一步访问数据库。 调用ObjectDataSource控件的delete命令后,紧接着发生GridView控件的RowDeleted事件,如果删除过程没有异常且deletedCategorysPdfPath值不为空,那就将对应的PDF文件从文件系统删除。我们注意到,代码没有删除类的picture,那是因为picture数据是直接存储在数据库里的,当删除记录时就一起删除了。 添加完上述2个事件处理器后,再次测试删除。当删除某个类时,其对应的PDF文件也删除了。 下面我们深入研究添加更新功能以应对类的brochure和picture.第6步探讨更新brochure信息的技术,第7章探讨更新picture。 第6步:更新类的Brochure 就像在教程16《概述插入、更新和删除数据》里探讨的一样,如果GridView的数据源控件支持编辑,那么我们就可以启用GridView控件的编辑功能。当前,名为CategoriesDataSource的ObjectDataSource控件并不支持编辑,那让我们添加吧。 点击ObjectDataSource控件的“设置数据源”链接,一直点到“定义数据方法”界面。由于在CategoriesBLL里对重载的UpdateCategory方法使用了DataObjectMethodAttribute属性,UPDATE标签的下拉列表自动的选择了该方法,它包含4个输入参数(不包含Picture)。我们选择另一个包含5个输入参数的重载的UpdateCategory方法。
ObjectDataSource控件现在包含了UpdateMethod属性以及相应的UpdateParameters参数集。就像在第4步提到的一样,当使用设置向导时,Visual Studio会将ObjectDataSource控件的OldValuesParameterFormatString属性设置为original_{0},这导致调用update和delete方法时出现问题。因此,要么将该属性清除,要么设该属性为{0}。 完成后,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控件的智能标签里选“编辑”。这将设置CommandField的ShowEditButton属性为true,结果是为每行添加一个Edit按钮(当记录处于编辑状态时,将呈现为Update和Cancel按钮)
从浏览器查看该页面,点某条记录的Edit按钮。CategoryName和Description列呈现为一个文本框。由于BrochurePath TemplateField没有EditItemTemplate模板,所以它依旧呈现其ItemTemplate模板——一个指向brochure的链接。Picture列呈现为一个文本框,并且该Picture ImageField的Text属性被指派为DataImageUrlField值,在这里,即CategoryID.
定制BrochurePath编辑界面 我们可以为BrochurePath TemplateField创建一个编辑界面,我们可以选择: .维持原样 我们也应该更新Picture ImageField的编辑界面,不过我们将放在第7步来讨论。 在GridView控件的智能标签里选择“编辑模板”,再从下拉列表里选BrochurePath TemplateField的EditItemTemplate模板。在模板里添加一个RadioButtonList Web控件,其ID为BrochureOptions;AutoPostBack属性为true.再在属性窗口里点Items属性的椭圆型区域,进入ListItem Collection Editor界面,分别添加值为1,2,3的选项: .Use current brochure 设第一个ListItem的Selected属性为true.
在RadioButtonList控件下面,添加一个FileUpload控件,ID为BrochureUpload,设其Visible属性为false。
RadioButtonList控件为用户提供了3个选择,只有当选择“Upload new brochure”时, FileUpload控件才会展现出来。为此,我们为RadioButtonList控件的SelectedIndexChanged事件创建事件处理器,如下: protected void BrochureOptions_SelectedIndexChanged(object sender, EventArgs e) { // Get a reference to the RadioButtonList and its Parent RadioButtonList BrochureOptions = (RadioButtonList)sender; Control parent = BrochureOptions.Parent; // Now use FindControl("controlID") to get a reference of the // FileUpload control FileUpload BrochureUpload = (FileUpload)parent.FindControl("BrochureUpload"); // Only show BrochureUpload if SelectedValue = "3" BrochureUpload.Visible = (BrochureOptions.SelectedValue == "3"); } 由于RadioButtonList控件和FileUpload控件同时出现在一个模板里,我们需要通过编程来访问这2个控件。在SelectedIndexChanged事件处理器里,我们通过输入参数sender来引用RadioButtonList控件。为了获取FileUpload控件,我们需要使用RadioButtonList的父控件(parent control),并使用FindControl("controlID")方法。一旦我们同时获取了RadioButtonList和FileUpload控件时,只要RadioButtonList控件的SelectedValue值等于3,即“Upload new brochure” ListItem的值时,将FileUpload控件的Visible属性设置为true 。 添加完上述代码后,花几分钟时间来测试编辑页面。点击某行的Edit按钮,默认是选中“Use current brochure”项,改选另一项,页面产生回传,如果是选择第3项,则FileUpload控件将会显示出来,否则处于隐身状态。图14显示点击Edit按钮的情形,而图15则是选择“Upload new brochure”时的情形。
保存Brochure文件并更新BrochurePath列 当点击GridView控件的Update按钮时,触发RowUpdating事件,调用ObjectDataSource控件的update命令,然后触发GridView控件的RowUpdated事件。跟deleting流程类似,我们需要创建这些事件的处理器。在RowUpdating事件处理器里,我们需要根据RadioButtonList的SelectedValue值来判断下一步怎么做。 .如果SelectedValue值为1,我们将保持rochurePath不变。所以我们将ObjectDataSource控件的brochurePath参数设置为当前处于编辑状态记录的BrochurePath值,方法为e.NewValues["brochurePath"] = value. .如果SelectedValue值为2,意味着将BrochurePath设为NULL。为此,我们需要将ObjectDataSource控件的brochurePath参数设为Nothing,结果就是在UPDATE命令里使用NULL。如果存在对应的brochure文件,我们必须将其删除,前提是没有抛出任何的异常。 .如果SelectedValue值为3,我们必须确保用户已经上传了一个PDF文件并将其保存在文件系统,然后更新记录的BrochurePath值。我们要先将被替换的前一个文件删除掉,当然前提是没有引发异常。 在上一章里,当在DetailsView控件里添加新记录时,触发DetailsView控件的ItemInserting事件。在本章,当RadioButtonList控件的SelectedValue为3时(即我们选择Upload new brochure时),接下来要采取的步骤实际上与DetailsView控件的ItemInserting事件处理器实现的功能相似。根据实现的功能,我划分为2个方法: .ProcessBrochureUpload(FileUpload, out bool):它以一个FileUpload控件实例为输入参数,结果为一个布尔值(Boolean)。根据该布尔值判断是否继续更新或删除操作,抑或取消操作。如果存在上传文件该方法就返回其路径,反之返回null。 .DeleteRememberedBrochurePath:如果页面变量deletedCategorysPdfPath不为null,则删除该参数指定的文件。 下面是上述2种方法的代码。注意ProcessBrochureUpload方法和DetailsView控件的ItemInserting事件处理器有某些相似性,在本章,我们更新DetailsView控件的事件处理器以使用这些新方法。下载本章的代码,查看我们对DetailsView控件的事件处理器所做的修改。 private string ProcessBrochureUpload (FileUpload BrochureUpload, out bool CancelOperation) { CancelOperation = false; // by default, do not cancel operation 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; CancelOperation = true; return null; } 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)); return brochurePath; } else { // No file uploaded return null; } } private void DeleteRememberedBrochurePath() { // Is there a file to delete? if (deletedCategorysPdfPath != null) { System.IO.File.Delete(Server.MapPath(deletedCategorysPdfPath)); } } 在GridView控件的RowUpdating和RowUpdated事件处理器里使用上面2个方法,如下: protected void Categories_RowUpdating(object sender, GridViewUpdateEventArgs e) { // Reference the RadioButtonList RadioButtonList BrochureOptions = (RadioButtonList)Categories.Rows[e.RowIndex].FindControl("BrochureOptions"); // Get BrochurePath information about the record being updated int categoryID = Convert.ToInt32(e.Keys["CategoryID"]); CategoriesBLL categoryAPI = new CategoriesBLL(); Northwind.CategoriesDataTable categories = categoryAPI.GetCategoryByCategoryID(categoryID); Northwind.CategoriesRow category = categories[0]; if (BrochureOptions.SelectedValue == "1") { // Use current value for BrochurePath if (category.IsBrochurePathNull()) e.NewValues["brochurePath"] = null; else e.NewValues["brochurePath"] = category.BrochurePath; } else if (BrochureOptions.SelectedValue == "2") { // Remove the current brochure (set it to NULL in the database) e.NewValues["brochurePath"] = null; } else if (BrochureOptions.SelectedValue == "3") { // Reference the BrochurePath FileUpload control FileUpload BrochureUpload = (FileUpload)Categories.Rows[e.RowIndex].FindControl("BrochureUpload"); // Process the BrochureUpload bool cancelOperation = false; e.NewValues["brochurePath"] = ProcessBrochureUpload(BrochureUpload, out cancelOperation); e.Cancel = cancelOperation; } else { // Unknown value! throw new ApplicationException( string.Format("Invalid BrochureOptions value, {0}", BrochureOptions.SelectedValue)); } if (BrochureOptions.SelectedValue == "2" || BrochureOptions.SelectedValue == "3") { // "Remember" that we need to delete the old PDF file if (category.IsBrochurePathNull()) deletedCategorysPdfPath = null; else deletedCategorysPdfPath = category.BrochurePath; } } protected void Categories_RowUpdated(object sender, GridViewUpdatedEventArgs e) { // If there were no problems and we updated the PDF file, // then delete the existing one if (e.Exception == null) { DeleteRememberedBrochurePath(); } } 注意:RowUpdating事件处理器是如何根据SelectedValue值的不同而使用一系列的条件语句来实现相应的功能。 使用上面的代码,我们就可以编辑一个类了,使用其当前的brochure,或不使用brochure,再或者使用一个新的brochure。在RowUpdating和RowUpdated事件处理器里设置断点(breakpoints)吧,以便更好的理解处理流程。 第7步:上传新图片 Picture ImageField的编辑界面呈现为一个文本框,里面显示的是DataImageUrlField 属性的值。在编辑流程,GridView控件向ObjectDataSource传入一个参数,参数名为ImageField的DataImageUrlField属性;参数值为在编辑界面输入文本框里的值。当图片是存储在文件系统,且DataImageUrlField属性包含的是访问该图片的完整URL时,这样做是恰当的。在这种情况下,在编辑界面里,文本框将会显示图片的URL。毫无疑问,默认的界面不允许用户上传新的图片,但用户却可以修改图片的URL值。不过,在本教程不会出现这种情况,因为Picture数据是直接存储在数据库的,且DataImageUrlField属性被设为CategoryID值。 为了更好的理解在本教程里编辑某行的ImageField时将会发生上什么,我们做如下假设:用户编辑一个CategoryID值为10行,Picture ImageField呈现为一个文本框,显示10,假设用户将其改为50后点Update按钮,页面回传,GridView控件最初产生一个名为CategoryID,值为50的参数。在GridView传递此参数(连同参数CategoryName和参数Description一起)以前,对DataKeys集添加值。因此,将当前行的CategoryID值10,覆盖掉。简言之,ImageField的编辑界面没有对本章教程的编辑流程产生任何影响,因为ImageField的DataImageUrlField属性和DataKey值都是同一个值。 当图片存储在数据库时,ImageField将其显示出来也很容易。不过在编辑界面里我们不需要使用文本框,而提供一个FileUpload控件供最终用户更改图片时使用。与BrochurePath不同,我们不允许类的图片为空——用户要么提供新图片要么使用当前的图片。 为定制ImageField的编辑界面,我们需要将其转化为一个TemplateField。在GridView控件的智能标签里点击“编辑列”,进入后选中ImageField,再点击“Convert this field into a TemplateField”链接。
转换后的TemplateField由2个模版构成。就像下面的声明代码显示的那样,ItemTemplate模版包含一个Image Web控件,其ImageUrl属性由一个数据绑定语法指定,该数据绑定语法基于ImageField的DataImageUrlField和 DataImageUrlFormatString属性。而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 TemplateField的EditItemTemplate模版。在模版里你会看见一个TextBox,将其删除。从工具箱里拖一个FileUpload控件到页面,设其ID为PictureUpload。同时在模版里添加如下的文本:“To change the category's picture, specify a new picture. To keep the category's picture the same, leave the field empty”。
记得我们设置ObjectDataSource控件调用CategoriesBLL的UpdateCategory方法,该方法的一个输入参数为数组,用于处理图片的数据。如果该数组为null值,则调用另一个重载的UpdateCategory方法,该重载的UpdateCategory方法的UPDATE SQL语句不会更改Picture列,因此类的图片不会由任何变化。在GridView控件的RowUpdating事件处理器里,我们编程访问名为PictureUpload的FileUpload控件,判断是否上传了文件。如果没有文件上传,我们将不会为参数picture指定值;反之,如果上传了文件,我们将确保其为JPG格式的文件,并通过参数picture将其传给ObjectDataSource控件。 就像第6步里的代码一样,我们此时将要用到的绝大多数的代码已经存在于DetailsView控件的ItemInserting事件处理器里了。现在我们创建一个新的方法ValidPictureUpload,并更新ItemInserting事件处理器以使用该方法。 在GridView控件的RowUpdating事件处理器的开头部分添加如下的代码,这很重要,因为我们不希望将一个不符合条件的上传文件存储在文件系统。 // Reference the PictureUpload FileUpload FileUpload PictureUpload = (FileUpload)Categories.Rows[e.RowIndex].FindControl("PictureUpload"); if (PictureUpload.HasFile) { // Make sure the picture upload is valid if (ValidPictureUpload(PictureUpload)) { e.NewValues["picture"] = PictureUpload.FileBytes; } else { // Invalid file upload, cancel update and exit event handler e.Cancel = true; return; } } ValidPictureUpload(FileUpload)方法只有一个FileUpload控件类型的输入参数,通过检查上传文件的扩展符以确保上传的文件为JPG格式。只有当上传了文件时才会调用该方法;如果没有文件上传,参数picture就只能使用其默认值—null。如果上传了图片,且ValidPictureUpload方法返回值true,将用图片的二进制数据对参数picture赋值。如果ValidPictureUpload方法返回值false,则取消更新,并退出事件处理器。 ValidPictureUpload(FileUpload)方法的代码如下: private bool ValidPictureUpload(FileUpload PictureUpload) { // 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; return false; } else { return true; } } 第8步:将原始几个类的图片替换为JPG格式 回想起最开始的那8个类的图片为位图文件其包含一个OLE报头。现在我们添加了新功能以编辑现有记录的图片,花几分钟将这些位图文件替换为JPG文件。如果你想使当前类的图片不变,你可以通过下面的布置将其转换为JPG格式: 1.将这些位图保存在硬盘。在浏览器里访问UpdatingAndDeleting.aspx页面,对这8个类的图片,点右键,选则保存图片。 2.在一个图片编辑器(比如Microsoft Paint)软件里打开图片。 3.将图片保存为JPG格式 4.在编辑界面里,用JPG图片更新类的picture 完成更新并上传JPG图片之后,图片不会呈现在浏览器里,原因是DisplayCategoryPicture.aspx将尝试对最开始8个类的图片剥离OLE报头。怎样修正呢?我们将剥离OLE报头的代码移除。这样,DisplayCategoryPicture.aspx页面的Page_Load事件处理器的代码如下: 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]; // 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); } 注意:UpdatingAndDeleting.aspx页面的编辑和添加界面要稍微复杂一点。DetailsView和GridView控件里的CategoryName和Description BoundFields应当转换成TemplateFields;另外由于CategoryName不能为NULL值,应对其添加一个RequiredFieldValidator控件。此外,Description应修改为允许换行的的文本框(multi-line TextBox),我将这些留给读者作为练习。 总结: 在接下来的一系列教程里,我们探讨各种缓存技术。使用缓存可以提升应用程序的整体性能。 祝编程快乐! 作者简介 本系列教程作者 Scott Mitchell,著有六本ASP/ASP.NET方面的书,是4GuysFromRolla.com的创始人,自1998年以来一直应用 微软Web技术。大家可以点击查看全部教程《[翻译]Scott Mitchell 的ASP.NET 2.0数据教程》,希望对大家的学习ASP.NET有所帮助。 |
请发表评论