在 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();
下面是核心处理,应该置于上述代码同一文件或可访问的其他类:
view plaincopy to clipboardprint?
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();
}
/// <summary>
/// 判断字符是否需要使用特殊的 ToHexString 的编码方式
/// </summary>
/// <param name="chr"></param>
/// <returns></returns>
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;
}
/// <summary>
/// 为非 ASCII 字符编码
/// </summary>
/// <param name="chr"></param>
/// <returns></returns>
private static string ToHexString(char chr)
{
UTF8Encoding utf8 = new UTF8Encoding();
byte[] encodedBytes = utf8.GetBytes(chr.ToString());
StringBuilder builder = new StringBuilder();
for (int index = 0; index < encodedBytes.Length; index++)
{
builder.AppendFormat("%{0}", Convert.ToString(encodedBytes[index], 16));
}
return builder.ToString();
}
#endregion
/// <summary>
/// 根据文件后缀来获取MIME类型字符串
/// </summary>
/// <param name="extension">文件后缀</param>
/// <returns></returns>
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