最近写网站用到FileUpload控件,好长时间不用手生了,所以特地又仔细地看了一遍MSDN文档。在看到其FileBytes属性时,看到其中的示例代码,颇感郁闷。
// 后面有给dudu的附言,如果有幸dudu莅临,请阅感觉其中有不少问题,虽然很细微,但出现在MSDN中,总感却不那么和谐。下面是完整的示例。 1 <% @ Page Language = " C# " %> 2 < html > 3 < head > 4 5 < script runat = " server " > 6 7 private void DisplayFileContents(HttpPostedFile file) 8 { 9 int fileLen; 10 string displayString = ""; 11 12 // Get the length of the file. 13 fileLen = FileUpload1.PostedFile.ContentLength; /**///// <---- 1.1 14 15 // Display the length of the file in a label. 16 LengthLabel.Text = "The length of the file is " 17 + fileLen.ToString() + " bytes."; 18 19 // Create a byte array to hold the contents of the file. 20 byte[] input = new byte[fileLen]; 21 input = FileUpload1.FileBytes; /**///// <---- 2, 1.2 22 23 // Copy the byte array to a string. 24 for (int loop1 = 0; loop1 <= fileLen - 1; loop1++) { 25 displayString = displayString + input[loop1].ToString(); /**///// <---- 3 26 } 27 28 // Display the contents of the file in a 29 // textbox on the page. 30 ContentsLabel.Text = "The contents of the file as bytes:"; 31 32 TextBox ContentsTextBox = new TextBox(); /**///// <---- 4 33 ContentsTextBox.TextMode = TextBoxMode.MultiLine; 34 ContentsTextBox.Height = Unit.Pixel(300); 35 ContentsTextBox.Width = Unit.Pixel(400); 36 ContentsTextBox.Text = displayString; 37 38 // Add the textbox to the Controls collection 39 // of the Placeholder control. 40 PlaceHolder1.Controls.Add(ContentsTextBox); 41 42 } 43 44 protected void UploadButton_Click(object sender, EventArgs e) 45 { 46 // Specify the path on the server to 47 // save the uploaded file to. 48 string savePath = @"c:\temp\uploads\"; 49 50 // Before attempting to perform operations 51 // on the the file, verify that the FileUpload 52 // control contains a file. 53 if (FileUpload1.HasFile) { 54 55 // Append the name of the file to upload to the path. 56 savePath += FileUpload1.FileName; 57 58 // Call the SaveAs method to save the 59 // uploaded file to the specified path. 60 // This example does not perform all 61 // the necessary error checking. 62 // If a file with the same name 63 // already exists in the specified path, 64 // the uploaded file overwrites it. 65 FileUpload1.SaveAs(savePath); 66 67 // Notify the user that the file was uploaded successfully. 68 UploadStatusLabel.Text = "Your file was uploaded successfully."; 69 70 // Call a helper routine to display the contents 71 // of the file to upload. 72 DisplayFileContents(FileUpload1.PostedFile); /**///// <---- 1.3 73 } 74 else 75 { 76 // Notify the user that a file was not uploaded. 77 UploadStatusLabel.Text = "You did not specify a file to upload."; 78 } 79 } 80</script> 81 82</head> 83<body> 84 85 <h3>FileUpload.FileContent Property Example</h3> 86 87 <form ID="Form1" runat="server"> 88 89 <h4>Select a file to upload:</h4> 90 91 <asp:FileUpload id="FileUpload1" 92 runat="server"> 93 </asp:FileUpload> 94 95 <br /><br /> 96 97 <asp:Button id="UploadButton" 98 Text="Upload file" 99 OnClick="UploadButton_Click"100 runat="server">101 </asp:Button>102 103 <br /><br />104 105 <asp:Label id="UploadStatusLabel"106 runat="server">107 </asp:Label> 108 109 <hr />110 111 <asp:Label id="LengthLabel"112 runat="server">113 </asp:Label> 114 115 <br /><br />116 117 <asp:Label id="ContentsLabel"118 runat="server">119 </asp:Label> 120 121 <br /><br />122 123 <asp:PlaceHolder id="PlaceHolder1"124 runat="server">125 </asp:PlaceHolder> 126 127 </form>128129</body>130</html>
问题1注释中的1.1、1.2、1.3 private void DisplayFileContents(HttpPostedFile file) { int fileLen; string displayString = ""; // Get the length of the file. fileLen = FileUpload1.PostedFile.ContentLength; /**///// <---- 1.1
input = FileUpload1.FileBytes; /**/ //// <---- 2, 1.2
protected void UploadButton_Click( object sender, EventArgs e) { // Call a helper routine to display the contents // of the file to upload. DisplayFileContents(FileUpload1.PostedFile); /**///// <---- 1.3
在这里,DisplayFileContents方法接受一个HttpPostedFile类型的参数file,表示从FileUpload控件上传的文件。的确,在1.3处将FileUpload1.PostedFile属性传了进来。
先往复杂了说,从方法的名字和参数可以猜想,作者的目的是降低显示文件内容的操作和FileUpload控件的耦合度。比如,如果页面上有多个FileUpload控件时,就可以重用该方法;或者如果不使用FileUpload控件了,而是用了自己编写的上传控件,只要能提供HttpPostedFile类型的对象,也可以使用该方法。但1.1和1.2两个地方颠覆了这个初衷,这个方法还是紧紧地与FileUpload1控件紧紧地关联在了一起。如果添加了另外一个FileUpload控件,或者原控件改名了,这个方法必须同时修改。再往简单了说,整个方法体都没有用到过这个file参数,又何必添加呢?问题2注释中的2 // Create a byte array to hold the contents of the file. byte [] input = new byte [fileLen]; input = FileUpload1.FileBytes; /**/ //// <---- 2, 1.2
这是一个比较严重的内存泄露问题。首先,之前的一行byte[] input = new byte[fileLen];在堆上分配了与文件大小相同的字节数组(注意数组都是引用类型),然后input = FileUpload1.FileBytes;直接修改了input变量所引用的对象,而原来new byte[fileLen]得到的数组成了孤立对象,遗留在堆中等待被回收。
试想如果这个代码出现在产品中,如果允许用户数MB甚至更大的文件,如果同时(或很短一段时间内)有数百甚至更多的人上传文件……改进方法:1 直接使用FileUpload1.FileBytes属性,或者直接作这样的赋值:byte[] input = FileUpload1.FileBytes,然后使用input变量;2 如果有必要用一个数组的话,写一个循环将FileUpload1.FileBytes的内容复制到input中。问题3注释中的3 // Copy the byte array to a string. for ( int loop1 = 0 ; loop1 <= fileLen - 1 ; loop1 ++ ) { displayString = displayString + input[loop1].ToString(); /**///// <---- 3 }
这也是一个比较严重的问题,但我就不用多说了,几乎提到了字符串操作的每篇技术文章或每本书都提到,慎用字符串的相加(连接)操作,尤其是在循环内部。
问题4
注释中的4 TextBox ContentsTextBox = new TextBox(); /**/ //// <---- 4 ContentsTextBox.TextMode = TextBoxMode.MultiLine; ContentsTextBox.Height = Unit.Pixel( 300 ); ContentsTextBox.Width = Unit.Pixel( 400 ); ContentsTextBox.Text = displayString; // Add the textbox to the Controls collection // of the Placeholder control. PlaceHolder1.Controls.Add(ContentsTextBox);
这是一个小问题,主要在于用户体验。每上传一次就要创建一个文本框显示文件内容,并且文本框是在服务器端添加的,刷新页面也无法消除。这样的话,如果用户查看了几个文件的内容后,要想清空页面,只有关闭浏览器重新打开,这样的用户体验是非常不好的。实际上,硬编码一个文本框,每次修改其Text属性即可。
----------- 分割线华丽地降临 -------------------实际上,对于示例代码来说,问题1和4无伤大雅,毕竟受众所关心的是如何通过FileBytes得到文件中的字节。但问题2和3些许有些“误人子弟”(如果有人直接像这样读取文件字节,那就得浪费fileLen个字节的内存,如果有n个人用这个功能,那就得浪费n*fileLen个字节,你付得起责任吗?——小学老师如是教育)。而如果是在产品中,这4个问题恐怕都会产生不良后果。----------- 分割线再次华丽地降临 ---------------P.S. to dudu:园子的回复下面有一个“刷新评论列表”,实在是个好东西。但有个问题,就是如果一篇文章一条回复也没有的话,这个链接是不会出现的。可如果我在浏览一个 尚没有回复的文章 时,希望刷一下看看有没有人在我浏览的时候发表了回复,那就只有刷新页面了。所以,能否改进一下,让这个链接任何时候都能出现?小问题,呵呵~