经常很多项目中都牵涉到文件的上传和下载,文件上传可以更改上传控件的外观形式,
详细参见:http://www.cnblogs.com/Charles2008/archive/2008/07/20/1247084.html
当文件上传后就牵涉到文件的下载。那么在asp.net怎样实现文件的下载呢?这是我要谈的话题。文件下载就是把用户从客户端上传的文件下载到本地。这里牵涉到几个问题:这里上传的文件保存在什么地方。有几种保存的方式?
一般来说上传的文件都保存在服务器上,可以是FTP服务器,web服务器,根据具体情况有所不同。但保存的方式不外乎有下面几种:
1.保存到服务器上的数据库中。(二进制格式)
2.保存到服务器上的XML 文件中。(适合小文件存储)
3.保存到服务器磁盘上的文件夹下。(比较通用)
然而在目前的移动项目开发中,我考虑把文件保存到服务器上的文件夹下:这样就引来了几个问题要解决:
1.如果用户在上传文件中,上传了2个一样的文件名不同文件:如:×××立项报告。那岂不是把上一次上传的文件覆盖掉。
当然这样的情况不是没有,如果换成是不同的用户都上传了相同的文件名的文件,那上传到服务器上的最终文件就是后一个上传的覆盖前一个上传的。那岂不是给一个严重的bug!!!.
方案:这个文件当然很好解决的,就是当文件上传到服务器上时候把文件重命名,用GUID来实现唯一。那么就能保证同一个用户和不同用户上传到服务器上的文件名都不相同,就能保证在服务器上的一个文件夹下同时存在。这样一来这个问题就解决了。然而这里我们还需要考虑一个问题是:
2.由于上传到服务器是是通过GUID命名的,所以直接连接那文件地址下载下来后的文件名和上传的文件名就不同了。这给用户带来了不好的体验。
方案:为了解决这个问题,我们想到了用Response.WriteFile()来实现:
下面是参考代码:
<
asp:GridView
ID
="GridView1"
Width
="50%"
runat
="server"
AutoGenerateColumns
="False"
EmptyDataText
="<tr><th class='gridHeader' scope='col'>附件名称</th><th class='gridHeader' scope='col'>上传人</th><th class='gridHeader' scope='col'>上传时间</th></tr>"
>
<
Columns
>
<
asp:TemplateField
HeaderText
="附件名称"
>
<
ItemTemplate
>
<
span
>
<%
#DataBinder.Eval(Container.DataItem,
"
OriginallyFileName
"
)
%>
</
span
>
<
a
href
='<%#"downfile.aspx?name="+DataBinder.Eval(Container.DataItem,"OriginallyFileName").ToString()+"&path="+DataBinder.Eval(Container.DataItem,"FilePath")%
>
' target="_self" >下载
</
a
>
</
ItemTemplate
>
</
asp:TemplateField
>
<
asp:BoundField
DataField
="InUser"
HeaderText
="上传人"
>
<
ItemStyle
CssClass
="itemStyle"
HorizontalAlign
="Center"
/>
<
HeaderStyle
CssClass
="gridHeader"
/>
</
asp:BoundField
>
<
asp:BoundField
DataField
="InDate"
HeaderText
="上传时间"
DataFormatString
="{0:yyyy-MM-dd}"
HtmlEncode
="false"
>
<
ItemStyle
CssClass
="itemStyle"
HorizontalAlign
="Center"
/>
<
HeaderStyle
CssClass
="gridHeader"
/>
</
asp:BoundField
>
<
asp:TemplateField
HeaderText
="操作"
>
<
ItemTemplate
>
<
asp:ImageButton
runat
="server"
ID
="btnDelete"
ImageUrl
="~/Images/btnDel.gif"
AlternateText
="删除"
ImageAlign
="Middle"
/>
</
ItemTemplate
>
<
ItemStyle
CssClass
="itemStyle"
HorizontalAlign
="Center"
/>
<
HeaderStyle
CssClass
="gridHeader"
/>
</
asp:TemplateField
>
</
Columns
>
</
asp:GridView
>
上面是aspx的代码:下载的链接需要知道一个OriginallyFileName,也就是文件上传的文件名。而FilePath则是文件上传后保存到服务器上的文件路径。(包括文件名)。上面的代码吧原始的文件名和保存到服务器上的文件路径传到了downfile.aspx页面,下面我们来看看downfile.aspx页面的代码:
protected
void
Page_Load(
object
sender, EventArgs e)
{
string
guidname
=
Request.QueryString[
"
path
"
];
string
reallyname
=
Request.QueryString[
"
name
"
];
String FullFileName
=
Server.MapPath(
"
~/Uploads/
"
+
guidname);
FileInfo info
=
new
FileInfo(FullFileName);
Response.Clear();
Response.ClearHeaders();
Response.Buffer
=
false
;
Response.ContentType
=
"
application/octet-stream
"
;
Response.AppendHeader(
"
Content-Disposition
"
,
"
attachment;filename=
"
+
HttpUtility.UrlEncode(reallyname,System.Text.Encoding.UTF8).Replace(
"
+
"
,
"
%20
"
));
Response.AppendHeader(
"
Content-Length
"
, info.Length.ToString());
Response.WriteFile(FullFileName);
Response.Flush();
Response.End();
}
有以下几点要注意:
1.使用System.Text.Encoding.UTF来支持中文。(否则加上文件名中包含中文名下载的文件名就成了乱码)
2.使用Replace方法来吧"+"替换成" "(空格),"+"是使用HttpUtility.UrlEncode编码后就把空格转换成了"+",然而"+"不能被浏览器理解为空格无法进行解码,所以需要手动的吧"+"换成空格(%20)(因为空格的字符编码在浏览器中识别为"%20).这是一个非常容易忽略的小bug,幸好测试人员及早发现了它,我用Replace方法把它修正了。
页面的效果如下:
当用鼠标左键单击下载链接时候出现:
当用鼠标右键另存为时候,也会出现如上所示的下载对话框:上传的文件名为XmlSerializer.txt,下载下来的文件名也是XmlSerializer.txt问题解决。
下载文件一般用标签A来实现,链接到一个新的页面,如上的(downfile.aspx页面),如果连接到当前页面通过onclick来触发服务器端的事件实现下载。那样的话只能支持通过鼠标左键单击下载,而不能通过右击另存为下载。(朋友们可以试一试)
这里我不就不演示了。
最后希望这篇文章对朋友们有所帮助,帮助别人是我最大的快乐。当然也希望朋友们能指出问题共同交流。谢谢。
MSN:[email protected]
Email:[email protected]
2008.9.8补充:项目中运行了这中方式下载,不久测试人员发现了问题:当文件太大的时候,下载很慢,甚至下载不下来。通过参考相关的资料,对代码进行了优化,下面学习心得。供朋友们分享。
Response.WriteFile()原理:服务器会先把所有内容加载到内存,然后输出
所以如果遇到大文件的话,而且如果服务器上的内存比较小时候,就会出现文件不能下载的情况。因此可以用FileStream,直接写到Web输出流中去。
建议下面的代码,将文件分块实现:
Code
Response.BufferOutput
=
false
;
Response.Clear();
Response.AppendHeader(
"
Content-Disposition
"
, (
@"
inline; filename=
"
+
"
filename
"
));
Response.ContentType
=
"
application/unknown
"
;
Response.CacheControl
=
"
Private
"
;
Stream stm
=
new
FileStream(
"
d:\\download\\test.txt
"
,FileMode.Open);
BinaryReader br
=
new
BinaryReader(stm);
byte
[] bytes;
for
(Int64 x
=
0
;x
<
(br.BaseStream.Length
/
10000
+
1
);x
++
)
{
bytes
=
br.ReadBytes(
10000
);
Response.BinaryWrite(bytes);
}
stm.Close();
特别注意:如果下载的文件大小只有0KB的话,当程序调用上面的代码时候,会出现异常:
因此为了考虑全面,应该先判断文件大小,当文件大小为0时候应该用Response.WriteFile()来实现,如果不为0时候就用上面的代码来实现分块下载。