首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

ASP.NET 處理檔案名稱包含特殊字元 (#、?、空白) 的 URI 編碼問題與解決方案

     編輯:ASP.NET 基礎

問題現象

上傳/下載檔案時,若檔名包含 `#` (如 `report#2024.pdf`),使用 `new Uri(url)` 會導致 `#` 後內容被視為 **Fragment (錨點)** 而截斷,造成檔名錯誤、下載失敗。

根本原因

Uri uri = new Uri("https://server/files/report#2024.pdf");
// uri.AbsoluteUri      -> "https://server/files/report"
// uri.Fragment         -> "#2024.pdf"  (被當作錨點)
// uri.LocalPath        -> "/files/report"  (遺失 # 後內容)

`Uri` 類別依照 RFC 3986 解析,`#` 為片段識別碼分隔符,**不可直接出現在路徑中**,必須經過 **Percent-Encoding (URL 編碼)** 轉為 `%23`。

解決方案比較

方案一:UriBuilder (推薦,.NET 內建)

public static Uri BuildSafeUri(string baseUrl, string fileName)
{
    var builder = new UriBuilder(baseUrl);
    // Path 需為絕對路徑,且檔名已編碼
    builder.Path = Path.Combine(builder.Path, Uri.EscapeDataString(fileName)).Replace('\\', '/');
    // Fragment 保留空白,避免殘留
    builder.Fragment = "";
    return builder.Uri;
}

// 使用
string baseUrl = "https://server/files/";
string fileName = "report#2024.pdf"; // 含特殊字元
Uri safeUri = BuildSafeUri(baseUrl, fileName);
// 產生:https://server/files/report%232024.pdf

方案二:HttpUtility.UrlEncode / Uri.EscapeDataString (手動編碼)

// 編碼檔名 (僅編碼路徑段落,不編碼 / : ? # 等保留字元)
string encodedName = Uri.EscapeDataString(fileName); // # -> %23, 空白 -> %20
// 或
string encodedName = HttpUtility.UrlEncode(fileName, Encoding.UTF8); // 空白 -> + (需注意)

string fullUrl = baseUrl.TrimEnd('/') + "/" + encodedName;
Uri uri = new Uri(fullUrl);

方案三:修正既有 Uri 物件 (原文修補法)

public static Uri FixUriFragmentIssue(Uri originalUri)
{
    // 若原始字串含 # 且未編碼,Fragment 會被錯誤解析
    if (!originalUri.OriginalString.Contains("#")) return originalUri;

    var builder = new UriBuilder
    {
        Scheme = originalUri.Scheme,
        Host = originalUri.Host,
        Port = originalUri.Port,
        // 關鍵:將 LocalPath + Fragment 合併為新 Path,並清空 Fragment
        Path = originalUri.LocalPath + originalUri.Fragment,
        Fragment = ""
    };
    return builder.Uri;
}

// 使用
Uri problematic = new Uri("https://server/files/report#2024.pdf");
Uri fixedUri = FixUriFragmentIssue(problematic);
// fixedUri -> https://server/files/report#2024.pdf (但實際傳輸時會自動編碼為 %23)
// 注意:此法僅修正 Uri 物件內部表示,實際發送請求前仍需確保 Path 正確編碼

完整上傳/下載實作範例

上傳 (HttpWebRequest)

public static void UploadFile(string serverUrl, string localFilePath, string fileName)
{
    // 1. 建構安全 URI
    var builder = new UriBuilder(serverUrl.TrimEnd('/'));
    builder.Path = Path.Combine(builder.Path, Uri.EscapeDataString(fileName)).Replace('\\', '/');
    Uri requestUri = builder.Uri;

    // 2. 建立請求
    var request = (HttpWebRequest)WebRequest.Create(requestUri);
    request.Method = "PUT"; // 或 POST
    request.Credentials = CredentialCache.DefaultCredentials;
    
    // 3. 讀取檔案並寫入請求流
    byte[] fileBytes = File.ReadAllBytes(localFilePath);
    request.ContentLength = fileBytes.Length;
    
    using (var reqStream = request.GetRequestStream())
    {
        reqStream.Write(fileBytes, 0, fileBytes.Length);
    }

    // 4. 取得回應
    using (var response = (HttpWebResponse)request.GetResponse())
    {
        if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created)
            throw new Exception($"上傳失敗:{response.StatusCode} {response.StatusDescription}");
    }
}

下載 (支援中文檔名、斷點續傳)

public static void DownloadFile(HttpResponse response, string serverFileUrl, string saveFileName)
{
    // 1. 正確編碼 URL
    var builder = new UriBuilder(serverFileUrl);
    // 假設 serverFileUrl 已包含正確編碼的路徑
    Uri requestUri = builder.Uri;

    // 2. 請求遠端檔案
    var request = (HttpWebRequest)WebRequest.Create(requestUri);
    request.Credentials = CredentialCache.DefaultCredentials;

    using (var remoteResponse = (HttpWebResponse)request.GetResponse())
    using (var remoteStream = remoteResponse.GetResponseStream())
    {
        // 3. 設定回應標頭 (關鍵:Content-Disposition 檔名編碼)
        response.Clear();
        response.Buffer = false;
        response.ContentType = remoteResponse.ContentType ?? "application/octet-stream";
        
        // RFC 5987 編碼:支援中文檔名
        string encodedFileName = HttpUtility.UrlEncode(saveFileName, Encoding.UTF8).Replace("+", "%20");
        response.AddHeader("Content-Disposition", 
            $"attachment; filename=\"{saveFileName}\"; filename*=UTF-8''{encodedFileName}");
        
        response.AddHeader("Content-Length", remoteResponse.ContentLength.ToString());
        response.AddHeader("Accept-Ranges", "bytes");

        // 4. 分塊傳輸
        const int bufferSize = 1024 * 64;
        byte[] buffer = new byte[bufferSize];
        int read;
        while ((read = remoteStream.Read(buffer, 0, bufferSize)) > 0 && response.IsClientConnected)
        {
            response.OutputStream.Write(buffer, 0, read);
            response.Flush();
        }
    }
    response.End();
}

特殊字元編碼對照表

字元URI 編碼說明
# (井號)%23片段識別碼分隔符,**路徑中必須編碼**
? (問號)%3F查詢字串分隔符,路徑中需編碼
空白%20 (或 +)路徑建議 %20,查詢字串可用 +
% (百分號)%25編碼指示符,自身必須編碼
& (アンド)%26查詢參數分隔符
= (等號)%3D查詢鍵值分隔符
中文/Unicode%E4%B8%AD%E6%96%87UTF-8 位元組序列各自編碼

最佳實踐總結

  1. 永遠使用 `UriBuilder` 或 `Uri.EscapeDataString` 建構含檔名的 URI,勿手動字串拼接。
  2. 檔名編碼統一使用 UTF-8,配合 `filename*=UTF-8''...` 標頭確保跨瀏覽器相容。
  3. 伺服端儲存檔案時:資料庫存原始檔名 (Unicode),實體檔案建議用 GUID/Hash 命名,避免檔案系統編碼問題。
  4. 測試案例:`file#1.txt`、`file?2.txt`、`file 3.txt`、`文件.pdf`、`file%20.txt`。

總結

檔名包含 `#` 等保留字元導致 URI 解析錯誤,是經典陷阱。掌握 `UriBuilder`、`Uri.EscapeDataString`、RFC 5987 `Content-Disposition` 編碼三大工具,即可徹底解決上傳/下載/連結產生中的特殊字元問題,確保跨平台、跨瀏覽器相容性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved