ASP.NET 2.0 想说爱你不容易

asp.net

  本文主要通过分析在ASP.NET 2.0中开发ASP.NET通配符映射应用程序遇到的一些问题,来说明ASP.NET 2.0中页面编译模型的不足之处。文章中如果有不妥之处,欢迎您指出。

  这里所说的ASP.NET通配符映射应用程序是指在IIS中将所有请求转发至ASP.NET 2.0运行时处理(对于IIS 5.0,就是建立.*到aspnet_isapi.dll的映射),在程序中通过实现System.Web.IhttpHandlerFactory接口来处理所有请求,实现System.Web.IhttpHandlerFactory的类就相当于一个前端控制器。典型应用就是.Text及基于.Text开发的博客园Blog软件。

  在ASP.NET 1.1中,实现通配符映射应用程序大家可能比较清楚,主要是两点:

  1、 实现System.Web.IhttpHandlerFactory接口,在GetHandler(HttpContext context, string requestType, string url, string path)中根据请求的url,基于一些规则,找到实际访问的页面文件,然后调用PageParser.GetCompiledPageInstance对页面进行编译并生成相应的实例处理请求。这样做的好处是:你可以使用任意的url地址,不必关心是否存在对应的页面文件,而且可以方便地控制对Web服务器上资源的访问。

  2、 在web.config中加上:

<httpHandlers>
<add path="*" verb="*" type="Dottext.Common.UrlManager.UrlReWriteHandlerFactory,Dottext.Common" validate="false" />
</httpHandlers>

  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")解决。

  接着进行访问个人Blog主页的测试,比如:http://www.cnblogs.com/dudu,访问时出现错误:

“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'.”

  在ASP.NET 2.0中,当我们第一次访问一个页面时,必不少的两个过程是:1、页面编译 2、创建编译后的页面代码的实例。页面编译是根据所访问的url地址中的扩展名找到匹配的Build Provider对页面进行编译。这里出现的问题是由于ASP.NET 2.0运行时没找到相应的Build Provider,对http://www.cnblogs.com/dudu这样地址,由于使用了通配符映射,在ASP.NET 2.0运行时处理时,得到的扩展名是空(如果没有使用通配符映射,IIS会自动将地址改为:http://www.cnblogs.com/dudu/default.aspx)。ASP.NET 2.0在这里的设计不足之处是没有考虑这种情况,无法通过在web.config中进行相应的配置来解决这个问题。如果能提供下面的配置,这个问题就可以轻松解决:

<buildProviders >
<add extension=".*" type="System.Web.Compilation.PageBuildProvider"/>
</buildProviders>

  对于这个问题,我的解决方法是在PageParser.GetCompiledPageInstance之前对url进行处理,在url中加上缺省文件,比如:default.aspx。如果你想使用其他的扩展名,比如:.html,需要在web.config中加上:

<buildProviders >
<add extension=".html" type="System.Web.Compilation.PageBuildProvider"/>
</buildProviders>

  这里还有一个小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)中查找,如果找到,就直接创建该类型的实例,不进行动态编译。如果没找到,就进行页面编译工作,而且查找的依据就是根据虚拟路径创建的缓存键。

  对于通常的页面访问方式,这样处理不会引起问题。但对于通配符映射的情况,就会带来问题。因为通配符映射时,常见情况是不同的url地址访问的却是同一个页面文件。比如博客园中每篇文章地址不同,但访问的却是同样的页面代码,如果按照目前ASP.NET 2.0中的页面编译模型,每篇文章第一次访问都要进行编译,如果博客园中的几十万篇文章被访问,就要进行几十万编译,难怪今天博客园网站升级至ASP.NET 2.0之后,服务器一直忙于编译。

  经过测试情况果然这样,当然访问地址: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,颜色用的不错,让人看了就灰心),想自己调用相应方法进行页面编译几乎是不可能(用反射的方法不知能否调用,还没试过,即使能调用,也要考虑性能上的损失)。

  难道要自己写System.Web.Compilation中那些类去处理页面编译?我宁愿选择ASP.NET 1.1,然后等ASP.NET 2.0 SP1,SP1解决不了,等SP2......希望不要等到ASP.NET 3.0。

  也许你想到了在GetHandler(HttpContext context, string requestType, string url, string path)中调用System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath来编译并创建页面的实例。这个方法我也尝试过,答案是不行,还不如PageParser.GetCompiledPageInstance,至少后者能让程序运行起来。使用BuildManager.CreateInstanceFromVirtualPath时,当访问的地址中不带扩展名时就会出现“The resource cannot be found”错误,原因是在GetVPathBuildResultInternal(VirtualPath virtualPath, bool noBuild, bool allowCrossApp, bool allowBuildInPrecompile)中调用了Util.CheckVirtualFileExists(virtualPath)对虚拟路径进行检查,检查时将虚拟路径转换为物理路径,检查当前请求的页面文件是否存在,对于通配符映射应用程序,很多地址是实际上不存在的,所以就出现“The resource cannot be found”错误。而PageParser.GetCompiledPageInstance中通过调用HostingEnvironment.AddVirtualPathToFileMapping避免了这个问题。而这个方法被
Internal保护,在代码中也无法调用。

  我觉得问题的核心是ASP.NET 2.0设计者在设计时没有考虑通配符映射这样的情况。是忽略还是另有考虑,就不得而知了。但ASP.NET 1.1能正确处理这个问题,而ASP.NET 2.0却处理不了,这里很不应该的。使用通配符映射的Web应用程序用户只能望ASP.NET 2.0心叹。最近花了很大精力想把博客园的程序迁移到ASP.NET 2.0,而结果却是无法迁移到ASP.NET 2.0,令人失望! 只能寄希望微软推出相应的补丁。

  还好,使用通配符映射的Web应用程序不是很多,这个问题影响不是很大。

