經常很多項目中都牽涉到檔案的上傳和下載,檔案上傳可以更改上傳控制項的外觀形式,
詳細參見: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:Charles.C.Chen@newegg.net
Email:Charles.C.Chen@newegg.com
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時候就用上面的代碼來實現分塊下載。