The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

ASP.NET 如何設定強制下載檔案並正確處理中文檔名的問題

我想一般人的作法都是透過設定 HTTP 回應 Content-Disposition 標頭(Header)的方式告知用戶端(Browser)強制下載檔案的,例如:

string fileName = "ExportData.csv";

string strContentDisposition = String.Format("{0}; filename=\"{1}\"", "attachment", fileName);

Response.AddHeader("Content-Disposition", strContentDisposition);

透過上述程式碼,就可以讓 Browser 強制下載此頁的內容,也就是該頁的內容(可能是文件或二進位檔案)不會直接在瀏覽器中開啟或下載後直接開啟相關程式(如:Office)。

其中 Content-Disposition 標頭的第一組參數是 attachment,代表此頁為一個「附件檔」,如果你將 attachment 改成 inline 的話,就代表這是一個內嵌與其他網頁內檔案(如:圖檔、CSS���JavaScript、Flash、...),而這也是「預設」的設定,所以也就等於不加上 Content-Disposition 標頭的情況。

而 Content-Disposition 標頭的第二組參數是 filename,也就是你可以指定下載檔案時預設的儲存檔名,在此範例中的下載檔名是 ExportData.csv (如下圖示)

 IE 強制下載檔案畫面

雖然這個小技巧很好用,不過當你的檔名內含「中文字」的時候,卻會發生以下錯誤:

設定強制下載檔案並使用中文檔名時的錯誤畫面

經測試發現,這個問題只會再 IE 出現,當我在使用 Firefox 的時候並不會有這個問題,嚴格算起來應該算是 IE 的 Bug。

我從 MSDN 的 HttpResponse.HeaderEncoding 屬性 說明文件發現 ASP.NET 在回應 HTTP 標頭的時候預設編碼是用 System.Text.UTF8Encoding 類別,但問題是 IE 瀏覽器無法正確解析 UTF-8 的 HTTP Header。從網路上得到的一般性解法就是特別指定 Response.HeaderEncoding 的編碼,因為 IE 瀏覽器在繁體中文的作業系統下可以支援的編碼是 Big5,所以照理說只要設定正確的編碼就能夠正確下載中文檔名,如下程式片段:

Response.HeaderEncoding = Encoding.GetEncoding("big5");

不過經過我測試的結果,這段 Code 在 ASP.NET Development Server 中執行是「完全無效」的,所有的中文字還是以 UTF-8 編碼輸出,但是同一段程式碼在 IIS 6.0 中卻可以正常執行,雖然可以正確輸出 Big5 編碼的 HTTP Header,但是下載後的檔名竟然有幾台機器會變成亂碼,雖然大多數主機下載的檔名是正確的,但此問題依然困擾著我,因為當你設定了 Big5 編碼後,中國大陸簡體中文的用戶又無法下載了,或是下載後檔名一樣變成亂碼。

為了解決這個問題,我不斷的上網尋找資料,不過都沒有人提出有別於設定 Response.HeaderEncoding 的作法,所以就一直試一直試,試了快 4 個鐘頭,結果看到頭暈眼花,還是放棄了。但今天突然靈機一動想說將中文檔名用 Server.UrlPathEncode 方法編碼看看,結果真的成功了!以下是程式碼範例:

string fileName = Server.UrlPathEncode("匯出資料檔080419.csv");

string strContentDisposition = String.Format("{0}; filename=\"{1}\"", "attachment", fileName);

Response.AddHeader("Content-Disposition", strContentDisposition);

此技巧不但可以正確下載中文檔名,且也不需要設定任何 Response.HeaderEncoding 就可以正常下載,同一段程式碼同時可以給任何支援 UTF-8 的作業系統下載,包括使用簡體中文的大陸用戶也可以正確下載檔案了,真是大快人心啊。

因為我們的目的是「要讓使用者能正確下載含有中文檔名的檔案」且目的也算是達成了,不過如果使用者直接在檔案下載視窗點選「開啟舊檔(O)」的話(如下圖):

檔案下載

IE 會先將該檔案暫存於 IE 的暫存目錄裡並且直接開啟該檔案,不過檔名卻會變成 %e5%8c%af%e5%87%ba%e8%b3%87%e6%96%99%e6%aa%94080419.csv (編碼過的檔名),如果使用者只是想開啟來看一下不存檔的話,那到沒什麼大礙,如果使用者按下「另存新檔」要儲存檔案時,那檔名就變的亂七八糟了,唉~ 殘念!這點真的無解!

另外我也在 Firefox 瀏覽器中測試,發現另存新檔或開啟檔案的檔名也一樣會變成編碼過的檔名( %e5%8c%af%e5%87%ba%e8%b3%87%e6%96%99%e6%aa%94080419.csv ),所以沒辦法一招半式闖江湖,我又調整了一下程式碼,讓 Content-Disposition 標頭中的檔名可以針對使用者透過 IE 瀏覽器下載時將檔案編碼:

string fileName = "匯出資料檔080419.csv";
if (Request.Browser.Browser == "IE") {
    fileName = Server.UrlPathEncode(fileName);
}
string strContentDisposition = String.Format("{0}; filename=\"{1}\"", "attachment", fileName);
Response.AddHeader("Content-Disposition", strContentDisposition);

這個版本應該是最終版了,除了 IE 開啟舊檔的問題無解外,其他應該是完美了。

這個技巧不只可以用在 ASP.NET,同樣的原理一樣也可以用在 JSP, PHP, Perl, Ruby, Python 或其他程式語言上。

2008/04/24 補充

我原本的程式在抓檔案的 HttpHandler 中有加上「不要快取」的設定,如下:

Response.Cache.SetNoStore();
Response.Cache.SetCacheability(HttpCacheability.NoCache);

但是如果使用者直接在檔案下載視窗點選「開啟舊檔(O)」的話,卻會造成「部分 IE 瀏覽器」在下載的時會發生找不到檔案的狀況,如下圖示:

下載的時會發生找不到檔案的狀況

其實我原本的用意是不要讓下載的檔案被瀏覽器快取住,但還是不要加上好了,以免造成使用者個困擾。