关于此文章的更新[201203]:
去年业余时间开发的一个小项目中偶然发现,使用 .NET 内置的 Uri.EscapeUriString 编码即可解决编码错乱问题,且在各浏览器下都表现正常。具体代码,请参见已开源的 Online Temporary FileStorage 项目中的 Destination.aspx.vb 文件中的相关代码,该代码位于第 104 行的 DownloadFile 方法中。
您不必按照此文章下文所述的方法来解决这一问题。

Article Update[03/2012]
A .NET built-in method Uri.EscapeUriString is a better way to solve the prolem, we always can get correct effects even the client uses different browsers. The sample codes are available in the Destination.aspx.vb of the open source projcet of mine:  Online Temporary FileStorage, you can see the core method at line 104(which holds the DownloadFile method).
So it’s not necessary to follow the steps below in the original version of this article.

在 asp.net 项目中,我们可以很方便地使用 Response.WriteFile() 方法向客户端输出一个文件。

实际使用 asp.net 向客户端输出文件流时,却出现了异常:

1、空格问题,当原文件的文件名中含有空格时,将引发客户端获取到的文件名与服务器端不一致。Spaces cannot be supported by some browsers

2、中文字符乱码,准确的是非 ASCII 字符乱码,当原文件的文件名中含有非 ASCII 字符时,将引发客户端获取到的文件名错乱。Non-US-ASCII characters cause incorrect result

3、一些特殊字符不能被正常输出(当然这里我并不是那些不常见的符号)special symbols cannot be output correctly

注意,本文用 C# 代码解决了在目前四种流行浏览器中Asp.net 输出文件流时文件名的空格及中文字符乱码这两个问题。使用本文的代码,你将可以让 IE(Internet Explorer)、Opera、Firefox 及 Chrome 的用户享受到没有乱码且支持空格文件名的文件输出引擎,同时支持文件名中各种像“# $ % ^ &”等常见的符号,如 “Microsoft.Asp.Net.doc” 、“F ile;;!@%#^&y.doc” 这样的文件名也可以了。请看下图:

Two Problems occur when we output a file via stream in asp.net: Filename with special symbols(e.g: space; # @ ! $ ) or Non-US-ASCII characters either cannot be supported by some browsers or cause incorrect filename in client machine. After reading this article, you can provide perfect filename in the all four popular browsers (IE,that is Internet Explorer; Opera; Firefox and Chrome), even your filename contains Non-US-ASCII characters or & and Spaces; something else can excite you that you can even put any number of special symbols in your filename if you like from now on (e.g: the filename ”Microsoft.Asp.Net.doc”  and “F ile;;!@%#^&y.doc” can both be output correctly ).  See the following figure:

本文下面的内容将描述问题的具体表现,并对相关代码做一些解释;

如果你不需要阅读这些内容,你可以直接下载示例代码:SpecifiedFileHandler.rar (2.22 kb) (Click the link to download the sample source code (wirtten with C#))

本文提供了一个 HttpHandler 以处理文件下载请求,你可以把它按你的需要做修改;

I provided a HttpHandler to process requests for files in this article, you can modify this file for your certain use.

问题现象:

对于第一个问题

当原文件名包含空格时,默认将被改成下划线,即“_”;如果我们在输出文件时对文件名使用 UrlEncode() 对其进行编码,空格将变成加号,即“+”。

看下面几个图,分别是没有使用 UrlEncode() 编码文件名和使用了UrlEncode()  的时候,全英文的原文件名的文件输出到客户端的情况:

未进行 UrlEncode() 的英文文件名,包含空格的,IE 浏览器:

未进行 UrlEncode() 的英文文件名,包含空格的,Opera 浏览器:

已进行 UrlEncode() 的英文文件名,包含空格的,IE 浏览器:

已进行 UrlEncode() 的英文文件名,包含空格的,Opera 浏览器:

在 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 浏览器:

问题的解决

**我们可以总结如下规律:

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

为了正确地编码,我参考一位外国人士的代码,使用了并改进了16进制编码方法。

参考下面的代码,可以大部分的解决问题。由于 Firefox 不支持空格,所以以下代码对于 Firefox ,仍不能完成具有空格的文件名的正确输出:

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

                string path = request.PhysicalPath;//获取客户端请求的文件的物理地址

                if (File.Exists(path))

                  {

                        string fileName = System.IO.Path.GetFileName(path);   // 获取文件的名字

                        if (Request.UserAgent.Contains(“MSIE”) || Request.UserAgent.Contains(“msie”)) 

                           {         

                            // 如果客户端使用 Microsoft Internet Explorer,则需要编码

                            fileName = ToHexString(fileName);     // 如果使用  fileName =Server.UrlEncode(fileName);  则会出现上文中出现的情况

                           }

                        Response.AddHeader(“Content-Disposition”, “attachment;filename=” + fileName);

                        Response.WriteFile(path);

                        Response.End();

                 }

 

应该置于上述代码同一文件或可访问的其他类的几个函数:

        ///

        /// 为字符串中的非英文字符编码

        ///

        ///

        ///

        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();
        }

        ///


        ///指定 一个字符是否应该被编码
        ///

        ///


        ///
        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;
        }

        ///


        /// 为非英文字符串编码
        ///

        ///


        ///
        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();
        }

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

点击下载示例代码:SpecifiedFileHandler.rar (2.22 kb)

2.22 kb)](/files/2010/4/SpecifiedFileHandler.rar)