在 asp.net 項目中,我們可以很友善地使用 Response.WriteFile() 方法向用戶端輸出一個檔案。
實際使用 asp.net 向用戶端輸出檔案流時,卻出現了異常:
1、空格問題,當原檔案的檔案名中含有空格時,将引發用戶端擷取到的檔案名與伺服器端不一緻。
2、中文字元亂碼,準确的是非 ASCII 字元亂碼,當原檔案的檔案名中含有非 ASCII 字元時,将引發用戶端擷取到的檔案名錯亂。
3、一些特殊字元不能被正常輸出(當然這裡我并不是那些不常見的符号)
注意,本文用 C# 代碼解決了在目前四種流行浏覽器中Asp.net
輸出檔案流時檔案名的空格及中文字元亂碼這兩個問題。使用本文的代碼,你将可以讓 IE(Internet
Explorer)、Opera、Firefox 及 Chrome 的使用者享受到沒有亂碼且支援空格檔案名的檔案輸出引擎,同時支援檔案名中各種像“#
$ % ^ &”等常見的符号,如 "Microsoft.Asp.Net.doc" 、“F ile;;!@%#^&y.doc”
這樣的檔案名也可以了。請看下圖:
本文下面的内容将描述問題的具體表現,并對相關代碼做一些解釋;
如果你不需要閱讀這些内容,你可以直接下載下傳示例代碼。
問題現象:
對于第一個問題
在IE中,當原檔案名包含空格時,預設将被改成下劃線,即“_”;如果我們在輸出檔案時對檔案名使用 UrlEncode() 對其進行編碼,空格将變成加号,即“+”。
在 Opera 中,檔案名不需要經過 UrlEncode() 即可正确地解析,但注意經過了 UrlEncode() 後也與IE一樣,空格變成了加号。
很
遺憾, Firefox 似乎并不歡迎含有空格的檔案名,它會直接舍棄空格後面的部分。對于上圖中的例子,沒有進行 UrlEncode()
之前,Firefox 會得到一個“My.axd”的檔案名,可以看到,它對檔案類型把握并沒有錯誤(隻因為這由别外的部分負責);進行
UrlEncode() 之後,它的結果與 IE、Opera 等一緻,空格變成了加号。
對于第二個問題
第二個問題有點複雜了。
當原檔案名包含中文或其他非英文字元時,由于編碼的錯誤,預設情況很糟糕,竟然完全是無法辨識的亂碼;如果我們在輸出檔案時對檔案名進行 UrlEncode() 對其進行編碼,這些中文将能正确地被顯示;
但注意,問題并沒有完。在Opera 或 Firefox 中,不需要經過 UrlEncode() 即能正确地顯示了;不幸地是,如果經過了 UrlEncode(),它們将無法正确地解析。
看下面幾個圖,分别是沒有使用 UrlEncode() 編碼檔案名和使用了 UrlEncode() 的時候,全英文的原檔案名的檔案輸出到用戶端的情況:
未進行 UrlEncode() 的中文檔案名,IE 浏覽器:
已進行 UrlEncode() 的中文檔案名,IE 浏覽器:
已進行 UrlEncode() 的中文檔案名,Opera 浏覽器
至于 Firefox 與 Chrome 的圖就不貼了,它們與 Opera 基本一緻。
問題的解決
我們可以總結如下規律:
Internet Explorer 能在用戶端已經UrlEncode() 的字元,包括空格在内;而
Opera 等其他浏覽器可以解析未經 UrlEncode()
的直接輸出的字元(這意味着,對于使用Opera或其他用戶端的客戶,我們不應該對它進行 UrlEncode()編碼)
為了正确地編碼,我參考一位外國人士的代碼,使用并改進了16進制編碼方法。參考下面的代碼,可以大部分的解決問題。由于 Firefox 預設不支援中文,特别對 Firefox 使用者做了一些處理,在下面的代碼中能夠展現。
在輸出檔案地地方使用的代碼:
view plaincopy to clipboardprint?
01.if (context != null)
02.{
03. HttpRequest request = context.Request;
04. HttpResponse response = context.Response;
05. //本檔案使用了 QueryString 來傳遞檔案名,你也可以不使用
06. if (!string.IsNullOrEmpty(context.Request.QueryString["file"]))
07. {
08. //取得用戶端正在請求的檔案的實體路徑
09. //不使用 QueryString 時,你可以使用 request.PhysicalPath 擷取
10. string path = context.Server.MapPath("~/") +
11. context.Server.UrlDecode(context.Request.QueryString["file"]).Replace("/", "//").ToLower();
12. if (File.Exists(path))
13. {
14. string extension = Path.GetExtension(path);
15. response.ContentType = GetMimeType(extension);
16. string fileName = System.IO.Path.GetFileName(path);
17. if (request.UserAgent.ToLower().IndexOf("msie") > -1)
18. {
19. //當用戶端使用IE時,對其進行編碼;We should encode the filename when our visitors use IE
20. //使用 ToHexString 代替傳統的 UrlEncode();We use "ToHexString" replaced "context.Server.UrlEncode(fileName)"
21. fileName = ToHexString(fileName);
22. }
23. if (request.UserAgent.ToLower().IndexOf("firefox") > -1)
24. {
25. //為了向用戶端輸出空格,需要在當用戶端使用 Firefox 時特殊處理
26. response.AddHeader("Content-Disposition", "attachment;filename=/"" + fileName + "/"");
27. }
28. else
29. response.AddHeader("Content-Disposition", "attachment;filename=" + fileName);
30. response.WriteFile(path);
31. response.End();
32. return;
33. }
34. }
35.}
36.//正在請求的檔案不存在;Cannot find the specified file
37.context.Response.Clear();
38.context.Response.Write("the data you are wanting to get does not exsit.");
39.context.Response.End();
if (context != null)
{
HttpRequest request = context.Request;
HttpResponse response = context.Response;
//本檔案使用了 QueryString 來傳遞檔案名,你也可以不使用
if (!string.IsNullOrEmpty(context.Request.QueryString["file"]))
{
//取得用戶端正在請求的檔案的實體路徑
//不使用 QueryString 時,你可以使用 request.PhysicalPath 擷取
string path = context.Server.MapPath("~/") +
context.Server.UrlDecode(context.Request.QueryString["file"]).Replace("/", "//").ToLower();
if (File.Exists(path))
{
string extension = Path.GetExtension(path);
response.ContentType = GetMimeType(extension);
string fileName = System.IO.Path.GetFileName(path);
if (request.UserAgent.ToLower().IndexOf("msie") > -1)
{
//當用戶端使用IE時,對其進行編碼;We should encode the filename when our visitors use IE
//使用 ToHexString 代替傳統的 UrlEncode();We use "ToHexString" replaced "context.Server.UrlEncode(fileName)"
fileName = ToHexString(fileName);
}
if (request.UserAgent.ToLower().IndexOf("firefox") > -1)
//為了向用戶端輸出空格,需要在當用戶端使用 Firefox 時特殊處理
response.AddHeader("Content-Disposition", "attachment;filename=/"" + fileName + "/"");
else
response.AddHeader("Content-Disposition", "attachment;filename=" + fileName);
response.WriteFile(path);
response.End();
return;
}
}
}
//正在請求的檔案不存在;Cannot find the specified file
context.Response.Clear();
context.Response.Write("the data you are wanting to get does not exsit.");
context.Response.End();
下面是核心處理,應該置于上述代碼同一檔案或可通路的其他類:
01.#region 編碼
02.
03./// <summary>
04./// 對字元串中的非 ASCII 字元進行編碼
05./// </summary>
06./// <param name="s"></param>
07./// <returns></returns>
08.public static string ToHexString(string s)
09.{
10. char[] chars = s.ToCharArray();
11. StringBuilder builder = new StringBuilder();
12. for (int index = 0; index < chars.Length; index++)
13. {
14. bool needToEncode = NeedToEncode(chars[index]);
15. if (needToEncode)
16. {
17. string encodedString = ToHexString(chars[index]);
18. builder.Append(encodedString);
19. }
20. else
21. {
22. builder.Append(chars[index]);
23. }
24. }
25.
26. return builder.ToString();
27.}
28.
29./// <summary>
30./// 判斷字元是否需要使用特殊的 ToHexString 的編碼方式
31./// </summary>
32./// <param name="chr"></param>
33./// <returns></returns>
34.private static bool NeedToEncode(char chr)
35.{
36. string reservedChars = "$-_.+!*'(),@=&";
37.
38. if (chr > 127)
39. return true;
40. if (char.IsLetterOrDigit(chr) || reservedChars.IndexOf(chr) >= 0)
41. return false;
42.
43. return true;
44.}
45.
46./// <summary>
47./// 為非 ASCII 字元編碼
48./// </summary>
49./// <param name="chr"></param>
50./// <returns></returns>
51.private static string ToHexString(char chr)
52.{
53. UTF8Encoding utf8 = new UTF8Encoding();
54. byte[] encodedBytes = utf8.GetBytes(chr.ToString());
55. StringBuilder builder = new StringBuilder();
56. for (int index = 0; index < encodedBytes.Length; index++)
57. {
58. builder.AppendFormat("%{0}", Convert.ToString(encodedBytes[index], 16));
59. }
60.
61. return builder.ToString();
62.}
63.
64.
65.#endregion
66.
67.
68./// <summary>
69./// 根據檔案字尾來擷取MIME類型字元串
70./// </summary>
71./// <param name="extension">檔案字尾</param>
72./// <returns></returns>
73.static string GetMimeType(string extension)
74.{
75. string mime = string.Empty;
76. extension = extension.ToLower();
77. switch (extension)
78. {
79. case ".avi": mime = "video/x-msvideo"; break;
80. case ".bin":
81. case ".exe":
82. case ".msi":
83. case ".dll":
84. case ".class": mime = "application/octet-stream"; break;
85. case ".csv": mime = "text/comma-separated-values"; break;
86. case ".html":
87. case ".htm":
88. case ".shtml": mime = "text/html"; break;
89. case ".css": mime = "text/css"; break;
90. case ".js": mime = "text/javascript"; break;
91. case ".doc":
92. case ".dot":
93. case ".docx": mime = "application/msword"; break;
94. case ".xla":
95. case ".xls":
96. case ".xlsx": mime = "application/msexcel"; break;
97. case ".ppt":
98. case ".pptx": mime = "application/mspowerpoint"; break;
99. case ".gz": mime = "application/gzip"; break;
100. case ".gif": mime = "image/gif"; break;
101. case ".bmp": mime = "image/bmp"; break;
102. case ".jpeg":
103. case ".jpg":
104. case ".jpe":
105. case ".png": mime = "image/jpeg"; break;
106. case ".mpeg":
107. case ".mpg":
108. case ".mpe":
109. case ".wmv": mime = "video/mpeg"; break;
110. case ".mp3":
111. case ".wma": mime = "audio/mpeg"; break;
112. case ".pdf": mime = "application/pdf"; break;
113. case ".rar": mime = "application/octet-stream"; break;
114. case ".txt": mime = "text/plain"; break;
115. case ".7z":
116. case ".z": mime = "application/x-compress"; break;
117. case ".zip": mime = "application/x-zip-compressed"; break;
118. default:
119. mime = "application/octet-stream";
120. break;
121. }
122. return mime;
123.}
#region 編碼
/// <summary>
/// 對字元串中的非 ASCII 字元進行編碼
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static string ToHexString(string s)
{
char[] chars = s.ToCharArray();
StringBuilder builder = new StringBuilder();
for (int index = 0; index < chars.Length; index++)
bool needToEncode = NeedToEncode(chars[index]);
if (needToEncode)
string encodedString = ToHexString(chars[index]);
builder.Append(encodedString);
else
builder.Append(chars[index]);
return builder.ToString();
}
/// 判斷字元是否需要使用特殊的 ToHexString 的編碼方式
/// <param name="chr"></param>
private static bool NeedToEncode(char chr)
string reservedChars = "$-_.+!*'(),@=&";
if (chr > 127)
return true;
if (char.IsLetterOrDigit(chr) || reservedChars.IndexOf(chr) >= 0)
return false;
return true;
/// 為非 ASCII 字元編碼
private static string ToHexString(char chr)
UTF8Encoding utf8 = new UTF8Encoding();
byte[] encodedBytes = utf8.GetBytes(chr.ToString());
for (int index = 0; index < encodedBytes.Length; index++)
builder.AppendFormat("%{0}", Convert.ToString(encodedBytes[index], 16));
#endregion
/// 根據檔案字尾來擷取MIME類型字元串
/// <param name="extension">檔案字尾</param>
static string GetMimeType(string extension)
string mime = string.Empty;
extension = extension.ToLower();
switch (extension)
case ".avi": mime = "video/x-msvideo"; break;
case ".bin":
case ".exe":
case ".msi":
case ".dll":
case ".class": mime = "application/octet-stream"; break;
case ".csv": mime = "text/comma-separated-values"; break;
case ".html":
case ".htm":
case ".shtml": mime = "text/html"; break;
case ".css": mime = "text/css"; break;
case ".js": mime = "text/javascript"; break;
case ".doc":
case ".dot":
case ".docx": mime = "application/msword"; break;
case ".xla":
case ".xls":
case ".xlsx": mime = "application/msexcel"; break;
case ".ppt":
case ".pptx": mime = "application/mspowerpoint"; break;
case ".gz": mime = "application/gzip"; break;
case ".gif": mime = "image/gif"; break;
case ".bmp": mime = "image/bmp"; break;
case ".jpeg":
case ".jpg":
case ".jpe":
case ".png": mime = "image/jpeg"; break;
case ".mpeg":
case ".mpg":
case ".mpe":
case ".wmv": mime = "video/mpeg"; break;
case ".mp3":
case ".wma": mime = "audio/mpeg"; break;
case ".pdf": mime = "application/pdf"; break;
case ".rar": mime = "application/octet-stream"; break;
case ".txt": mime = "text/plain"; break;
case ".7z":
case ".z": mime = "application/x-compress"; break;
case ".zip": mime = "application/x-zip-compressed"; break;
default:
mime = "application/octet-stream";
break;
return mime;
此外,針對一些浏覽器做了一些特殊的處理,已經展現在本文示例代碼的注釋中。此代碼已經能非常完好地解決問題了,在 Internet Explorer 、Opera、Firefox 及 Chrome 中得到的體驗一緻,支援中文,支援空格的正常輸出。
如果複制代碼後運作不正常,可以參考在示例代碼檔案的處理情況,在這裡下載下傳示例代碼檔案,示例檔案是一個 HttpHandler,是以你可能需要為它在 Web.Config 中做相關配置,關于配置方法,請參考其他資料。你可以按你的需要來修改示例代碼
轉自:http://blog.csdn.net/ciznx/archive/2010/05/26/5625222.aspx