ASP.NET乱码深度剖析

8/10/2015来源:ASP.NET技巧人气:1538

asp.net乱码深度剖析

写在前面

在Web开发中,乱码应该算一个常客了。今天还好好的一个页面,第二天过来打开一看,中文字符全变“外星文”了。有时为了解决这样的问题,需要花上很长的时间去调试,直至抓狂,笔者也曾经历过这样的时期。有时虽然是“侥幸”解决了,但对其中的原理却一知半解。

为了弄清楚这个问题,今天查了大半天的资料、测试。现把这些点滴记录下来,以激励自己重视基础,同时和大家分享一下,望大家不吝批评指正。

预备知识

先介绍一些字符编码方面的基本知识,如果你对这些已经比较了解了,请直接跳过此节。

1. 字符集与字符编码概述

简单来说,字符集就是与特定区域相关的一系列有效字符的有序集合,比如字母、数字、标点符号等。注意关键字“有序”,表明集合中的每一个字符都是具有唯一数字编号(码值)的。不同国家使用的语言文字、符号不一样,相应的字符集必定也不一样。比如中国使用汉字,美国使用英语,韩国使用韩文,等等。

字符集是为了信息交互而设计的,最终还是要转化成计算机的表示法。我们知道,计算机只认识0和1,它对字符集符号不感冒。所以,我们必须想办法把字符转化为0和1的序列。我们知道,计算机最小的存储单位是位(bit),程序中一般使用的最小单位是字节(byte)。为了把字符存储到计算机中,我们就要考虑用几个byte几个bit,考虑每一个bit上是0还是1,考虑存储和读取效率,并且必须兼顾整个字符集,这就是字符编码

一句话,字符集只关心字符的定义,而字符编码负责字符的存储和读取细节。用三层模式来打比喻的话,字符集是模型层,而字符编码是业务层。注意:一般常说的GB2312、GBK等其实同时包含了这两方面的定义

2. 常用中文字符编码简介

GB2312

GB2312的全称是《信息交换用汉字编码字符集-基本集》,由国家标准总局于1980发布,1981年5月1日施行,中国大陆、新加坡使用此编码。基本集收录了6763个汉字,只能显示简体汉字。

GBK

1995颁布,全称是《汉字编码扩展规范》。在GB2312的其他上,增加了繁体汉字,支持ISO/IEC 10646-1 和GB-13000-1的全部中、日、韩(CJK)字符,共20902个。向下兼容GB2312。

GB18030

全称是《信息交换用汉字编码字符集基本集的扩充》,目前两个版本,分别于2000年和20005年颁布。该字符集收录了70000多个汉字,包括了藏、蒙古、维吾文等少数民族字符,是我国计算机系统必须遵循的基础性标准之一。向下兼容GBK和GB2312。

BIG5

台湾和港台地区使用的汉字编码,俗称“大五码”,共收录了13060个汉字。

UTF-8

这是目前使用最多的一种Unicode编码,是Visual Studio内置的编码,相信大家一定都不陌生。根据字符码值的不同,可能用1、2、3个字节表示。

注意,编码之间一般都不是兼容的。其它编码在此不作介绍,若想进一步了解字符编码,请看我收藏的一篇文章:http://blog.csdn.net/tomysea/article/details/6712344

3. 字符串、字符数组和字节数组

C#中的字符串(string)和字符(char)其实都是对象,他们有相应的类String和Char,string和char只是这两个类的一别名而已,内部都是采用Unicode码值表示。请注意我说的是码值,不是编码。

我们已经知道,Unicode的字符大多是多字节表示的,那么一个char就得用几个byte来表示。这里我要说的重点是,使用不同的编码表示字符串,其对应的byte可能是不一样的。请看下面的代码,注意输出字节数部分。UTF-8编码的字节数是22,而GB2312编码的字节数是16。

string title = "2012真的来了吗?"; //字符串

char[] chars = title.ToCharArray(); //字符数组

byte[] bytes = System.Text.Encoding.UTF8.GetBytes(title);

