在這篇博客中,我將把我想到的一些與亂碼有關的經驗總結出來,供你們參考。javascript
在一個網站中,有些頁面會正常顯示,然而,有些頁面會顯示成亂碼。 若是發生這種狀況,能夠檢查一下web.config和文件編碼。html
若是web.config是這樣配置的:前端
<globalization fileEncoding="utf-8" />
而文件的編碼不是UTF-8:java
那麼就會有亂碼問題。jquery
注意:反之是否是會出現亂碼的。
1. 不設置fileEncoding,此時不會有亂碼現象。
2. fileEncoding="gb2312",文件以utf-8編碼,此時也不會有亂碼現象。web
所以,我建議最好讓全部文件都以UTF-8編碼保存,從而解決這類亂碼問題。ajax
AJAX技術流行了這麼多年了,我想如今沒有幾個網站不使用這種技術的。 然而,有些人在使用AJAX時,遇到了亂碼問題。數據庫
經過分析這類亂碼案例中,我發現幾乎都是採用這種方式向服務端提交數據: 「key1=」 + escape(value1) +「&key2=」 + escape(value2)瀏覽器
這種方法在多數狀況下,的確可以正常工做,然而遇到一些特殊字符,就行不通了。緣由我後面再來解釋。服務器
我爲這類不正確的方法準備了一個示例 (爲了保持示例簡單,我演示一個拼接URL),
頁面代碼以下:
<p><a id="link2" href="#" target="_blank">escape</a></p> <script type="text/javascript"> var str = "aa=1&bb=" + escape("fish li + is me.") + "&cc=" + escape("大明王朝1368"); $("#link2").attr("href", "/test_url_decode.ashx?method=escape&" + str); </script>
服務端的代碼就是從QueryString讀取那些參數值,而後輸出。因爲代碼實在太簡單,就不貼出了。(可下載示例代碼)
當我點擊連接時,服務端返回了這樣的結果:
注意:"fish li + is me." 中間的加號沒有了。
解決這個問題有個簡單的方法,那就是使用JQuery的$.param()方法,修改後的代碼以下:
<script type="text/javascript"> var myobject = { aa: 1, bb: "fish li + is me.", cc: "大明王朝1368" }; $("#link1").attr("href", "/test_url_decode.ashx?method=param&" + $.param(myobject)); </script>
另外,我很是反感拼接這種提交數據:「key1=」 + escape(value1) +「&key2=」 + escape(value2)
由於這種代碼的可讀性太差了,在此,我建議在AJAX調用時,最好直接使用JQuery的$.ajax方法向服務端提交數據。
請看下面的示例代碼(注意我爲data屬性賦值的方式):
<p><a id="btnTestParam" href="javascript:void(0);">Click me! 【點擊我】</a></p> <div id="divResult"></div> <script type="text/javascript"> $(function() { $("#btnTestParam").click(function() { $.ajax({ url: "/TestParam.ashx", type: "GET", cache: false, data: { id: 2, name: "fish li + is me.", tel: "~!@#$%^&*()_+-=<>?|", "x?x!x&x": "aa=2&bb=3&cc=漢字。", // 特殊的鍵名,值內容也特殊。 encoding: "見鬼去吧。?& :)", 中文鍵名: "大明王朝1368" }, success: function(responseText) { $("#divResult").html(responseText); } }); }); }); </script>
運行結果:
看過前面的示例,您有沒有想過:爲何escape不能解決的問題,JQuery就能解決呢?
對於這個問題,我想仍是先來看看MSDN中關於escape的說明(截圖):
MSDN說的很清楚,我也沒有必要再作解釋。
不過,我想有人可能會問:我用POST提交數據呢?那但是不通過URL的。
是的,POST數據時,參數沒有放在URL中,可是,仍然採用URL編碼。
POST數據也採用URL編碼,是由於:表單能夠採用GET方式提交,那麼數據將經過URL提交給服務器。
因此提交的數據都要通過URL編碼。
咱們再來看一下$.ajax是如何處理數據的提交過程的:
ajax: function( origSettings ) { var s = jQuery.extend(true, {}, jQuery.ajaxSettings, origSettings); // ............... 去掉一些無關的代碼 // convert data if not already a string if (s.data && s.processData && typeof s.data !== "string") { // 注意下面這個調用 s.data = jQuery.param( s.data, s.traditional ); }
再來看jQuery.param的實現過程:
// Serialize an array of form elements or a set of // key/values into a query string param: function( a, traditional ) { var s = []; // ............... 去掉一些無關的代碼 // If an array was passed in, assume that it is an array of form elements. if ( jQuery.isArray(a) || a.jquery ) { // Serialize the form elements jQuery.each( a, function() { add( this.name, this.value ); }); } else { // ............... 去掉一些非重點代碼 } // Return the resulting serialization return s.join("&").replace(r20, "+"); function add( key, value ) { // If value is a function, invoke it and return its value value = jQuery.isFunction(value) ? value() : value; s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); } }
這段代碼的核心就是add函數的實現了,它在內部調用了encodeURIComponent()函數。
咱們應該注意JQuery對數據的處理方式:encodeURIComponent(key) + "=" + encodeURIComponent(value);
JQuery在最後還把%20還替換成 + 號了。
在WEB開發領域,我想你們對JQuery的權威應該不用懷疑吧? 因此我認爲JQuery的方法確定是正確的。
從JQuery的實現方式也能夠看出:encodeURI()其實也是不推薦在編碼URL數據時使用的。
說到這裏,我要說說爲何不推薦使用encodeURI。
encodeURI用於對整個URL字符串進行編碼,若是某個參數值自己包含一些特殊字符。
例如:key = "x?x/x&x", value = "aa=2&bb=3&cc=漢字。",這個函數的結果將會不正確。
它一般用於編碼URL路徑中包含有相似漢字這種場合,不適合處理URL參數。
可是,URL路徑中的目錄名與文件名,咱們能夠選擇英文字符,因此encodeURI一般沒有機會使用。
前面介紹了JavaScript中三種URL的編碼方式,在服務端,ASP.NET有更多的URL編碼方法,
今天我也把服務端的編碼也作了個總結,由於我發現網上有些資料也是錯誤的。
在ASP.NET中提供三個URL編碼方法:HttpUtility.UrlPathEncode、HttpUtility.UrlEncode、Server.UrlEncode
.NET framework還提供了System.Uri這個類,它也有一些用於URL處理的方法。 好比EscapeUriString,EscapeDataString這二個方法,可用於URL路徑與參數的編碼任務。
面對這些方法,我該選擇哪一個?
我建議在 ASP.NET 中,編碼查詢參數 時選擇HttpUtility.UrlEncode(str) ,
而且在拼接URL時,採用 HttpUtility.UrlEncode(key) + "=" + HttpUtility.UrlEncode(value) 的方法。
若是要 編碼URL中的路徑,請使用HttpUtility.UrlPathEncode()
下面我來解釋前面不建議使用的另外的一些方法的緣由:
1. Server.UrlEncode: 這個方法其實也是調用HttpUtility.UrlEncode,只是它會盡可能使用Response.ContentEncoding所表示的編碼格式, 然而HttpUtility.UrlEncode(str)老是會使用UTF-8編碼,若是你不但願被字符編碼糾纏,那就應該放棄Server.UrlEncode , 畢竟UTF-8編碼纔是更好的選擇。
2. 雖然System.Uri的那二個編碼方法,也能實現咱們須要的URL編碼任務, 可是,當ASP.NET在填充Request.QueryString, Request.Form時,使用的解碼方法是HttpUtility.UrlDecode, 所以,若是你執意選擇使用System.Uri的相關的編碼方法,顯然就不能與解碼方法匹配,後果如何就難說了。
因爲編碼函數(方法)較多,並且又比較重要,我認爲有必要再作個總結。
一個完整的URL能夠簡單地認爲包含二個部分:文件路徑(含目錄) 和 查詢參數(QueryString)
在編碼時,必定要分開處理。
編碼文件路徑時,應該選擇 encodeURI,HttpUtility.UrlPathEncode 。
編碼查詢參數時,應該選擇 encodeURIComponent,HttpUtility.UrlEncode,並且拼接方式應該是:Encode(key) + "=" + Encode(value)
絕對不能先把整個URL(包含查詢參數)拼接起來了,再來考慮該選擇哪一個編碼方法。
再說一遍:在JavaScript中使用escape確定是錯誤的。
前面我建議在JavaScript中使用encodeURIComponent()來處理提交數據, 然而encodeURIComponent()在編碼字符時,使用的是UTF-8編碼。 也正由於這個緣由,有人可能會說:個人網站使用的編碼方式是gb2312 !
<globalization requestEncoding="gb2312" responseEncoding="gb2312" />
對於這個回答,我有時實在不想再說下去了:你就不能把網站的編碼改爲UTF-8嗎?
如今好了,我設計了一種方法,能夠解決在GB1212編碼的網站中使用encodeURIComponent(), 這個方法的設計思路比較直接:既然encodeURIComponent()是使用UTF-8編碼, 那麼,咱們是否是隻要告訴服務端,客戶端提交的數據是UTF-8編碼的,此時服務端只要識別後,按照UTF-8編碼來解碼,問題就解決了。
理清了思路,代碼其實很簡單。首先來看客戶端的代碼。
$.ajax({ // 注意下面這行代碼,它爲請求添加一個自定義請求頭 beforeSend: function(xhr) { xhr.setRequestHeader("x-charset", "utf-8"); }, url: "/TestParam.ashx", type: "GET", cache: false, data: { id: 2, name: "fish li + is me.", tel: "~!@#$%^&*()_+-=<>?|", "x?x!x&x": "aa=2&bb=3&cc=漢字。", // 特殊的鍵名,值內容也特殊。 encoding: "見鬼去吧。?& :)", 中文鍵名: "大明王朝1368" }, success: function(responseText) { $("#divResult").html(responseText); } });
注意:在原來的基礎上,我只加了一行代碼:
beforeSend: function(xhr) { xhr.setRequestHeader("x-charset", "utf-8"); },
再來看服務端代碼。我寫了一個HttpModule來統一處理這個問題。
public class ContentEncodingModule : IHttpModule { public void Init(HttpApplication app) { app.BeginRequest += new EventHandler(app_BeginRequest); } void app_BeginRequest(object sender, EventArgs e) { HttpApplication app = (HttpApplication)sender; HttpWorkerRequest request = (((IServiceProvider)app.Context) .GetService(typeof(HttpWorkerRequest)) as HttpWorkerRequest); // 注意:我並無使用 app.Request.Headers["x-charset"] // 由於:絕大部分程序不訪問它,它將一直保持是 null, // 若是我此時該問這個集合,會致使填充它。 // 我認爲填充Headers集合比我下面的調用的成本要高不少, // 因此,直接經過HttpWorkerRequest讀取請求頭對性能的損耗會最小。 string charset = request.GetUnknownRequestHeader("x-charset"); if( string.Compare(charset, "utf-8", StringComparison.OrdinalIgnoreCase) == 0 ) // ASP.NET在填充QueryString,Form時,會訪問Request.ContentEncoding作爲解碼時使用的字符編碼 app.Request.ContentEncoding = System.Text.Encoding.UTF8; }
改造後的結果是:除非客戶端明確添加"x-charset"請求頭,不然仍是按原來的方式處理,對於服務端代碼來講,徹底不用修改。
說明:
1. 若是網站的提交所有采用JQuery,也能夠統一設置,這是JQuery支持的功能。
2. 若是使用JQuery1.5以上版本,也能夠寫成:headers: {"x-charset" : "utf-8"}
3. 就算之後網站使用UTF-8編碼,全部代碼不須要作任何修改。
前段時間,有人在博客的評論中問我:asp.net服務器端寫中文cookie,js客戶端讀取時亂碼。
其實這個問題仍是比較好解決的,方法是:寫Cookie時用HttpUtility.UrlEncode編碼,而後在客戶端使用decodeURIComponent把內容轉回來就能夠了。 在此,我推薦使用jquery.cookie.js這個插件來讀寫Cookie。 示例代碼以下(前端):
$(function() { var cookie = $.cookie("TestJsRead"); $("#cookieValue").text(cookie); });
服務端代碼:
cookie = new HttpCookie("TestJsRead", HttpUtility.UrlEncode("大明王朝1368")); Response.Cookies.Add(cookie);
有時咱們須要在程序運行時動態的建立文件,並讓用戶下載這個在運行時產生的文件, 然而,有時候用戶會要求程序能生成一個默認的文件名,方便他們保存。 此時,咱們只須要設置Content-Disposition這個響應頭,並給一個默認的文件名就能夠了。
通常說來,咱們只要讓默認的下載文件名是英文及數字,問題永遠不會出現, 可是,有時候用戶可能要求默認的文件中包含漢字, 最終,問題也隨之發生了。 請看下面的代碼:
public void ProcessRequest(HttpContext context) { byte[] fileContent = GetFileContent(); context.Response.ContentType = "application/octet-stream"; string downloadName = "ClownFish性能測試結果.xlsx"; string headerValue = string.Format("attachment; filename=\"{0}\"", downloadName); context.Response.AddHeader("Content-Disposition", headerValue); context.Response.OutputStream.Write(fileContent, 0, fileContent.Length); }
這段代碼在個人FireFox, Opera, Safari, Chrome都能正常運行,其中FireFox顯示的下載對話框也是我期待的樣子:
遺憾的是,在個人IE8中是這樣的:
對於這個亂碼問題,咱們須要把代碼作一點修改:
string downloadName = "ClownFish性能測試結果.xlsx"; if( context.Request.Browser.Browser == "IE" ) downloadName = HttpUtility.UrlPathEncode(downloadName);
此時IE顯示的文件名就不是亂碼了。
說明:個人機器環境是 Windows Server 2003 SP2, 用於測試的瀏覽器版本分別爲:
如今還有一種亂碼問題是:同一個程序供多種不一樣字符集(語言)的用戶在使用。
例如:程序是簡體中文的,此時,繁體中文的用戶沒法保存繁體漢字(就算簡體漢字能正常顯示)。
當發現這種現象時,須要檢查一下數據庫的字段類型,是不是Unicode或者UTF-8, 由於當數據字段的字符集不支持多種語言時,亂碼問題一定產生。
我建議在使用SQL SERVER時,保存文字的字段都使用N開頭的類型, 如:nvarchar, nchar,除非明確知道要保存郵政編碼或者md5值,纔有必要使用char(xxx)這種數據類型。 相似的,在MySQL中,我建議使用UTF-8。
ASP.NET的亂碼問題通常與二個因素有關:
1. 選擇了不恰當的字符編碼,如:gb2312
2. 選擇了不正確的URL編碼方法,如:escape()
所以,解決方案其實也不難:
1. 字符編碼選擇 utf-8 ,包含文件編碼,請求/響應編碼,數據庫字段類型。
2. URL編碼方法選擇encodeURIComponent,再次強烈推薦直接使用JQuery
我一直認爲:正確的方法可讓我在無形中避開許多問題。
若是你還爲亂碼問題而煩惱,我建議你先想一想你是否選擇了不正確的編碼(方法)。