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,代表此頁唯一個「附件檔」,如果你將 attchment 改成 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 瀏覽器」在下載的時會發生找不到檔案的狀況,如下圖示:

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

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

  

此文章由 will 發表於 2008/4/20 上午 12:03:49

永久連結 | 評論 (10) | 此文章的RSSRSS comment feed |

分類: ASP.NET | Web

標籤: , , , ,

收藏:

相關文章

評論

四月 20. 2008 01:13

TeYoU


HttpUtility.UrlEncode(fileName);

以上也有同樣效果... 這一招我用過耶 ^^

TeYoU tw

四月 20. 2008 10:39

Will 保哥

雖然 HttpUtility.UrlEncode(fileName); 與 HttpUtility.UrlPathEncode(fileName); 差不多,但是用途是有差的。

「網址的路徑(Path)」與「網址的參數(QueryString)」編碼方式不一樣!

路徑包括目錄名稱與檔案名稱的部分,要用 HttpUtility.UrlPathEncode 編碼。
參數的部分才用 HttpUtility.UrlEncode 編碼。

例如:空白字元( )用 HttpUtility.UrlPathEncode 會變成(%20),但用 HttpUtility.UrlEncode 卻會變成加號(+),而檔名中空白的部分用 %20 才是對的,否則存檔後檔名空白的部分會變成加號(+)那檔名就不對了。

Will 保哥 tw

四月 22. 2008 12:41

TeYoU

哇 原來如此阿
又學到了 ^^

TeYoU tw

五月 23. 2008 11:26

李武夷

ASP Code :

Response.AddHeader "Content-Disposition", "attachment; filename=" & Server.URLEncode(filename2)

其實Content-Disposition只不過是為 HTTP 增加一個 MIME 部署,attachment是告訴browser,接下來的內容是附檔, filename則是指定附檔名稱.這裡面只有String內容,將內容可以Format為任何編碼格式,
然後Browser端對這些內容進行解析.
可嘆的是,IE只支援 ANSI格式的Content-Disposition編碼,Unicode編碼則不支持,但是如果使用 URLEncode,則IE6能將它反 Encode,顯示出正確的下載文件名,但是保存時卻出現一個奇怪的現象,剛才顯示都還正確的文件名,保存時卻變成了亂碼,估計是OS本身Directory雖然可以保存Unicode格式的文件名,但是Explorer及 Common Dialog裡面支持的卻僅是ANSI編碼格式的文件名,所以 SaveFileDialog裡面的文件名輸入框是 OS本身CodePage的ANSI編碼格式的String了,現出轉換錯誤.
而IE7則干脆對 URLEncode後的格式不進行反Encode了.FireFox不存在這種問題,對UTF-8支持很好.

目前還沒找到 UTF-8兼容的,對IE繁簡兼容的解決方案.

李武夷 cn

五月 23. 2008 14:36

李武夷

非常抱歉上午所回復的內容沒有說明狀態,惹您生氣了,
實際上我的代碼是運行在 IFrame裡面的ASP(VBS)程式,
運行平台是 Win2K 繁體Server版/ 繁體IE6 / 繁體IE7 / 英文FireFox3 Beta5
運行結果是:在 Win2K繁體Pro版的 IE6上面點選下載,結果為文件下載彈出一個會話框,顯示正確的文件名(因不能上傳圖檔,我只能口述了),但是點選"存儲"按鈕時,文件名顯示成了亂碼.
改用Win2003繁體 IE7打開,文件出現過%e5%8c%af%e5%87%ba%e8%b3%87%e6%96%99%e6之類的樣式,但後來不知道改動了什么地方,錯誤不再出現,正在研究中.
在沒有IFrame 的情況下,不會出上述問題

源程式有3部分如下:
a.htm

<head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head><body>
asdfasdfasdfa
<br>
<iframe src="a.asp" id="f1" name="f1"
align="left" width=100% height=100% marginwidth=0
marginheight=0 frameborder="0" style="z-index:1; overflow: visible" >

對不起!您的瀏覽器不支援iframe!</iframe>
</body>


====================
filename.txt (簡體文字)
[quote]
万年历查询.htm


====================
a.asp
[quote]
<%
session.codepage=65001
Response.CharSet="UTF-8"

if trim(request("aa"))<>"" then
dim strfilename,filename1,filename2
set fs=CreateObject("Scripting.FileSystemObject")
Set f = fs.OpenTextFile("F:\www\Desktop\filename.txt", 1, True,-2)
filename1=f.readall
f.close
set f=nothihng
strFilename="F:\www\Desktop\" & filename1
Set s = Server.CreateObject("ADODB.Stream")
s.Open
s.Type = 1

on error resume next
Set fso = Server.CreateObject("Scripting.FileSystemObject")
if not fso.FileExists(strFilename) then
Response.Write("<h1>Error:</h1>File " & strFilename & " does not exist!<p>")
Response.End
end if
Set f = fso.GetFile(strFilename)
intFilelength = f.size
s.LoadFromFile(strFilename)
if err then
Response.Write("<h1>Error: </h1>" & err.Description & "<p>")
Response.End
end if

pLoc=instrrev(filename1,".")
if IsNull(pLoc) or pLoc=0 then '===Check Ext FileName
pLoc=len(filename1)
else
pLoc=pLoc-1
end if
filename2= """"& Server.URLEncode(left(filename1,pLoc)) & right(filename1,len(filename1)-pLoc) &""""
Response.AddHeader "Content-Disposition", "attachment; filename=" & filename2
Response.AddHeader "Content-Length", intFilelength
Response.ContentType = "application/octet-stream"
Response.BinaryWrite s.Read
Response.Flush
s.Close
Set s = Nothing
response.end
end if
%>
<a href="a.asp?aa=aa">開始下載</a> <br>
<%
response.write " <a href=""a.asp?aa=aa"">開始下載</a><br>"
%>

李武夷 cn

九月 14. 2008 19:10

ha

http://cape7.pixnet.net/blog/
你看這裡無論開啟或者下載都是正確的說

ha us

九月 15. 2008 09:32

will

我剛測試直接開啟檔名是 URLEncode 過的,不知道你說「都是正確的」是什麼意思?

will tw

九月 16. 2008 16:08

liu_da_shi

我的程序在IE6下有另一个问题。拿你的上传的文件来说吧( %e5%8c%af%e5%87%ba%e8%b3%87%e6%96%99%e6%aa%94080419.csv )。我下载的时候点击“開啟舊檔”后看到的文件名称多了“[1]”不知怎么回事儿。跪求答案!文件名中没有“,”号

liu_da_shi cn

九月 16. 2008 16:55

will

因為檔案會先儲存在你電腦的 TEMP 目錄下,只要遇到有重複名稱的檔案,就會自動加上 [1] , [2] , ... 以此類推。

will tw

九月 16. 2008 17:00

liu_da_shi

非常感谢!我用一个新文档试了一下,也是出现同样的问题。按说在Temp目录下不会有同名的文档才对啊,怎么还是给加了个"[1]".这样客户端每次都提示“找不到文件”
找了一天了 也没解决!

liu_da_shi cn

新增評論


(將顯示您的Gravatar圖示)  

  Country flag

[b][/b] - [i][/i] - [u][/u]- [quote][/quote]



線上預覽

一月 6. 2009 14:47