WebClient DownloadString() 編碼問題
| | 0 | | ![]() |
同事反映某段使用 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