时间: 2024-11-09 00:17:52

ASP.NET 2.0 想说爱你不容易的相关文章

一位seoer的心声:百度想说爱你不容易

我是从08年开始自己学习建站,我想说:我是个菜鸟,但是菜鸟也有建站的权力.网站对于菜鸟来说是自己的奋斗方向以及对自己努力付出后的一种成就的肯定与展现. 从刚开始的学习html代码,在到后来的asp,我慢慢的开始深入了解了网站建设的魅力与前景.我清楚的记得我的第一个个人主页是在刚上大学那会做的,当时连续通宵了2个晚上做出了10多个页面的全静态网站.很有成就感!但那会根本没有什么SEO,百度都没这么有名,还没上市呢-- 和很多刚入门的站长一样,我以为有了网站就算是成功了.后来发现我的网站根本没什么人

[转]利用ASP.NET 2.0创建自定义Web控件(2)

原址:http://hi.baidu.com/sjbh/blog/item/5a8298454403a321cffca39c.html   如何生成的? Render() 方法基本上控制着 WebControl 的整个输出.默认情况下,Render() 方法实际上会依次调用 RenderBeginTag().RenderContents() 以及 RenderEndTag().尽管在 ASP.NET 1.x 中调用结构并未变化,但由于该呈现模型,修改这些调用的影响却发生了变化. 您可以覆盖 Re

ASP.NET 2.0实现AJAX的Web开发

ajax|asp.net|web [导读]在过去的几个月中,基于AJAX技术开发高度交互的Web应用程序的设计模式迅速流行开来.现在,具有高度可配置性的Web应用程序,例如Google Maps和A9,都在综合利用这些技术来创造丰富的客户端用户体验.其实,结合AJAX技术进行Web开发并非最近的研究成果,只不过这些技术一直以来不断得到持续更新和改进. 本文中我有三个目的.首先,我想提供一个AJAX风格应用程序的高级概述.其次,我想详细地描述ASP.NET 2.0的异步回调机制.最后,我想对构建A

将 JavaScript 与 ASP.NET 2.0 配合使用

asp.net|javascript 将 JavaScript 添加到服务器控件 将 JavaScript 添加到位于 ASP.NET 页面中的某个特定服务器控件是非常简单的.我们以按钮服务器控件为例.如果您使用任一 Microsoft Visual Studio 2005 将 Button HTML 服务器控件(HtmlInputButton 类)拖放到某个页面中,并将其作为服务器控件运行,则应具有以下代码结构: <input id="Button1" type="b

抢先试用ASP.NET 2.0中的新型安全控件

asp.net|安全|控件 一. 引言 与ASP.NET 2.0一同上市的有几个新的安全控件-它们位于工具的Login选项卡中(见图1)-这些控件大大简化了Web开发人员的工作.通过使用这些新的安全控件,现在你可以执行例如用户登录.注册.口令改变等的任务:而且,为此做出的努力仅是拖放相应的控件到你的Web表单上去.在本文中,我将向你展示怎样使用这些新控件来实现用户认证. 首先,让我们探索一下LoginView.LoginStatus和LoginName三个控件的使用.首先,让我们使用Visual

使用 IIS 进行 ASP.NET 2.0 成员/角色管理(2):实现

asp.net|iis 摘要:本文介绍如何通过创建三层结构式 ASP.NET 2.0 应用程序来维护 IIS 生产服务器中的成员身份数据库和角色数据库. 简介 成员身份编辑器 Microsoft Visual Studio 2005 版本中没有用于维护 Microsoft IIS 中的成员身份数据库和角色数据库的"现成"解决方案.将开发环境中的应用程序移至 IIS 生产服务器时这就会是个问题.Microsoft 提供的实用程序 ASP.NET Web Configuration 只能在

从ASP.NE T 1.1升级到ASP.NET 2.0需要考虑的Cookie问题

asp.net|cookie|问题 当你准备将Web应用程序从ASP.NET 1.1升级到ASP.NET 2.0,你将面对这样一个cookie问题:在ASP.NET 1.1应用程序中客户端保存的所有cookie将失效. 博客园也遇到了这样的问题,对博客园来说,意味着所有使用cookie的用户都需要重新登录,虽然这不是一个很大的问题,但的确给大家带来了麻烦,如果忘记了密码,将更加麻烦. 对于一个非常重视用户满意度的网站来说,应该努力去解决这个问题.博客园希望尽可能减少升级带来的影响,所以这两天我一

ASP.NET 2.0中实现跨页面提交

asp.net|页面 在ASP.NET 1.X 版本中,页面都是提交到自己本身,并不能方便的指定需要提交的目的页面.例如FirstPage.aspx中的button只能提交到FirstPage.aspx,而不能提交到SecondPage.aspx.很多时候,ASP.NET 1.X这样工作方式使我们的开发方式受到不少限制.熟悉ASP/JSP/PHP的朋友大概很不习惯,因为以前经常使用的提交方式突然无法使用,虽然也有解决这个问题的方法(演示Webcast),可是过程太烦琐,不甚方便.令我们高兴的是,

ASP.NET 2.0 的内部变化

asp.net Jayesh Patel.Bryan Acker 和 Robert McGovern Infusion Development 适用范围: Microsoft ASP.NET 2.0 摘要:尽管 ASP.NET 2.0 与 ASP.NET 1.1 完全向后兼容,但还是为 ASP.NET 带来了大量的内部变化,包括代码模型.编译.页面生命周期等的变化.本文将概括介绍这些变化. 本页内容 引言 代码模型 编译 完全运行时编译(代码目录) 页面生命周期 可扩展性 高级缓存技术 性能 结