为WebForms说几句话,以及一些ASP.NET开发上的经验(3)

简介:

四、生成复杂的ID难以使用JavaScript操作

  我在上一篇文章的最后提到了,虽然使用WebForms我们能够对于页面上的HTML属性和样式等进行自由的定制和控制,但是有一点是毋庸置疑的,我们没有办法(正常的办法吧,Hack不算)让服务器端控件在客户端生成一个简单的ID。例如,一个TextBox控件,在服务器端的ID是txtUserName,但是最终在客户端生成的ID可能是LoginForm_txtUserName,因为它被放在一个ID为LoginForm的NamingContainer中。
  有了组件模型,就出现了大量控件。控件最主要的目的之一就是复用,而复用的一个特点就是应该高度内聚,而不依赖于外部环境。因此,为了使组件内部的服务器控件最终生成的客户端ID能够在页面上唯一,WebForms引入了NamingContainer这个概念。在NamingContainer中的服务器端控件最终在客户端生成的ID,会使用NamingContainer的“客户端ID”作为前缀。如此“递归”的做法保证了服务器控件在客户端的ID唯一。
  Web 2.0在业界风卷残云般的势头至今还未停歇,与其有密切相关的AJAX技术也被广泛使用。AJAX技术从根本上讲,是一种在浏览器中使用JavaScript实现的技术,因此使用JavaScript操作DOM元素的情况非常多见。在非WebForms的页面中我们可以编写如下的代码:
< input  type="text" id="textBox" />

< script  language="javascript" type="text/javascript">
    document.getElementById("textBox").value = "Hello World!";
</ script >
  但是由于NamingContainer的缘故,我们在使用WebForms的服务器端的控件时就可能无法通过textBox在客户端获得文本框(生成的<input />元素)。为了解决这个问题,服务器端的控件模型提供了一个ClientID属性,通过这个属性,我们就可以在服务器端得到控件最终在客户端的ID。例如,如果上面的代码放在一个用户控件里的话,就一定必须写成如下形式:
<% @  Control Language="C#" AutoEventWireup="true" %> 

< asp: TextBox  runat="server" ID="textBox" />

< script  language="javascript" type="text/javascript">
    document.getElementById("<%= this.textBox.ClientID %>").value = "Hello World!";
</ script >
  此时,当控件被放到页面上之后,它在客户端生成的代码则会是:
< input  name="DemoControl1$textBox" type="text" id="DemoControl1_textBox" />

< script  language="javascript" type="text/javascript">
    document.getElementById("DemoControl1_textBox").value = "Hello World"!;
</ script >
  请注意<input />元素的name和id,它们都留下了NamingContainer的痕迹。由于我们在页面上使用了<%= %>标记直接输出了服务器控件的ID,这样在客户端的JavaScript代码也就可以正确访问到服务器端<asp:TextBox />对应的客户端<input />元素了。
  这种在设计器很难预测的客户端ID,就是使用WebForms时所谓的“客户端ID污染”。
  接下来我们不妨来看一个略为复杂点的例子:
<% @  Control Language="C#" AutoEventWireup="true" %> 

< asp: TextBox  runat="server" ID="textBox" />

< script  language="javascript" type="text/javascript">
    var counter = 0;

    function increase() 
    { 
        document.getElementById("<%= this.textBox.ClientID %>").value = (counter++);
 
        window.setTimeout(increase, 500); 
    } 

    increase(); 
