12.1 文件上传与下载
文件上传与下载是开发者经常实现的操作,如用户修改个人资料时需要更改个人头像,将更改后的文件重新上传到服务器。本节将分别介绍文件上传与文件下载。
12.1.1 文件上传
ASP.NET中提供了一个FileUpload控件,该控件显示一个文本框和一个【浏览】按钮,使用户可以选择客户端上的文件并将它上传到Web服务器。用户通过在控件的文本框中输入本地计算机上文件的完整路径(如C:\MyFiles\TestFile.txt)来指定要上载的文件。也可以通过单击【浏览】按钮,然后在【选择文件】对话框中定位文件来选择文件。
用户选择要上传的文件后,FileUpload控件不会自动将该文件保存到服务器。开发者必须显式提供一个控件或机制,使用户能提交指定的文件。如可以提供一个按钮,用户单击它即可上载文件。为保存指定文件所写的代码应调用SaveAs()方法,该方法将文件内容保存到服务器上的指定路径。通常,在引发回发到服务器的事件处理方法中调用SaveAs()方法。
除了SaveAs()方法外,FileUpload控件提供了一系列的属性,常用属性如表12-1所示。
表12-1 FileUpload控件的常用属性
在表12-1中,PostedFile属性返回HttpPostedFile对象,该对象提供对客户端已上载的单独文件的访问,它的常用属性如下。
(1)ContentLength:获取上载文件的大小(以字节为单位)。
(2)ContentType:获取客户端发送的文件的MIME内容类型。
(3)FileName:获取客户端上的文件的完全限定名称。
(4)InputStream:获取一个Stream对象,该对象指向一个上载文件,以准备读取该文件的内容。
【范例1】
完成用户注册功能,通过FileUpload控件选择用户头像,然后将选择的头像上传到指定的服务器,并保存到数据库中。步骤如下。
(1)创建一个模拟实现用户注册的页面,在表单中添加七行两列的表格,前三行提供用户名输入框、联系电话输入框和用户头像选择,第四行提供操作按钮。主要代码如下。
<asp:TextBox ID="txtName" runat="server"></asp:TextBox><asp:RequiredFieldValidator ID="rfvName" runat="server" ControlToValidate="txtName"> 必须输入</asp:RequiredFieldValidator> <asp:TextBox ID="txtPhone" runat="server" TextMode="Phone"></asp:TextBox><asp:RequiredFieldValidator ID="rfvPhone" runat="server" ControlToValidate="txtPhone">必须输入</asp:RequiredFieldValidator> <asp:FileUpload ID="fuUploadFile" runat="server" /> <asp:Button ID="btnUpload" runat="server" Text=" 上 传 " OnClick="btnUpload_Click" />
(2)在表格的后三行中,都包含一个Label控件,第一个Label控件用于显示结果,第二个Label显示上传文件的信息,第三个Label显示图像。代码如下。
<asp:Label ID="lblResult" runat="server"></asp:Label> <asp:Label ID="lblInfo" runat="server"></asp:Label> <asp:Image ID="imgShow" runat="server" Width="300" Height="200" />
(3)为页面中的Button控件添加Click事件代码,首先获取用户在页面中输入的用户名和联系电话。代码如下。
protected void btnUpload_Click(object sender, EventArgs e) { string userName = txtName.Text; //获取用户名 string userPhone = txtPhone.Text; //获取电话 /* 省略其他代码 */ }
(4)通过FileUpload控件的HasFile属性判断是否已有选择文件,如果是则通过ContentType属性获取文件内容类型,如果类型符合要求,则获取上传文件的路径、文件名称和服务器端指定文件的路径。代码如下。
if (fuUploadFile.HasFile) { //判断是否选择文件 string fileContentType = fuUploadFile.PostedFile.ContentType; //获取文件内容类型 if (fileContentType == "image/bmp" || fileContentType == "image/gif" || fileContentType == "image/pjpeg"){ //判断类型是否符合条件 string name = fuUploadFile.PostedFile.FileName; //客户端文件路径 FileInfo file = new FileInfo(name); string fileName = file.Name; //文件名称 string webFilePath = Server.MapPath("fileupload/" + fileName); //服务器端文件路径 /* 省略其他代码 */ } else { lblResult.Text = "提示:文件类型不符"; } }
(5)通过File.Exists()方法判断webFilePath路径中是否已经存在文件,如果不存在则调用SaveAs()方法保存文件,并显示文件信息。然后再调用SqlHelper类中的ExecuteNonQuery()方法将用户输入的数据提交到数据库。代码如下。
if (! File.Exists(webFilePath)) { //判断相同文件是否存在 try { fuUploadFile.SaveAs(webFilePath); //使用SaveAs()方法保存文件 lblResult.Text = "提示:文件" + fileName + "上传成功!路径是:" + "fileupload/" + fileName; lblInfo.Text = "文件大小:"+file.Length +"字节<br/>文件扩展名:" + file.Extension + "<br/>"; imgShow.ImageUrl = "fileupload/" + fileName; string sql = "INSERT INTO RegisterUserInfo (UserName, UserPhone, UserImage) VALUES (@name, @phone, @image)"; SqlParameter[] para = new SqlParameter[]{ new SqlParameter("@name", userName), new SqlParameter("@phone", userPhone), new SqlParameter("@image", "~/UploadDown/fileupload/"+fileName) }; SqlHelper.ExecuteNonQuery(SqlHelper.connString, CommandType. Text, sql, para); } catch (Exception ex) { lblResult.Text = "提示:文件上传失败,失败原因:" + ex.Message; } } else { lblResult.Text = "提示:文件已经存在,请重命名后上传"; imgShow.ImageUrl = "fileupload/" + fileName; }
(6)运行页面输入内容后单击按钮进行测试,如图12-1所示。
图12-1 文件上传
注意
实现文件上传时最好使用IE 10及其版本以下的浏览器,因为在这个浏览器中FileUpload控件的FileName属性能够获取到完整的路径。如果IE浏览器禁用,不包含上传路径,那么可以在【Internet选项】|【安全】|Internet|【自定义设置】里启用将文件上传到服务器时包含本地目录路径。
在文件上传的过程中,文件数据作为页面请求的一部分,上传并缓存到服务器的内存中,然后再写入服务器的物理硬盘中。实现文件上传时,需要注意以下三点。
1.确认是否包含文件
在调用SaveAs()方法将文件保存到服务器之前,通过HasFile属性验证FileUpload控件是否确实包含文件。如果HasFile属性的返回值为true,则调用SaveAs()方法。
2.文件上传大小限制
默认情况下,上传文件大小限制为4096KB,即4MB。可以通过设置httpRuntime元素的maxRequestLength属性来允许上传更大的文件。如果要增加整个应用程序所允许的最大文件大小,需要设置Web.config文件中的maxRequestLength属性。如果要增加指定页所允许的最大文件大小,需要设置Web.config文件中location元素内的maxRequestLength属性。
3.上传文件夹的写入访问权限
应用程序可以通过两种方式获取写入访问权限。开发者可以将要保存上传文件的目录的写入访问权限显式授予运行应用程序所使用的账户;也可以提高为ASP.NET应用程序授予的信任级别。
12.1.2 文件下载
文件下载是与文件上传相反的过程。文件上传是将文件从客户端保存到服务器端,而文件下载则将文件从服务器端下载到客户端。文件下载主要通过Response对象的相关属性和方法来实现。主要步骤如下。
(1)通过ContentType属性设置输出流的类型。
(2)调用AddHeader()方法定义文件下载中的应答头。
(3)执行文件下载操作。
(4)释放资源。
【范例2】
获取当前程序目录下fileupload文件夹中的所有文件信息,包括文件名称、扩展名、文件大小和上传时间,然后单击文件的链接进行下载。步骤如下。
(1)在页面中添加Repeater控件,为该控件添加ItemTemplate模板。代码如下。
<asp:Repeater ID="Repeater1" runat="server"> <ItemTemplate> <table width="90%" align="center" border="1" cellpadding="1" cellspacing="0" bgcolor="#e1e1e1" class="title_font"> <tr> <td class="title_font" width="10%" align="center">文件名称: </td> <td><asp:Label ID="FileTitle" runat="server" Text='<%#Eval("Name") %>'></asp:Label></td> <td width="10%" align="center">扩展名:</td> <td width="6%" align="center"><%#Eval("Extension") %></td> <td width="10%" align="center">文件大小:</td> <td width="8%" align="center"><%#Eval("Length") %>KB</td> <td width="10%" align="center">上传时间:</td> <td><%#Eval("CreateTime") %></td> <td width="10%" colspan="2" align="center"><asp:LinkButton ID="LinkButton1" CommandArgument='<%#Eval("Name") %>' runat= ="server" OnClick="LinkButton1_Click">下载文件 </asp:LinkButton></td> </tr> </table> </ItemTemplate> </asp:Repeater>
(2)在页面的Load事件中添加代码,页面首次加载时获取fileupload文件夹下的所有文件,将绑定到Repeater控件。代码如下。
protected void Page_Load(object sender, EventArgs e) { if (! Page.IsPostBack) { //如果首次加载 string downpath = Server.MapPath("fileupload"); //返回指定的文件路径 DirectoryInfo dirinfo = new DirectoryInfo(downpath); //创建DirectoryInfo对象 if (! dirinfo.Exists) //如果目录不存在 ClientScript.RegisterStartupScript(GetType(), "", "<script> alert(’文件目录不存在’)</script>"); else { FileInfo[] filist = dirinfo.GetFiles(); //获取该目录下的所有文件 IList<Down> downinfo = new List<Down>(); //文件列表集合对象 foreach (FileInfo fi in filist) { //遍历列表对象 Down di = new Down(fi.Name, fi.Extension, fi.Length.ToString(), fi.CreationTime); downinfo.Add(di); } Repeater1.DataSource = downinfo; //指定数据源 Repeater1.DataBind(); } } }
在上述代码中,DirectoryInfo类专门针对目录处理,FileInfo类专门针对文件处理。Down是文件下载类,包含对文件名称、文件扩展名、文件大小和上传时间等字段的封装。
(3)为Repeater控件中的LinkButton控件添加Click事件,在事件代码中获取用户要下载的文件,然后通过File.Exists()方法判断文件是否存在,如果存在则下载。代码如下。
protected void LinkButton1_Click(object sender, EventArgs e) { string downFile = ((LinkButton)sender).CommandArgument; string path = Server.MapPath("fileupload") + "\\" + downFile; //服务器端下载文件的路径 if (File.Exists(path)) { FileInfo fi = new FileInfo(path); Response.ContentEncoding = System.Text.Encoding. GetEncoding ("UTF-8"); //解决中文乱码 Response.AddHeader("Content-Disposition", "attachment; filename= "+Server.UrlEncode(fi.Name)); //将HTTP头添加到输出流 Response.AddHeader("Content-length", fi.Length.ToString()); Response.ContentType = "application/octet-stream"; //设置输出流的类型 Response.WriteFile(fi.FullName); //将指定文件的内容作为文件块直接写入HTTP响应输出流 Response.End(); } else Page.ClientScript.RegisterStartupScript(GetType(), "", "<script> alert(’你要下载的文件不存在,可以地址发生改变。请确认后下载!')</script>"); }
在上述代码中,通过Response对象的AddHeader()方法设置HTTP标头名称和值。ContentType属性用于设置输出流的类型。WriteFile()方法表示将指定文件的内容写入到HTTP输出流中。
(4)运行页面查看效果,如图12-2所示。
图12-2 文件下载页面
(5)单击图12-2中某个文件后名称为“下载文件”链接进行下载,效果图不再显示。
【范例3】
范例2通过WriteFile()方法进行下载,除了这种方法外,还可以通过其他方式进行下载。如下代码以字符流的形式下载文件。
protected void Button4_Click(object sender, EventArgs e) { string fileName = "pic.zip"; //客户端保存的文件名 string filePath = Server.MapPath("DownLoad/aaa.zip"); //路径 FileStream fs = new FileStream(filePath, FileMode.Open); byte[] bytes = new byte[(int)fs.Length]; fs.Read(bytes, 0, bytes.Length); fs.Close(); Response.ContentType = "application/octet-stream"; Response.AddHeader("Content-Disposition", "attachment; filename="+HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8)); Response.BinaryWrite(bytes); Response.Flush(); Response.End(); }
无论是通过WriteFile()方法下载文件或是字符流下载文件,都用到AddHeader()方法设置HTTP标头名称和值,常设的标头的信息如表12-2所示。
表12-2 AddHeader()方法常设的标头信息