同事反映某段使用 WebClient 抓網頁的共用函式傳回中文亂碼, 追查後發現我有個觀念錯了很久 - 我一直以為 WebClient.DownloadString() 會依據 HTTP Response Header 傳回的 Contenty-Type 自動決定編碼。

用以下 MVC 範例示範。Home/Index Action 用 WebClient.DownloadString() 取得 Home/Api Action 傳回 Content("中文測試ABC")。ASP.NET MVC 預設 Response 是用 UTF-8 編碼, 我刻意再加上 Content-Type text/html; charset=utf-8 明示。

public class HomeController : Controller
{
        
    string GetAbsUrl(string relativeUrl)
    {
        //REF: https://stackoverflow.com/a/9833745/
        var ub = new UriBuilder(Request.Url.AbsoluteUri)
        {
            Path = Url.Content(relativeUrl),
            Port = Request.Url.IsDefaultPort ? -1 : Request.Url.Port
        };
        return ub.ToString();

    }

    public ActionResult Index()
    {
        WebClient wc = new WebClient();
        var url = GetAbsUrl("~/Home/Api");
        var str = wc.DownloadString(url);
        var report = new StringBuilder();
        report.AppendLine("DownloadString=" + str);
        report.AppendLine("byte[]=" + BitConverter.ToString(Encoding.UTF8.GetBytes(str)));
        report.AppendLine("Encoding.Default=" + Encoding.Default.EncodingName);
        wc.Encoding = Encoding.UTF8;
        str = wc.DownloadString(url);
        report.AppendLine("DownloadString(Force UTF8)=" + str);
        return Content(report.ToString(), "text/plain");
    }

    public ActionResult Api()
    {
        return Content("中文測試ABC", "text/html; charset=utf-8");
    }
}

同場加映:順便示範了用 UriBuilder() 在 ASP.NET MVC 產生完整 URL(包含 http:、host)的簡潔做法。

依據實測結果,即使 Response 已指明是 UTF-8,DownloadString() 抓回的中文還是變亂碼,以 Encoding.GetBytes() 檢查傳回結果的 byte[], 驗證 Response 傳回編碼是 UTF-8 沒錯,故問題是出在 WebClient 解析錯誤。

WebClient 有個 Encoding 屬性,依據[微軟文件]((https://docs.microsoft.com/zh-tw/dotnet/api/system.net.webclient.encoding?view=netframework-4.8), Encoding 屬性用來決定 UploadString()、UploadStringAsync()、DownloadString()、DownloadStringAsync() 使用何種編碼轉換 byte[] 與 string。 其預設值為 Encoding.Default,在繁體中文 Windows 環境為 BIG5。設定 WebClient.Encoding = Encoding.UTF8 後,即可被正確取回中文字串。

回到這麼明顯的問題為何沒早早爆發,原因是共用函式大部分用 DownloadData() 取回 byte[] 再自己用 Encoding.UTF8.GetString() 解析, 只有少數情況會直接 DownloadString(),讓我的觀念模糊這麼久。

Reminder of always setting WebClient.Encoding when using DownloadString()/UploadString().


Comments

Be the first to post a comment

Post a comment