天天看點

關于下載下傳檔案是檔案名的空格顯示不出來和亂碼問題 轉

在 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