引言
想升级到.Net 4.0已经很有一段时间了,本来是应该把各个工程文件都升级的,但由于种种原因,一直没有太合适的时机。考虑.Net 4.0可以做到版本想下兼容,因此在近期采用保持dll不变,而仅仅将程序运行环境升级到.Net 4.0的策略进行了一次升级工作。我们的程序为一个Web应用,在升级过程中遇到了两个比较有意思的问题,特予以记录。
主要的升级工作
对于Web应用,主要的升级工作其实就是手工更改web.config文件,主要参考了以下文档:
How to: Upgrade an ASP.NET Web Application to ASP.NET 4
另外,为httpRuntime节增加了一个requestValidationMode属性:
否则在操作页面时会出现下面的错误:
问题1:ClientIDModel
修改web.config后,在32位环境下已基本可运行。但部分功能节点出现问题,表现为弹出窗口关闭后,主界面并未按预期刷新。
通过Visual Studio调试器跟踪,发现是服务端找不到btnFind控件,所以没有触发btnFind_Click事件。服务端控件创建没有问题,但客户端传递的EventTarget是M$P1$BtnFind,而正确的值应该是u$M$P1$BtnFind。
服务端控件中,CachedUniqueID是期望的,CachedPredictableID则输出了不正确的ID。原因是.net使用了Predictable方式进行ID输出:
Predictable是4.0提供的一种优化的ID生成算法,也是默认的配置。将ClientIDMode修改成 "AutoID",使用.Net 2.0的方式进行ID输出,问题解决:
问题2:64位环境下w3wp进程启动时崩溃
解决问题1后,在32位环境下web应用一切正常。但在64位环境下,发现w3wp.exe启动时出现Crash现象,IIS在自动连续尝试3次后停止,而IE客户端指示无法显示该网页:
注:即便是64位环境,如果将AppPool配置为32位模式也没有问题。
收集Dump文件,发现是由于ExecutionEngineException异常导致w3wp崩溃:
调用栈指示最后的出错位置是在通过反射获取Assembly相关Attribute时:
查看相关代码,作用是查找AppDomain中加载的Assembly,如果有Assembly打上了AjaxFrameworkAssemblyAttribute标记,则将该Assembly作为DefaultAjaxFrameworkAssembly,否则使用系统自带的System.Web.Extensions程序集:
每个ScriptManager在创建时均会触发上述代码,通过两个静态变量_defaultAjaxFrameworkAssembly和_ajaxFrameworkAssemblyConfigChecked控制相关逻辑只触发1次。
在了解了代码的大致逻辑后,解决问题的思路自然就是要查看究竟是在获取哪个Assmbly的CustomAttributes时引发了异常?但捕获的dump文件已无法确定assembly的详细信息。于是直接用Reflector Pro反编译生成CLR源码进行调试。调试时发现,尽管设置断点可以顺利命中,但各个变量的值却无法用调试器查看,原因是CLR代码已优化。
换一个做法,把反编译得到的相关代码拷贝出来,单独放置到一个独立的.aspx页面文件中执行,顺利得到了异常信息:
引发问题的是一个FarPoint.CalcEngine.dll,将其从Portal\bin下删除后w3wp就可以正常启动了。FarPoint是一个第三方图形控件,其Target Runtime指示为.Net 1.0。但这并非问题所在,FarPoint有多个dll,均为.Net 1.0,但只有这个FarPoint.CalcEngine.dll会引发问题:
问题解法
显然,每次通过手工排查的方式去查找引发异常的Assembly太累了,需要一种较为彻底的解决方案。选择的解决方案为通过反射技术将_defaultAjaxFrameworkAssembly强制赋值为true,以屏蔽掉查找DefaultAjaxFrameworkAssembly的逻辑。
具体做法是编辑网站的Global.asax文件,在Application_Start事件处理函数中加入如下两行代码:
System.Reflection.FieldInfo fldInfo = typeof(System.Web.UI.ScriptManager).GetField("_ajaxFrameworkAssemblyConfigChecked",
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
fldInfo.SetValue(fldInfo.GetValue(null), true);
至此问题圆满解决。
问题探讨
其实问题1并非真的问题,微软只是没有在升级Asp.net 4的文档中指明而已,但另外有一个同样来自微软的文档,已经详细描述了包括ClientIDModel在内的一些由.Net 4.0引发的变动问题:
但问题2我认为是一个大问题,很难理解设计者的意图。难道是允许开发者替换默认的MS-Ajax框架?如果真是这样,那ScriptManager也会被替换掉啊,怎么可能保留MS-Ajax的ScriptManager,由其指向一个第三方的Ajax框架?另外,代码实现缺乏健壮性,至少应该加一个try..catch异常处理,避免程序崩溃。估计开发者没有考虑到会有Assembly导致出现致命异常的情况。
这里还有一个疑问,就是为什么64位环境和32位环境会存在差异?用windbg的!Analyze –v命令对dump文件进行分析,最后的Native代码为调用clr!BlobToAttributeSet方法,触发的是一个访问违例异常(AccessViolationException):
从以上信息看,最后出错是因为访问了一个空指针。按理,32位的windows和64位的windows在虚拟空间的最底层都留了64K用于空指针检查,并无特别差异,何况这里是真正为0的空指针啊?
用ILDasm查看FarPoint.CalcEngine.dll,推测Bug应该是由这个reqrefuse引用为空造成的:
这是该dll与其它几个FarPoint dll最显著的一个不同之处。至于问题的准确答案,也只有能用CLR Native源码进行调试的微软程序员可以给我们解惑了。