Rocho.J

人脑是不可靠的, 随时记录感悟并且经常重复!

 

[转] 解决Asp.net 实现文件下载时、输出文件时文件名的中文乱码和空格异常 --- http://blog.csdn.net/ciznx/article/details/5625222

在 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() 的中文文件名,IE 浏览器:

 

已进行 UrlEncode() 的中文文件名,IE 浏览器

 

 

已进行 UrlEncode() 的中文文件名,Opera 浏览器

已进行 UrlEncode() 的中文文件名,Opera 浏览器

 

 

 至于 Firefox 与 Chrome 的图就不贴了,它们与 Opera 基本一致。

 

 

问题的解决

我们可以总结如下规律:
 Internet Explorer 能在客户端已经UrlEncode() 的字符,包括空格在内;而 Opera 等其他浏览器可以解析未经 UrlEncode() 的直接输出的字符(这意味着,对于使用Opera或其他客户端的客户,我们不应该对它进行 UrlEncode()编码)

为了正确地编码,我参考一位外国人士的代码,使用并改进了16进制编码方法。参考下面的代码,可以大部分的解决问题。由于 Firefox 默认不支持中文,特别对 Firefox 用户做了一些处理,在下面的代码中能够体现。

 

在输出文件地地方使用的代码:

  1. if (context != null)  
  2. {  
  3.     HttpRequest request = context.Request;  
  4.     HttpResponse response = context.Response;  
  5.     //本文件使用了 QueryString 来传递文件名,你也可以不使用   
  6.     if (!string.IsNullOrEmpty(context.Request.QueryString["file"]))  
  7.     {  
  8.         //取得客户端正在请求的文件的物理路径   
  9.         //不使用 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();  

 

 

 
下面是核心处理,应该置于上述代码同一文件或可访问的其他类:

  1. #region 编码   
  2. /// <summary>   
  3. /// 对字符串中的非 ASCII 字符进行编码   
  4. /// </summary>   
  5. /// <param name="s"></param>   
  6. /// <returns></returns>   
  7. public static string ToHexString(string s)  
  8. {  
  9.     char[] chars = s.ToCharArray();  
  10.     StringBuilder builder = new StringBuilder();  
  11.     for (int index = 0; index < chars.Length; index++)  
  12.     {  
  13.         bool needToEncode = NeedToEncode(chars[index]);  
  14.         if (needToEncode)  
  15.         {  
  16.             string encodedString = ToHexString(chars[index]);  
  17.             builder.Append(encodedString);  
  18.         }  
  19.         else  
  20.         {  
  21.             builder.Append(chars[index]);  
  22.         }  
  23.     }  
  24.     return builder.ToString();  
  25. }  
  26. /// <summary>   
  27. /// 判断字符是否需要使用特殊的 ToHexString 的编码方式   
  28. /// </summary>   
  29. /// <param name="chr"></param>   
  30. /// <returns></returns>   
  31. private static bool NeedToEncode(char chr)  
  32. {  
  33.     string reservedChars = "$-_.+!*'(),@=&";  
  34.     if (chr > 127)  
  35.         return true;  
  36.     if (char.IsLetterOrDigit(chr) || reservedChars.IndexOf(chr) >= 0)  
  37.         return false;  
  38.     return true;  
  39. }  
  40. /// <summary>   
  41. /// 为非 ASCII 字符编码   
  42. /// </summary>   
  43. /// <param name="chr"></param>   
  44. /// <returns></returns>   
  45. private static string ToHexString(char chr)  
  46. {  
  47.     UTF8Encoding utf8 = new UTF8Encoding();  
  48.     byte[] encodedBytes = utf8.GetBytes(chr.ToString());  
  49.     StringBuilder builder = new StringBuilder();  
  50.     for (int index = 0; index < encodedBytes.Length; index++)  
  51.     {  
  52.         builder.AppendFormat("%{0}", Convert.ToString(encodedBytes[index], 16));  
  53.     }  
  54.     return builder.ToString();  
  55. }  
  56. #endregion   
  57. /// <summary>   
  58. /// 根据文件后缀来获取MIME类型字符串   
  59. /// </summary>   
  60. /// <param name="extension">文件后缀</param>   
  61. /// <returns></returns>   
  62. static string GetMimeType(string extension)  
  63. {  
  64.     string mime = string.Empty;  
  65.     extension = extension.ToLower();  
  66.     switch (extension)  
  67.     {  
  68.         case ".avi": mime = "video/x-msvideo"break;  
  69.         case ".bin":   
  70.         case ".exe":  
  71.         case ".msi":  
  72.         case ".dll":  
  73.         case ".class": mime = "application/octet-stream"break;  
  74.         case ".csv": mime = "text/comma-separated-values"break;  
  75.         case ".html":  
  76.         case ".htm":  
  77.         case ".shtml": mime = "text/html"break;  
  78.         case ".css": mime = "text/css"break;  
  79.         case ".js": mime = "text/javascript"break;  
  80.         case ".doc":  
  81.         case ".dot":  
  82.         case ".docx": mime = "application/msword"break;  
  83.         case ".xla":  
  84.         case ".xls":   
  85.         case ".xlsx": mime = "application/msexcel"break;  
  86.         case ".ppt":   
  87.         case ".pptx": mime = "application/mspowerpoint"break;              
  88.         case ".gz": mime = "application/gzip"break;  
  89.         case ".gif": mime = "image/gif"break;  
  90.         case ".bmp": mime = "image/bmp"break;  
  91.         case ".jpeg":   
  92.         case ".jpg":   
  93.         case ".jpe":   
  94.         case ".png": mime = "image/jpeg"break;  
  95.         case ".mpeg":   
  96.         case ".mpg":  
  97.         case ".mpe":   
  98.         case ".wmv": mime = "video/mpeg"break;  
  99.         case ".mp3":   
  100.         case ".wma": mime = "audio/mpeg"break;  
  101.         case ".pdf": mime = "application/pdf"break;  
  102.         case ".rar": mime = "application/octet-stream"break;  
  103.         case ".txt": mime = "text/plain"break;  
  104.         case ".7z":  
  105.         case ".z": mime = "application/x-compress"break;  
  106.         case ".zip": mime = "application/x-zip-compressed"break;  
  107.         default:  
  108.             mime = "application/octet-stream";  
  109.             break;  
  110.     }  
  111.     return mime;  
  112. }  

 

 

 此外,针对一些浏览器做了一些特殊的处理,已经体现在本文示例代码的注释中。此代码已经能非常完好地解决问题了,在 Internet Explorer 、Opera、Firefox 及 Chrome 中得到的体验一致,支持中文,支持空格的正常输出。

 

如果复制代码后运行不正常,可以参考在示例代码文件的处理情况,在这里下载示例代码文件,示例文件是一个 HttpHandler,因此你可能需要为它在 Web.Config 中做相关配置,关于配置方法,请参考其他资料。你可以按你的需要来修改示例代码。

 

本文版权:陈计节

转载请注明出处。

欢迎访问本文在 ciznx.com 的原地址:http://blog.ciznx.com/post/aspnetstreamdownloaddisplaynonunicodespacechar.aspx

posted on 2012-09-24 14:20  RJ  阅读(...)  评论(...编辑  收藏

导航

统计