Ext.Net各版本在渲染脚本的时候有一定概率会产生中文随机乱码。这个问题已经困扰笔者很长时间,网上也有很多人在问,而且也有人将问题提交到了Ext.Net官方论坛上,这个问题直到2.5版本的时候才被官方修复。虽然问题已经修复了,但是还是有个问题,那就是对于至今还在用v1版本的老工程怎么办?
还能怎么办,自己动手呗!
翻阅Ext.Net官方论坛,发现跟帖中只写明了2.5版本修复问题,修复过程还得看SVN,但是像笔者这种只能用免费版的人来说,看SVN下载源代码简直是个白日梦。看来只能自己发现问题的根源了。
废话不说了,马上抄起Reflactor大法一窥究竟。
经过笔者对1.6版本进行了一上午的比对、调试和跟踪,发现问题出在了InitScriptFilter和AjaxRequestFilter这两个类上。
摘入InitScriptFilter类的代码片段如下:
public class InitScriptFilter : BaseFilter
{
private readonly StringBuilder html;
private readonly Stream response;
public override void Write(byte[] buffer, int offset, int count)
{
this.html.Append(HttpContext.Current.Response.ContentEncoding.GetString(buffer, offset, count));
}
{
分析上面的代码可知,Write传入的是字节数组,然后使用ContentEncoding将字节数组转化成字符,拼接到html后面。这里就有个问题,当编码是GB2312或UTF8的时候,中文字将被编码成2字节或3字节,如果一个中文字的一半正好处在buffer的尾部,被分两次Write,那么这里就会产生乱码。
同样,AjaxRequestFilter也是一样的情况。
原因找到了,现在开始写补丁代码修复这个问题。
2.5版本中的官方修正是使用List的缓冲区代替直接写入到html字符串中,在Flush的时候再统一进行Encoding转成字符串。
这个方法在1.6版本中很难进行修正,因为我们的修正操作是要在IL代码中进行的,修改太多会很麻烦。
为了简便起见,我设计的方案是这样的:
在Write的时候对字节数组编码转成字符串时,如果遇到不能成功转码的情况,就将不能转码的字节排除并记下,延后到下一次Write的时候跟后面的字节数组一起再进行转码。代码片段如下:
private Queue<byte> lastBytes; // 添加到类成员变量
public InitScriptFilter(Stream stream) // 构造函数
{
this.response = stream;
this.html = new StringBuilder();
lastBytes = new Queue<byte>(); // 添加这句
}
public override void Write(byte[] buffer, int offset, int count) // 重写整个Write函数
{
byte[] buf = new byte[count + lastBytes.Count];
int i = 0;
while (lastBytes.Count > 0) buf[i++] = lastBytes.Dequeue();
Array.Copy(buffer, offset, buf, i, count);
Encoding encoding = Encoding.GetEncoding(HttpContext.Current.Response.ContentEncoding.CodePage, new EncoderReplacementFallback(), new DecoderExceptionFallback());
string str = null;
for (i = 0; i <= 2; i++)
{
try
{
str = null;
str = encoding.GetString(buf, 0, buf.Length - i);
break;
}
catch (DecoderFallbackException) { }
}
if (str != null)
{
for (int j = i; j >= 1; j--) lastBytes.Enqueue(buf[buf.Length - j]);
this.html.Append(str);
}
}
好了,修正代码写好了,怎么修正到DLL里面去呢?笔者用了ildasm和ilasm组合大法。
首先用ildasm将Ext.Net.dll反编译,获得Ext.Net.il和好几千个资源文件。所使用的命令如下:
ildasm /utf8 /output=Ext.Net.il Ext.Net.dll
然后再用把写好的修正代码也编译一份DLL然后用ildasm反编译。
之后用Notepad++等文本编辑器将新代码覆盖到Ext.Net.il中的相应位置。AjaxRequestFilter类也用相同的方法处理。
改完之后,用ilasm进行重新编译。命令如下:
ilasm /dll /optimize /resource=Ext.Net.res /output=Ext.Net.fixed.dll Ext.Net.il
在等待了命令行输出了很大一堆文字后,本以为可以顺利地编译成功,但是意想不到的事情发生了。ilasm输出了一段错误信息:
Error: failed to read expected 10599816 bytes from mgd resource file 'Ext.Net.Build.Ext.Net.extjs.resources.images.slate.box.tb-blue.gif'
Could not create output file, error code=0x80004005
天哪,这什么问题,好像不是IL代码的问题。没办法,笔者只好爬上梯子求助好久不见谷哥。
据谷哥所述,ilasm有个毛病,不能编译资源文件超过1024个的程序集,并且程序集的总长度不能超过10M,如果超过了这两个要求,就会出错。只能将所有的资源文件单独列出来,使用VS进行编译,然后用ilmerge工具将两个程序集合并。
好了,解决方案在了,现在开始动手。
将Ext.Net.il中所有.mresource { }样子的代码全部注释掉,然后再用ilasm进行重编译,这次成功获得了一个不带任何资源的程序集。
用VS新建一个类库工程,将ildasm反编译出来的所有资源文件加入工程中,并且将所有文件的生成操作设置成“嵌入的资源”。这里需要注意,必须把所有资源文件以Ext.Net.Build.开头的名字去掉Ext.Net.,这个Ext.Net.是ildasm加上去的,程序集中的资源名实际上并没有这个命名空间前缀。然后编译,生成一个只带资源文件的程序集Ext.Net.res.dll。
最后用ilmerge将两个程序集组合成一个程序集,命令如下:
ilmerge /ndebug /v2 /out:Ext.Net.dll Ext.Net.fixed.dll Ext.Net.res.dll
至此,修正了随机字符乱码的Ext.Net就成功编译完成了。
以上过程适用Ext.Net v2.5之前的所有版本,各版本的实际代码可能有所不同,操作时需按照实际代码进行修改。