代码改变世界

ASP.NET 无法确保在注册的 JavaScript 内不存在重复定义

2007-01-28 00:13  Cat Chen  阅读(6817)  评论(11编辑  收藏

在ASP.NET 2.0中,我们使用Page.ClientScript属性(也就是一个ClientScriptManager对象)的一些名字以Register开头的方法注册客户端脚本,这是大家都知道的。

理论上应该如何避免冲突

先说说为什么要这样注册脚本,而不用Response.Write直接输出。举个例子,你用3个DropDownList做了一个输入日期的区域,分别代表年/月/日,然后你为了防止用户输入2007/02/31,所以你决定把这3个DropDownList做成级联的,也就是随着年和月的输入改变,日的可选项跟着改变。这时候你可以通过写一些JavaScript来实现级联,例如定义一个名为updateDateRange()的JavaScript函数负责更新DropDownList,然后直接把这些JavaScript放到C#的字符串里,并且使用Response.Write输出。这些代码用起来会很正常,直到有一次你的页面需要输入两个日期。

在需要输入两个日期的那个页面上,你把3个DropDownList复制粘贴了一遍,也把输出的JavaScript的那段代码复制粘贴了一遍,接着根据两处ID的不同做了相应的修改,结果有一组级联无法正常运行起来。你查看服务器端输出的HTML,接着恍然大悟——原来有两个updateDateRange()函数。于是你把updateDateRange()改为updateDateRange(yearControlClientId, monthControlClientId, dateControlClientId),同时把JavaScript删减为仅输出一遍,这时候无论哪组级联都使用同一个函数,它们根据调用时输入的DropDownList.ClientID来区分。

又有一天,你决定把这组级联封装为一个UserControl,做起来当然还是复制粘贴大法,也就是把3个DropDownList和JavaScript复制进UserControl,然后把UserControl的引用复制回原本的调用处。忙完之后,发现那个有两个日期输入的页面又出错了,原来updateDateRange(yearControlClientId, monthControlClientId, dateControlClientId)又被重复输出了,因为页面上放入了两个UserControl所以JavaScript被输出了两遍,并且没办法减少输出次数。

这时候你可以使用Page.ClientScript.RegisterClientScriptBlock解决问题,它通过type和key这两个参数确定脚本是否被重复注册,而被重复注册的脚本仅会输出一次。为什么要type和key两个参数呢?以前ASP.NET 1.x的同类函数只有key一个参数,这带来的问题是可能两个不同的控件设计时都使用了同一个key来注册自己的脚本,结果其中一个控件脚本的成功输出必然会抑制另一个控件脚本的输出。加上了type参数,各控件都用自己的类型作为标识,这样就能有效避免注册时冲突。

为何无法真正避免冲突

关于这个问题,我们先看看ASP.NET内部定义的JavaScript是以什么方式命名的。通常,private的全局函数或变量,命名都以双下划线开头,例如大家熟悉的__doPostBack,或者是WebPartManager在客户端使用的__wpm。而public或protected的全局函数或变量,一般就好像C#那样使用Pascal命名法。具体的例子,大家可以用Reflector看看System.Web.UI的资源中的那些js文件。

我们暂时就假设这种命名法是正确的,然后模仿着去在自己开发的控件中实践。事实上很多控件开发者也确实这样做了,比较多的专业控件中你都能看到双下划线开头命名的函数或变量,这至少可以避免和控件使用者在页面上注册的函数或变量冲突,因为在页面上注册的函数或变量通常都采用比较简单的命名法。

假如现在我们要做一个浮动上下文菜单,也就是当你的鼠标移动到某个HTML元素上时该浮动菜单自动出现,当鼠标离开元素并且也不在菜单上时,菜单自动消失。为了方便用户操作,我们允许用户鼠标移动过程中稍微离开菜单区域,所以定义当鼠标离开菜单区域若干时间后才让菜单消失,而这个时间在客户端保存在__disappearAfter变量中。这个控件看起来什么问题都没有,直到你把它和ASP.NET 2.0自带的Menu控件放在同一个页面上,因为Menu控件也有类似的功能,而且和我们的控件一样Menu控件选择了将时间变量保存在一个名为__disappearAfter的变量中。

现然,作为ASP.NET框架的使用者,框架没有声明这个变量的名字不允许使用,我用了有问题当然就可以认为是框架的错。Brad Abrams写了一本《.NET设计规范/Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries》,里面却完全没有提及JavaScript和CSS的规范,好像在ASP.NET中使用到的JavaScript与CSS都是琐碎的不能在琐碎的事情,所以完全不值得一提。

事实上,既然ASP.NET允许一个页面上不同的控件设计者引入不同的JavaScript和CSS,就必须提供一种方法去管理潜在的命名冲突。如果是JavaScript,我们可以考虑使用ASP.NET AJAX的namespace来避免冲突,下一代代号为Orcas的Visual Studio和ASP.NET将内置ASP.NET AJAX支持,所以其内置控件所使用的JavaScript应该也会有namespace,这样就有有效降低冲突概率。至于CSS命名冲突,暂时没有好的解决放案,只能依赖控件设计者的习惯了,你可以考虑为你的控件根元素附上一个namespace以示区分,这样也算是降低冲突概率的一个办法。

最后,如果你希望阅读更多类似的ASP.NET主题文章的话,欢迎订阅Cat in dotNET (Feed: http://feeds.feedburner.com/CatChen/dotNET)或Cat in Chinese (Feed: http://feeds.feedburner.com/CatChen/Chinese)。