为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,如需转载请自行联系原作者
相关文章
|
15天前
|
人工智能 前端开发 Devops
.NET技术在现代开发中的作用:.NET技术的核心价值、在现代应用开发中的实际应用、以及面临的挑战与未来趋势。
.NET技术是软件开发领域的核心力量,本文从其核心价值、实际应用及未来挑战三方面进行探讨。它支持多种语言,提供强大的开发工具和丰富的类库,并具备跨平台能力。在现代应用开发中,.NET广泛应用于企业级系统、Web应用、移动应用、云服务和游戏开发等领域。面对性能优化、容器化、AI集成等挑战,.NET持续创新以适应不断发展变化的技术环境。
33 4
|
15天前
|
开发框架 JavaScript 前端开发
震撼!破解 ASP.NET 服务器控件 Button 执行顺序之谜,颠覆你的开发认知!
【8月更文挑战第16天】在ASP.NET开发中,通过Button控件实现先执行JavaScript再触后台处理的需求十分常见。例如,在用户点击按钮前需前端验证或提示,确保操作无误后再传递数据至后台深度处理。此过程可通过设置Button的`OnClientClick`属性调用自定义JavaScript函数完成验证;若验证通过,则继续触发后台事件。此外,结合jQuery也能达到相同效果,利用`__doPostBack`手动触发服务器端事件。这种方式增强了应用的交互性和用户体验。
32 8
|
15天前
|
人工智能 开发框架 .NET
.NET技术的强大功能:.NET技术的基础特性、在现代开发中的应用、以及它如何助力未来的软件开发。
.NET技术是软件开发领域的核心支柱,以其强大功能、灵活性及安全性广受认可。本文分三部分解析:基础特性如多语言支持、统一运行时环境;现代应用如企业级与Web开发、移动应用、云服务及游戏开发;以及未来趋势如性能优化、容器化、AI集成等,展望.NET在不断变化的技术环境中持续发展与创新。
32 4
|
15天前
|
人工智能 开发框架 .NET
如何掌握.NET技术,引领开发前沿:.NET技术的核心能力、在现代开发中的应用实践、以及如何通过.NET技术实现持续创新。
.NET技术已成为软件开发不可或缺的部分,本文分三部分探讨:核心能力如多语言支持、统一运行时环境、丰富的类库及跨平台能力;现代开发实践包括企业级应用、Web与移动开发、云服务及游戏开发;并通过性能优化、容器化、AI集成等方面实现持续创新,使开发者站在技术前沿。
31 3
|
3天前
|
JSON C# 开发者
💡探索C#语言进化论:揭秘.NET开发效率飙升的秘密武器💼
【8月更文挑战第28天】C#语言凭借其强大的功能与易用性深受开发者喜爱。伴随.NET平台演进,C#持续引入新特性,如C# 7.0的模式匹配,让处理复杂数据结构更直观简洁;C# 8.0的异步流则使异步编程更灵活高效,无需一次性加载全部数据至内存。通过示例展示了模式匹配简化JSON解析及异步流实现文件逐行读取的应用。此外,C# 8.0还提供了默认接口成员和可空引用类型等特性,进一步提高.NET开发效率与代码可维护性。随着C#的发展,未来的.NET开发将更加高效便捷。
10 1
|
15天前
|
人工智能 前端开发 开发工具
.NET技术的开发潜力是巨大的,其核心优势和广泛应用不断推动着软件开发的进步
.NET技术自推出后已成为软件开发的关键力量。本文分三部分探讨其开发潜力:核心优势如统一平台、强大工具与跨平台能力;现代应用中的创新,包括企业级与Web应用、移动开发、云服务及游戏制作;以及面对的挑战与未来机遇,如性能优化、容器化支持、AI集成等。.NET的持续发展正塑造着软件行业的未来。
47 1
|
20天前
|
开发框架 JavaScript 前端开发
提升生产力:8个.NET开源且功能强大的快速开发框架
提升生产力:8个.NET开源且功能强大的快速开发框架
|
19天前
|
存储 人工智能 开发框架
一款.NET开发的AI无损放大工具
【8月更文挑战第11天】本示例介绍了一个基于.NET开发的AI无损图像放大工具架构。前端采用WPF或ASP.NET Core构建,提供直观的用户界面;后端包括图片上传、放大处理与结果存储服务。AI模型处理层负责加载预训练模型及图像预测放大。示例代码展示了图片上传与放大服务的关键逻辑,以及WPF界面设计。实际开发需关注模型选择、性能优化、用户体验、格式兼容与部署维护等方面。
|
20天前
|
XML 开发框架 数据格式
.Net Core 开发框架,支持多版本的类库
.Net Core 开发框架,支持多版本的类库
28 0
|
15天前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
26 0
下一篇
云函数