</ script >
  上面这段JavaScript代码的作用是每500为一个计数器加1,并且显示在文本框上。随着项目的发展,页面上复杂的JavaScript代码会越来越多,于是我们就会想办法将其转移到js文件中并且在页面上引用它们。使用js文件的好处很多,便于进行代码管理是一方面,但是最重要的好处之一还是对于性能的提高。如果JavaScript代码完全写在页面上,这样每次加载页面都需要下载这些JavaScript代码,而js文件可以缓存,这样客户端只需要在第一次加载时下载这个文件就可以了。减少了客户端与服务器之间数据通信的大小,也就加快页面加载的速度,提高了性能。
  不过问题就此出来了:为了能够正确引用到页面上的某个服务器控件生成的DOM元素,我们就必须在页面中使用<%= %>标记来输出控件的ClientID,但是<%= %>无法写在js文件中,这可怎么办?于是很多人着急了起来,我也不时会收到此类问题,似乎很难找到合适的解决办法。于是“客户端ID污染”似乎也就成了一个使用WebForms时非常严重的问题。
  有些朋友会说:“这个没有问题啊,仔细观察ClientID的组成方式能够很容易找到规律的。”服务器控件的ClientID是由自身ID和它所在的NamingContainer“树”来共同决定的,因此在理论上我们也完全可以在设计器得到“已经放置在页面中”的某个服务器控件的客户端ID,并将其写进JavaScript代码中。话虽如此,的确没错,但是这个解决方案实在不好,因为它违背了控件的重要特性:“复用”。作为一个控件来说,它可能会被放在任意的NamingContainer树下,也就是说,它的客户端ID在不同的环境中并不固定。另外,如果控件上层NamingContainer树中有任何一个的服务器端ID被修改的话,js文件中使用的ID就需要进行改变,这样实在不利于的维护,随着项目增大,此类问题会愈发明显。
  那么我们究竟该怎么做呢?
  在设法解决这个问题之前,我们先来思考一下这个问题。如果我们没有使用WebForms进行开发,就在普通的页面上编写代码,那么我们对于上面的功能会如何将其提取到js文件中呢?嗯,就直接在代码中通过textBox这个ID来获得DOM元素吧。那么好,请您先回答我以下几个疑问:
  1. 为什么要写textBox而不是其他ID呢?
  2. 如果其他页面上有个同样需要实现的功能,而那个文本框的id是txtCounter,那么该怎么作呢?
  3. 如果一张页面上有两个文本框需要显示这样的计数器,那么又该怎么做呢?
  上面的几个疑问其实只反应了一件事情,那就是这个计数器的复用性实在太差。什么叫做好的复用性呢?那么我们来看一下一个典型的示例, MaskedEditExtender。我们来看看它是怎么做的:
< ajaxToolkit : MaskedEditExtender
    TargetControlID="TextBox1" 
    Mask="9,999,999.99"
    MessageValidatorTip="true" 
    OnFocusCssClass="MaskedEditFocus" 
    OnInvalidCssClass="MaskedEditError"
    MaskType="Number" 
    InputDirection="RightToLeft" 
    AcceptNegative="Left" 
    DisplayMoney="Left"
    ErrorTooltipEnabled="True" />
  MaskedEditExtender的第一个属性TargetControlID,就可以决定了究竟是为哪个文本框添加效果,然后效果的样式可以由MaskType和Mask决定,获得焦点的样式和输入错误的样式可以由OnFocusCssClass和OnInvalidCssClass属性决定,连字符输入的顺序都可以定制。
  这就是复用:爱怎么用,就怎么用。爱给谁用,就给谁用。想什么时候用,就什么时候用。
  要复用,一般总需要组件化或模块化,内部实现通用的功能,而具体的信息应该由外部传入。例如我们上面的计数器就应该进行改造(用到了MS AJAX Lib里的Function.createDelegate方法):
function  Counter(textBoxId, interval)
{
    this._counter = 0;
    this._textBox = document.getElementById(textBoxId);
    this._interval = interval;
}
Counter.prototype =
{
    run : function()
    {
        this._textBox.value = (this._counter ++);
        window.setTimeout(
            Function.createDelegate(thisthis.run), this._interval);             
    }
};
  现在这个技术器的复用性已经有质的飞跃了,因为我们可以随意指定一个客户端的文本框进行显示,并且可以自由地设置计数器增长的间隔时间。于是我们在WebForms页面中就可以写如下的代码了:
< asp : TextBox  runat="server" ID="textBox1" />
< asp : TextBox  runat="server" ID="textBox2" />

< script  language="javascript" type="text/javascript">
    new Counter("<%= this.textBox1.ClientID %>", 500).run();
    new Counter("<%= this.textBox2.ClientID %>", 1000).run();