Response.Write(chars.Length + " "); //10 (字符数)

Response.Write(bytes.Length + " "); //22 (UTF-8编码的字节数)

bytes = System.Text.Encoding.GetEncoding("GB2312").GetBytes(title);

Response.Write(bytes.Length + " "); //16 (GB2312编码的字节数)

从http请求响应模型说起

http是一个请求/响应的模型,这个我们大家都知道。http请求可以分为请求头和请求实体两部分,相应地http响应也可以分为响应头和响应实体。请求头或响应头是浏览器与Web服务器通信用的(假定用浏览器访问Web服务器),而实体则是实际发送的数据,比如Form表单的数据、Ajax提交的数据、传回来的html代码等。不管是浏览器还是Web服务器,在发送实体前都会把它转换为字节流。明白这一点很重要,因为涉及字节流就一定会与字符编码有关。

从上面的请求响应模型中我们可以得出一个结论:请求和响应编码必须严格保持一致!为什么呢?这很好理解,浏览器和Web服务器是要通信的,如果编码不一样的话,势必会造成许多“误解”。假设浏览器是中国人(不懂E文),而Web服务器是美国人,他们两个的“编码”(语言)不一致,悲催的结局不言而喻。

ASP.NET中请求响应编码的设置

你可以在machine.config或web.config文件指定全局配置,也可以在页面级特别指定。如果你未手动指定且machine.config中也为空,则默认会读取计算机上“区域选项”中的设置。

1. 全局配置

在machine.config或web.config文件(根目录或者子目录都有效)中的system.web节点中配置globalization节点。如果在根目录下的web.config配置,则会响应整个网站,若只是在子目录下配置,则只会响应该目录及其子目录。 详细配置如下:

<system.web>

<globalization fileEncoding="utf-8" requestEncoding="utf-8" responseEncoding="utf-8"/>

<!--按顺序是:文件编码 请求编码 响应编码-->

<!—-fileEncoding会在后面说到-->

<!--后面还有其它配置-->

2. 页面级的配置

在aspx页面的Page指令中设置响应编码

<%@ Page Language="C#" AutoEventWireup="true" ResponseEncoding="utf-8"

CodeBehind="byte.aspx.cs" Inherits="DevKit.Web.test.charset._byte" %>

在aspx页面中手动指定meta标签

<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />

在后台cs文件中配置

Request.ContentEncoding = System.Text.Encoding.UTF8; //请求编码

Response.ContentEncoding = System.Text.Encoding.UTF8; //响应编码 接下来,我们从几个示例中去体验乱码,从而总结出解决乱码的一般方法。

测试环境

操作系统Windows xp PRofessional SP3 雨林木风版

开发环境:Visual Studio 2008 专业版 + SP1(.NET 3.5)

Web容器:VS集成的Development Server

浏览器:IE8 、Firefox 5

实例分析与研究

实例1 aspx页面提示意外的字符“XXX”,引号里面是乱码

背景

网站配置了在根目录配置了文件、请求、响应编码都为utf-8,页面成功编译,没有任务错误。详细错误见下图:

\

html代码

<div>

aspx页面中的中文

<br />后台的中文变量:<em><%=汽车%></em>

</div>

后台代码

view plain

public partial class _byte : System.Web.UI.Page

{

protected string 汽车= "我是凯迪拉克"; //别怀疑,中文变量是可以的:)

protected void Page_Load(object sender, EventArgs e)

{

//...

}

}

分析与解决

既然web.config已经配置了一样的请求响应编码,而且页面级别也没设置,可以排除这方面的问题了。注意到文件编码是UTF-8,会不是会文件编码引起的呢?(提示:这里的文件编码指的是保存文件时指定的编码,点击“另存为&hellip;”,在弹出的窗口中选择“编码保存”可以看到)。果然,此aspx页面的保存编码为GB2312,与web.config文件不一样,把它修改为UTF-8。

