昨天在使用ASP.NET AJAX Control Tookit 1.0.10618的时候, 发现如果在Web.config中如下设置使用gb2312编码:
<globalization fileEncoding="gb2312" requestEncoding="gb2312" responseEncoding="gb2312" culture="zh-CN" uiCulture="zh-CN"/>
AJAX控件无法正常使用,错误为脚本库中出现的,提示为:
“出现了运行时间错误。是否要进行调试?行: 684 错误: 缺少'}'”
“行: 86 错误: 'AjaxControlToolkit'未定义”
经过搜索与试验,最终比较圆满的解决了这个问题,方案也找到了两种,比较完美的解决了ASP.NET AJAX Control Tookit 1.0.10618的乱码问题。本文记录下我解决这个问题的过程,有一点罗嗦,如果你只想知道结果的话,那就直接看文中强调的部分吧。
我首先在网上搜了一下,发现CSDN也有提类似问题的帖子,但是除了将编码改为utf-8之外没有其他的解决方案,而且还有人质疑为什么一定要用gb2312的编码。在我看来,使用gb2312可能有以下理由:
1、接收从其他页面发过来的GB2312编码的URL参数。虽然在URL中直接使用GB2312编码并不标准,但是目前相当多的页面都是直接通过GB2312来在URL中传递中文参数的。
2、对网页的Referrer进行访问来源统计的时候,很多网站的Referrer都是用GB2312编码的,页面用UTF-8的话无法得到含有中文(比如用户进行搜索的关键字)的真实的Referrer。
3、提交到其他使用GB2312的网页。比较典型的应用是在线支付网关,大多数在线支付网关都是只支持GB2312编码,如果你使用UTF-8提交内容就会产生乱码。
4、在增加AJAX功能之前网站一直使用GB2312,如果改编码会牵扯以前项目中很多的方面。
5、减少网络流量。因为GB2312用固定的两个字节存储中文,而UTF-8是不定长的编码,每一个汉字需要2-4个字节。
经过尝试,我发现在ASP.NET AJAX Control Toolkit中提供的示例网站上即使使用了GB2312编码,也可以正常使用。这时<globalization>标签中只有culture="zh-CN" uiCulture="zh-CN"这两个参数不一样,在示例网站中是culture="en-us" uiCulture="en"。在我的网站中,把uiCulture也改为"en"之后,AJAX Control Toolkit可以正常运行了。这样就得到了解决乱码问题的第一个方法:
方案一:将Web.config文件中<globalization>的uiCulture="zh-CN"改为uiCulture="en"。
经过使用Fiddler对修改前后的脚本进行比较,发现这个参数对网站实际的影响表现在AJAX Control Toolkit的国际化方面,参数值en所对应的输出片段为:
AjaxControlToolkit.Resources={
"PasswordStrength_InvalidWeightingRatios":"Strength Weighting ratios must have 4 elements","Animation_ChildrenNotAllowed":"AjaxControlToolkit.Animation.createAnimation cannot add child animations to type /"{0}/" that does not derive from AjaxControlToolkit.Animation.ParentAnimation",……
参数zh-CN的输出中相对应的片段为:
AjaxControlToolkit.Resources={
"PasswordStrength_InvalidWeightingRatios":"密码强度的权重比例必须有 4 种","Animation_ChildrenNotAllowed":"AjaxControlToolkit.Animation.createAnimation 无法加入一个不是派生自 AjaxControlToolkit.Animation.ParentAnimation 且类型为 {0} 的子动画",……
虽然经过简单的修改,避开了中文的编码问题,脚本不再报错了,可这样做局限性也很明显:这个方案只能用在没有国际化的网站中;而且要可以接受可能出现的英文提示。另外在类似Login、CreateUserWizard等控件中会自动根据uiCulture这个参数生成对应语言的界面,修改后会都变成英文。如果整个网站中这样的页面不是很多,可以在<%@ Page %>中加上一个UICulture="zh-CN"来单独为某一些页面配置UICulture而不影响全局。也可以将个别控件的样式、模板全部自定义,而不让其自动生成。
方案一十分简单,只是局限性太明显了,有没有更好的办法呢?由于AJAX Control Toolkit是一个开源项目,如果问题确实出在这里面,就给了我们自己修改Bug的机会。在下手之前我们需要知道使用UTF-8和GB2312编码的输出之间有哪里不一样,才能初步判断问题出在哪里。
查看GB2312的输出中未经解码的十六进制Response的内容,这一段内容如下:
一眼看去,选定部分所占用的字节数肯定超过了"密码强度的权重比例必须有 4 种"这个字符串字数的两倍,似乎这并不是GB2312的编码。经过确认,这一段代码果然是UTF-8编码的!很明显问题就在这里,Web.config明明选择的是GB2312,输出的脚本文件中的中文编码却是UTF-8,不出乱码才怪呢。可是将Web.config中的responseEncoding改为"UTF-8"之后,输出的中文也是用UTF-8编码的,内容一模一样:
经过使用文件比较工具Beyond Compare对比后,发现其他的不同之处不会引起错误,于是可以断定问题就在这里。
看到这里,相信跟Unicode打过交道的朋友们已经可以猜到,很可能是AJAX Control Toolkit在输出脚本的时候没有对当前所使用的编码进行判断,而统一使用了在.NET中作为默认值的UTF-8。有了这个假设,我们就可以在AJAX Control Toolkit的源代码中进行求证,确认后就可以尝试通过修改源代码来解决这个问题。
输出脚本是ToolkitScriptManager控件的责任,在ToolkitScriptManager.cs文件中发现了一个可疑的方法:
private static void WriteScripts(List<ScriptEntry> scriptEntries, TextWriter outputWriter)
这个方法通过调用System.Resources.ResourceManager来输出脚本资源,在项目的资源文件中也发现了"密码强度的权重比例必须有 4 种"等字符串,只是这个方法内部没有任何与编码有关的代码,而编码的设置应该在参数outputWriter传入这个方法之前就已经确定了,如果只修改这个方法中outputWriter的编码,恐怕会使这个方法前面输出的脚本产生混乱。于是继续寻找调用WriteScripts的方法,找到了最可疑的方法为:
public static bool OutputCombinedScriptFile(HttpContext context)
在方法体中,文件的第256行发现了生成传入WriteScripts的参数:
using (StreamWriter outputWriter = new StreamWriter(outputStream))
有经验的话一眼就能看出问题,StreamWriter的默认编码为UTF-8,如果需要指定编码的话需要调用另一个重载的构造函数,将编码作为第二个参数传递给StreamWriter的构造函数。而这里确不管当前使用了哪一种编码,统统改成UTF-8来输出,正会造成前面所说的结果。
这个问题的修改很简单,因为我们可以通过OutputCombinedScriptFile这个方法传入的参数HttpContext来获得所使用的编码,并把他加到StreamWriter构造函数的第二个参数上去就可以了:
using (StreamWriter outputWriter = new StreamWriter(outputStream, context.Response.ContentEncoding))
编译并在自己的网站中更新引用,运行后发现的确已经解决了这个问题。看来不需要修改其他的地方了。这时再用Fiddler查看十六进制的Response输出:
这下代码的长度确实缩短了,可以验证一下,这正是"密码强度的权重比例必须有 4 种"的GB2312编码。总结一下方案二:
方案二:修改AJAX Control Toolkit 1.0.10618的源代码,将ToolkitScriptManager.cs的第256行改为using (StreamWriter outputWriter = new StreamWriter(outputStream, context.Response.ContentEncoding)),并重新编译。
至此,ASP.NET AJAX Control Tookit 1.0.10618与GB2312的乱码问题似乎已经完全解决了。只是我在测试的时候,发现在UpdatePanel中提交的文本框中的中文仍然是乱码,说明ASP.NET AJAX Extensions 1.0本身仍然有编码的问题。只是我的网站中使用UpdatePanel的页面都不需要跟其他的GB2312网页直接交互,所以我就把这些页面的编码单独改成了UTF-8,而其他大部分页面使用在Web.config中默认的GB2312编码。这里多说一句,在<%@ Page%>中只能设置ResponseEncoding,无法设置RequestEncoding,而解决这个问题的关键是设置RequestEncoding。可以通过Web.config的<location>标签中针对某一个文件设置其<globalication>标签的requestEncoding属性,如:
<configuration>
<location path="Member/UserProfile.aspx">
<system.web>
<globalization requestEncoding="utf-8"/>
</system.web>
</location>
</configuration>
比较一下这两个方案,方案一优点是简单,只需要修改一个属性,缺点是不能用在国际化的网站上,而且要能接收可能会出现的英文提示。方案二缺点是需要修改并重新编译Control Toolkit,优点是解决的比较彻底,没有局限性。与方案二的有点相比,其缺点似乎是微不足道的,因为只需要重新编译一次,一劳永逸,且步骤并不复杂。最后留了一个小尾巴,就是ASP.NET AJAX Extensions 1.0的乱码问题,可以通过设置个别页面的编码来绕过,暂时我是没有去解决的动力了,不过它也提供了源代码,感兴趣的朋友们可以尝试一下。
方案二修改后的ASP.NET AJAX Control Toolkit 1.0.10618编译我已经上传到了CSDN的下载栏目,基于1.0.10618的代码,只修改了本文说的那一行代码,版本号和签名都没有变,因此可以直接替换项目中以前用的1.0.10618版本。下载地址: http://download.csdn.net/source/232485。本文所提到的Bug我也已经提交到AJAX Control Toolkit官方网站的Issue Tracker栏目,如果你也遇到了类似的问题,不妨来投一票,以便引起开发者的重视,也许在下一个官方版本就会解决了。地址: http://www.codeplex.com/AtlasControlToolkit/WorkItem/View.aspx?WorkItemId=12327。