转载:http://www.cnblogs.com/william_fire/articles/31324.html
最近看见有人在研究ASP.NET的上传组件,如何上传大文件的问题,于是简单地研究了一下,发现了一些东西,总结如下,希望大家能够提出一个较好的解决方案。
据msdn所言,在Web.Config中,默认可以上传的文件大小为8M,如果需要上传更大的文件,则需要手动指 定: 这里的示例是最大100M的,经过测试,100M以下的文件还是可以正常上传的(占用内存必须在服务器可用内存60%以下)在使用.NET中的文件上传的 功能时,实际上使用的是Html控件,并不是Web控件,这就意味着,HtmlInputFile控件不是明显地支持服务端的Event的,所以,提交 时,应该使用Html控件的Button进行Submit。
经测试,可以发现,当不断地进行上传文件的时候,内存的消耗是不断地累积的,也就是说,当你上传了一个20M的文件 后,再上传20M时,缓存中内存消耗会达到40M左右。 在MSDN文档中的说明里面指出,当文件上传到服务器后,是存储在缓存中的,在没有把缓存中的数据存储成文件时,是不会清空缓存的。
其实,这句话有一定的误导性,因为在HtmlInputFile控件中,PostedFile属性中,提供了一个 SaveAs方法成员,给许多人一种错觉就是,当进行了HtmlInpuFile的SaveAs后,缓存会被清空。实际上不是这样,首先,查看一下 HtmlInputFile中的PostedFile属性的代码,如下:
[WebCategory("Default"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue("")]
public HttpPostedFile PostedFile
{
get { return this.Context.Request.Files[this.RenderedNameAttribute]; }
}
可以看出,实际上是一个HttpPostedFile类实例,那么要明白它的工作原理,肯定就要追到HttpPostedFile类去看看。
好了,我们再看看,这个HttpPostFile中提供的SaveAs()的代码:
public void SaveAs(string filename)
{
HttpRuntimeSection section1;
if (!Path.IsPathRooted(filename))
{
section1 = RuntimeConfig.GetConfig().HttpRuntime;
if (section1.RequireRootedSaveAsPath)
{
throw new HttpException(HttpRuntime.FormatResourceString("SaveAs_requires_rooted_path", filename));
}
}
FileStream stream1 = new FileStream(filename, FileMode.Create);
try
{
this._stream.WriteTo(stream1);
stream1.Flush();
return;
}
finally
{
stream1.Close();
}
}
注意,用标记的那一句,观看整个代码,可以发现的是:实际上,在SavaAs方法中,处理的流是在此方法中新建的一 个FileStream的实例,这里只有stream1.Flush(),而this._stream所指向的实例实际上还存在于缓存中,它才是真正占用 了大量内存空间的罪人。
好了,再看看HttpPostedFile的构造函数,就可以知道this._stream是从哪里来的了.
internal HttpPostedFile(string filename, string contentType, HttpInputStream stream)
{
this._filename = filename;
this._contentType = contentType;
this._stream = stream;
}
嗯?原来如此,这里的流对象原来也只是一个引用罢了,真正的占用内存的杀手在哪儿?看样子,只要知道了是谁创建了HttpPostedFile这个类的实例,谁就是罪魁祸首。
不过,这里,我们要暂放一步,目前我们基本知道了实际上进行SaveAs方法后,缓存中的数据并没有被清除,相反 的,它还好好地留在内存中。 我们的目标是要解决这个问题,正好,在HtmlInputFile中也给出了所创建的实例的引用,所以,现在,针对HttpInputStream流来进 行一些处理。
保险起见,我们先看看HttpInputStream的内容吧,再作定论吧。
public override void Flush() { }
@#%*&@,Flush方法居然是这样写的,嗯,以后要注意了!
public override void Close() { this.Uninit(); }
这是Close方法,里面调用了Uninit(),所以,我们再看看Uninit.
protected void Uninit() { this._data = null; this._offset = 0; this._length = 0; this._pos = 0; }
Mmmm,虽然感觉有点不爽,但起码使用这个方法时,这个方法能够保证它会把缓存对象清空。
最后,写出的代码类似下面的形式:
private void btnUpLoad_ServerClick(object sender, System.EventArgs e)
{
try
{
string strFileName = new FileInfo(imageUpload.PostedFile.FileName).Name;
UploadFile.PostedFile.SaveAs("C:\\"+strFileName);
Response.Write("上传成功!");
}
catch
{
Response.Write("上传失败!");
}
finally
{
HttpInputStream upStream = imageUpload.PostedFile.InputStream;
upStream.Close();
}
}
经过测试,发现这样也不是一个理想的方案,还必须进一步补充才行。
过程如下:
在本机上,浏览FileUpload.aspx,此时,w3wp进程占用内存空间为28,948K
当上传一个12.7M的文件后,据返回的信息表示,上传的文件大小为133307341,内存占用变化为49,312K。
再上传一个一个371K的文件,据返回的信息表示,上传的文件上小为3799041,内存占用此时为31,540K
似乎达到了我们的要求,但这里有一个问题就是,当你上传一个大文件后,没有再上传一个小文件的话,那么这个大文件将会在内存里一直占用空间,不知,是否还有更好的办法去解决,现在快1点了,睡觉,以后再说。