小提示:UTF-8有两种编码:UTF-8(带签名)和UTF-8(无签名)。带签名的UTF-8会在文件的开头写入“EF BB BF”(16进制),以标示自己采用的编码格式,这个标志称为BOM(Byte Order Mark),即字节序。打个比方,UTF-8(带签名)戴了校徽的学生,就算不认识他的人,一看校徽就明白了;而UTF-8(无签名)则是没戴校徽的。这里的校徽就是我们说的BOM,一个能够表明自己身份的标志。

\

小结

这是因为文件的保存编码与当前网站指定的文件编码不一致引起的,所以最佳实践是:手动在web.config中指定文件编码,并确保所有页面的保存编码与web.config一致。

其实最容易出这种问题的是js和CSS文件,如果你用其它工具(比如Dreamweaver)来编写这些文件却采用不同的编码保存,一旦文件包含中文就可能出这样的错误,导致js脚本错误,css无效!

实例2 跨页post提交时接收的Form数据变成了乱码

背景

有两个页面,注册页面(register.html)和处理注册的页面(handle.aspx),注册页面的表单信息以post方式提交到handle.aspx。根目录的配置的文件编码、请求编码和响应编码都是UTF-8。

register.html页面的关键html

<head>

<title></title>

<meta http-equiv="Content-Type" content="text/html;charset=gb2312" />

</head>

<body>

<form id="form1" name="form1" action="handle.aspx" method="post">

<input type="text" id="txtName" name="txtName" />

<input type="submit" id="btnSubmit" value="Post" />

</form>

handle.aspx页面关键后台代码

view plain

protected void Page_Load(object sender, EventArgs e)

{

string name = Request.Form["txtName"];

Response.Write(name);

}

错误信息如下

\

分析与解决

这是在提交表单信息过程产生的乱码,这里就涉及http请求和http响应的编码问题。我们上面说过,在请求时浏览器会把表单信息按指定编码转化成字节流发向Web服务器,在服务器ASP.NET会把这些字节流按指定的编码解码,以取得表单信息。那么我们就要检查这两个页面的编码了。仔细检查之后,发现register.html有这么一行“<meta http-equiv="Content-Type" content="text/html;charset=gb2312" />”,这里手动指定了页面编码为GB2312。问题很有可能就出在这里了,把本行删除之后,handle.aspx页面成功接收到表单信息。

没错,这就是由于两个页面的编码不一样引起。让我们再深入一点,仔细看看问题是怎么一步一步产生的吧。register.html的编码为GB2312,当我们点击了“Post”按钮时,浏览器会把“我是中文”这几个字按GB2312的方式编码成字节流,然后提到到handle.aspx页面。handle.aspx没有手动指定编码,那么他将会采用web.config里面的配置,为UTF-8。它收到请求后,用UTF-8编码解码字符流。由于请求用的是GB2312,而接收用的却是UTF-8,这样就导致乱码的产生。通过下面这幅图可以看到这个过程。

\

小结

所有的页面(不管是aspx,还是html,或其它)都必须使用相同的编码。如果涉及跨页提交,不管是get还是post,更应该严格保持相关页面编码的一致性。特别是跨站点提交时,更应该注意!

实例3  cookie存取发生乱码

背景

这是一个旧项目,现在决定增加一个自动登录的功能。详细过程是这样的:

在登录页面,用户登录成功后把用户名写到cookie中。这样,当用户再次访问时,就可以根据cookie判定用户是否已登录,从而实现自动登录。

登录成功后cookie是这样保存的

view plain

string userName = "cookie大侠"; //待保存的用户名

userName = HttpUtility.UrlEncode(userName); //编码特殊字符,如中文

HttpCookie cookie = new HttpCookie("userName", userName);

Response.Cookies.Add(cookie);

判断用户是否已登录时,代码是这样的

view plain

string userName = Request.Cookies["userName"].Value;

userName = Server.UrlDecode(userName);

Response.Write(userName); //总是获取不到cookie,所以决定打印出来看看

结果在测试读取cookie的时候,页面输出了乱码,如下图:

\

分析与解决

全球化信息是这样配置的

<gl