ASP.NET 2.0中新的页面编译模型给实现通配符映射应用程序带来意想不到的问题,下面我以博客园Blog软件为例与大家一些探讨这些问题。
在博客园Blog软件中,实现IhttpHandlerFactory接口的是Dottext.Common.UrlManager. UrlReWriteHandlerFactory,不改变在ASP.NET 1.1中实现的UrlReWriteHandlerFactory代码,直接在ASP.NET 2.0中编译并运行,当程序运行在IIS根目录下,就会在执行PageParser.GetCompiledPageInstance时出现“Object reference not set to an instance of an object”异常(运行在虚拟目录中不会出现这个问题)。这个问题是ASP.NET 2.0中的一个小Bug,之前我写的PageParser.GetCompiledPageInstance中的一个Bug及解决方法对这个问题进行了一些分析,这个问题可以通过在PageParser.GetCompiledPageInstance之前调用context.RewritePath("~/default.aspx")解决。
“There is no build provider registered for the extension ''. You can register one in the <compilation><buildProviders> section in machine.config or web.config. Make sure is has a BuildProviderAppliesToAttribute attribute which includes the value 'Web' or 'All'.”
这里还有一个小bug,在上面的错误信息“Make sure is has a BuildProvider AppliesToAttribute attribute which includes the value 'Web' or 'All'.”提示需要设置AppliesToAttribute属性,实际上web.config中并不支持这样的属性,可能是正式版之前的ASP.NET 2.0支持过这个属性,后来去掉后,错误提示信息并没有修改。
解决了上面的两个问题,原以为通配符映射应用程序可以在ASP.NET 2.0中正常运行了,我在本机上测试博客园的程序,页面能正常访问。可是今天凌晨在服务器进上将网站升级到ASP.NET 2.0之后,发现ASP.NET运行时在频繁地编译页面,CPU占用一直100%,编译了一个多小时还在编译,而且编译似乎与访问量有关,访问少的站点页面还能打开,博客园主站由于访问量大,几乎无法访问。问题出在哪?于是我从PageParser.GetCompiledPageInstance的源代码寻找线索,在BuildManager.GetCacheKeyFromVirtualPath中发现可疑之处,BuildManager是根据所请求的虚拟路径创建缓存键,然后根据这个键查找或创建页面编译后的缓存对象。当对一个页面发出请求时,BuildManager会检查缓存,先从内存中检查,如果内存中没有就从缓存文件夹(Temporary ASP.NET Files)中查找,如果找到,就直接创建该类型的实例,不进行动态编译。如果没找到,就进行页面编译工作,而且查找的依据就是根据虚拟路径创建的缓存键。
经过测试情况果然这样,当然访问地址:http://www.cnblogs.com/dudu/archive/2006/03/07/345107.html时会在Temporary ASP.NET Files中文件夹编译生成类ASP.dudu_archive_2006_03_07_345107_html,而访问其他文章地时,也根据文章地址生成另外一个类。这样编译效率实在太低了!为什么要根据虚拟路径创建缓键,设计者设计时根本没考虑到通配符映射的问题,真是糟糕的设计!如果按照ASP.NET 1.1那样根据实际访问的页面文件名创建缓存键,就可以轻松地避免这个问题。ASP.NET 2.0新的页面编译模型在这里似乎是一个败笔。更糟糕的是连让开发人员弥补这个Bug的机会都没有,System.Web.Compilation.BuildManager中没有提供一个让开发人员自己设置缓存键的方法或属性。
(注:创建缓存键的方法是BuildManager. GetCacheKeyFromVirtualPath(VirtualPath virtualPath, out bool keyFromVPP))。更糟糕的是,System.Web.Compilation中的很多类都是internal,很多类的方法是灰色(Reflector用灰色显示internal static或private,颜色用的不错,让人看了就灰心),想自己调用相应方法进行页面编译几乎是不可能(用反射的方法不知能否调用,还没试过,即使能调用,也要考虑性能上的损失)。