</ script >
  现在WebForms客户端ID污染已经不构成问题了吧!
  其实解决客户端ID污染的做法用一句话就能说清:“将不变的部分提取至js文件,将变化的部分(例如服务器控件的客户端ID)留在页面中”。但是我在这里将它上升到组件化的高度,因为它能让我们开发出更优秀的客户端程序。组件化的客户端编程方式较之传统的零散function的做法,更有利于代码的管理,并且增强了复用性和可维护性。有人说,客户端ID污染问题使脚本代码很难做到“内聚”——可能他的意思是将脚本代码提取到js文件中吧——但是我认为,这种污染“迫使”我们使用组件化的方式进行客户端开发,而这种组件化或者模块化的做法恰恰提高了代码的内聚性。
  不过,似乎组件化的编程方式会写更多的代码,不是吗?从理论上来说,可能的确是。不过需要注意的是,我上面提出的例子非常简单,简单到了其中的一半代码是用于“组件化”编程的“骨架”上。而对于一个略为复杂的功能来说,例如一个通用的表单验证组件,或者客户端级联组件,增加的这点“骨架”还算得了什么呢?
  这也算是一种因祸得福吧。


本文转自 jeffz 51CTO博客,原文链接:http://blog.51cto.com/jeffz/59535,如需转载请自行联系原作者
相关文章
|
1月前
|
人工智能 芯片
D1net阅闻|OpenAI员工疯狂暗示,内部已成功开发ASI?被曝训出GPT-5但雪藏
D1net阅闻|OpenAI员工疯狂暗示,内部已成功开发ASI?被曝训出GPT-5但雪藏
|
3月前
|
Linux API C#
基于 .NET 开发的多功能流媒体管理控制平台
基于 .NET 开发的多功能流媒体管理控制平台
71 9
|
3月前
|
缓存 算法 安全
精选10款C#/.NET开发必备类库(含使用教程),工作效率提升利器!
精选10款C#/.NET开发必备类库(含使用教程),工作效率提升利器!
121 12
|
3月前
|
Web App开发 前端开发 调度
一款基于 .NET + Blazor 开发的智能访客管理系统
一款基于 .NET + Blazor 开发的智能访客管理系统
|
3月前
|
前端开发 JavaScript C#
基于.NET8+Vue3开发的权限管理&个人博客系统
基于.NET8+Vue3开发的权限管理&个人博客系统
|
3月前
|
网络协议 C#
基于.NET WinForm开发的一款硬件及协议通讯工具
基于.NET WinForm开发的一款硬件及协议通讯工具
|
3月前
|
监控 前端开发 API
一款基于 .NET MVC 框架开发、功能全面的MES系统
一款基于 .NET MVC 框架开发、功能全面的MES系统
|
3月前
|
开发框架 监控 .NET
C#进阶-ASP.NET WebForms调用ASMX的WebService接口
通过本文的介绍,希望您能深入理解并掌握ASP.NET WebForms中调用ASMX WebService接口的方法和技巧,并在实际项目中灵活运用这些技术,提高开发效率和应用性能。
126 5
|
4月前
|
机器学习/深度学习 人工智能 物联网
.NET 技术:引领未来开发潮流
.NET 技术以其跨平台兼容性、高效的开发体验、强大的性能表现和安全可靠的架构,成为引领未来开发潮流的重要力量。本文深入探讨了 .NET 的核心优势与特点,及其在企业级应用、移动开发、云计算、人工智能等领域的广泛应用,展示了其卓越的应用价值和未来发展前景。
99 5
|
4月前
|
传感器 人工智能 供应链
.NET开发技术在数字化时代的创新作用,从高效的开发环境、强大的性能表现、丰富的库和框架资源等方面揭示了其关键优势。
本文深入探讨了.NET开发技术在数字化时代的创新作用,从高效的开发环境、强大的性能表现、丰富的库和框架资源等方面揭示了其关键优势。通过企业级应用、Web应用及移动应用的创新案例,展示了.NET在各领域的广泛应用和巨大潜力。展望未来,.NET将与新兴技术深度融合,拓展跨平台开发,推动云原生应用发展,持续创新。
65 4

热门文章

最新文章

相关实验场景

更多