source: click here, thanks to author.
基于asp.net的文件上传和下载
UpLoad and Download File Based on Asp.net
---------------------简述
文件上传和下载是网络中非常重要的两种应用。其实现方法主要有FTP方式和HTTP方式两种,FTP(File Transfer Protocol)是指文件传输协议,主要用来在网络上传输文件% 这种方式虽说文件传输稳定、系统资源占用率低、对文件大小没有限制,但服务器的部署比较复杂, 需要使用特定的软件来完成上传和下载,而且功能单一、权限设置复杂,一般用于专业的文件传输,对于普通用户来说实用性不高。HTTP(Hyper Text Transfer Protocol)是指超文本传输协议,是互联网上应用最为广泛的一种网络传输协议。所有的WWW页面都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTTP页面的方法。相比于FTP,HTTP 使用浏览器作为客户端软件,界面友好,操作简单,可以通过WEB表单机制实现文件的上传, 利用超级链接和二进制读写的方法实现文件的下
载。因为HTTP的文件上传和下载可以集成在WEB 页面中,易用性、实用性较强,已经被广泛的应用于论坛、电子邮件、网络硬盘等实际应用中。本文主要介绍使用ASP.NET技术完成文件的上传和下载。
----------------------上传功能实现描述
在ASP.NET 出现之前, 完成HTTP方式的文件上传是一件很复杂的事情。ASP.NET 作为微软的新一代网络程序开发平台,提供了专门用来进行文件上传的控件HtmlInputFile,使用这个控件可以很方便的进行文件的上传。该控件的语法为<input type="file" runat="server">将上传文件保存在服务器指定文件夹下的语法为:
控件名称.PostedFile.SaveAs(服务器端物理路径)此外还可以使用ContentLength、ContentType和FileName属
性,分别获取文件大小、文件类型和在客户端的路径,从而对文件的类型、大小、文件名等进行特别的限定,保证系统的安全和稳定性。具体上传代码如下(本文所列举代码都使用C#语言):
//因用到了System.IO.Path类的GetFileName方法, 需导入System.IO 名称空间
<%@ Import Namespace="System.IO" %>
<script language="C#" runat="server">
private void Enter_Click(object sender,EventArgs e)
{
String filename,filepath;
//Path.GetFileName 方法可以从任何一个路径字符串中获取文件名称
filename=Path.GetFileName(uploadfile.PostedFile.FileName);
//filepath 是服务器端保存上传文件的物理路径
filepath=Server.MapPath("upload")+filename;
//保存上传文件
uploadfile.PostedFile.SaveAs(filepath);
//下面显示上传文件详细信息
message.InnerHtml="文件已经成功上载,详细信息如下";
message.InnerHtml+="<br />保存路径:"+filepath;
message.InnerHtml+="<br />文件名称:"+filename;
message.InnerHtml+="<br />文件大小:"+uploadfile.PostedFile.ContentLength+"字节";
message.InnerHtml+="<br />文件类型:"+uploadfile.PostedFile.ContentType;
message.InnerHtml+="<br />客户端路径:"+uploadfile.PostedFile.FileName;
message.InnerHtml+="<br /><a href=//upload/"+filename+"//>查看文件</a>";}
</script>
<html>
<body>
<h4 style="text-align:center">文件上传</h4>
<form id="form1" runat="server" enctype="multipart/form-data">
选择文件:<input id="uploadfile" type="file" runat="server" />
<br /><input type="button" id="enter" value="提 交" onserverclick="Enter_Click"
runat="server" />
<p><span id="message" style="font:9pt 宋体;" runat="server"></span></p>
</form>
</body>
</html>
使用这种方式实现基本的文件上传的确是非常方便,但在实际项目应用中发现,在ASP.NET中文件上传的默认大小为4MB。 如果通过客户端直接上传超过4MB的文件,会出现DNS 错误,而且因为是客户端错误, 使用Try...Catch机制在服务器端无法捕捉,严重的影响了程序的健壮性,增加了最终客户使用的难度。对于这个问题,一般情况下,我们可以采用更改Web.Config文件中特定选项的方法来解决,如下例:
Web.Config文件
<system.web>
<httpRuntime
MaxRequestLength="80690"
ExecutionTimeout="6000"
/></system.web>
其中MaxRequestLength 就是用来限制上传文件大小的,单位为KB, 可以通过修改这一项的值来增加ASP.NET允许上传文件的最大值。另一项ExecutionTimeout用来设定执行请求的最大时间值,单位为秒。 在这个例子中上传文件的最大值被重新设置成了80mb,另外超时时间也设置成了6000 秒,解决了ASP.NET中4mb文件上传的限制。
但是,这种方法也不是很完善,首先,MaxRequestLength 的值并不能无限的扩大, 因为在上传文件时,ASP.NET会将客户端文件载入服务器内存后,再加以处理,所以如果上传文件超出了服务器可用内存,仍然会出错。 经测试,在配置512MB内存的服务器上,最大可以上传160MB左右的文件,如果超过这个大小,就会发生错误;其次,在文件上传过程中无法给出传输进度的提示,当文件很大时,会使用户无法了解上传进度,甚至认为传输失败&另外,上传文件的大小超过MaxRequestLength 的值后,还是会出现客户端错误。所以这种方法比较适合于进行几百(具体要根据服务器的内存大小来确定)左右的文件上传。
另一种解决的方案就利用隐含的HttpWorkerRequest,用它的GetPreloadedEntityBody和ReadEntityBody方法从IIS 为ASP.NET建立的PIPE里分块读取数据,对于每块分块进行分析并存储为临时文件,最后再转存为指定的文件。
其中分块读取数据的基本代码如下:
IServiceProvider provider = (IServiceProvider )HttpContext.Current;
HttpWorkerRequest request1 = (HttpWorkerRequest)provider.GetServicetypeof(HttpWorkerRequest));
//为上传数据建立缓冲区
byte[] buffer;
//返回HTTP请求正文已被读取的部分
buffer = request1.GetPreloadedEntityBody();
//如果是附件上传
if (!request1.IsEntireEntityBodyIsPreloaded())
{//分块大小
int n = 1024;
//定义临时缓冲区
byte[] tempBuffer = new byte[n];
//循环分块读取,直到所有数据读取结束
while (request1.ReadEntityBody(tempBuffer, n) > 0)IY
{//对读取到临时缓冲区中的数据进行分析并存储
......}}
利用这种方式,通过对分块数据的分析还可以获得上传的进度,从而实现上传进度条。 这种方法实现效率较高,不受服务器内存大小的限制,经测试,在配制512MB内存的服务器上可稳定上传1GB 的数据文件,但实现方法相对要复杂的多。最后, 还有一种方案就是使用基于ASP.NET的第三方组件如AspnetUpLoad等来实现。 这种方法实现简单,无上传文件大小限制,稳定性)上传效率等方面都非常不错,而且界面友好,普遍都提供了上传进度指示等附加功能。 但存在的问题是,优秀的组件一般都为商业软件,需要付费才能使用。
---------------------------下载的实现
在web页面中下载的实现相对于上传要简单的多,一般页面使用的办法都是给要下载的文件建立一个超级链接,在客户端点击超级链接后,弹出下载对话框进行下载。这种方式对于使用rar或zip压缩的文件来说比较适合,但如果源文件为doc、txt、bmp 等常用格式,而客户端又对这些格式的文件进行了关联,那么将不会弹出下载窗口,而是直接在当前位置打开了文件。这样对一些初级用户来说,会认为系统出现了问题,没有完成下载,影响系统的正常使用。
为了解决这个问题,可以用二进制读写的方法来实现,使用FileStream对象的read()方法读取文件到字节数组中,再用Response.BinaryWrite()方法向客户端输出。具体代码:
//FilePath为要下载文件的具体路径
FileStream fs = new FileStream(FilePath, FileMode.Open);
//建立缓冲区
byte[] bytes = new byte[(int)fs.Length];
//读取文件到缓冲区
fs.Read(bytes, 0, bytes.Length);
fs.Close();
Response.Clear();
//向浏览器输出,首先指明ContentType
Response.ContentType = "application/octet-stream";
//向输出流中添加文件名,并进行必要的编码
Response.AddHeader("Content-Disposition", "attachment;filename=" + HttpUtility.UrlEncode(fs.Name,
System.Text.Encoding.UTF8).Replace("+"," "));
//输出二进制文件
Response.BinaryWrite(bytes);
Response.End();
使用这种方法,无论是何种类型的文件,在希望下载时都会弹出选择窗口让用户确认"下载"或者"打开"。
--------------------------------结束语
使用asp.net的控件可以比较容易的实现文件的上传和下载,但毕竟http不是专门用来进行文件传输的, 所以还存在很多的不足。例如:服务器内存占用率相当高,特别是在上传、下载文件较大的时候。所以,如果经常性的要进行大型文件(例如超过2G)的上传下载,用户群体又相对专业时,还是使用FTP 更合适一些。但对于要求操作简单、界面友好且文件传输量不大的场合,上面介绍的方法还是具有很大的优势和实用性。