[转载]答《漫话ID》中的疑问:UniqueID和ClientID的来源-程序员宅基地

 

在《漫话ID》一文中,作者提出了一个问题:为什么在ItemCreated事件中访问ClientID会导致MyButton无法响应事件,事实上 MyButton无法响应事件是因为他在客户端的ID被改变了,而此文从UniqueID和ClientID入手,进行较为深入的探讨,展示 UniqueID和ClientID是如何生成的,在何时生成,并同时解答《漫话ID》一文中作者的疑问。

为什么有UniqueID和ClientID

这个我想多数使用WebForm的人已经知道了,很大原因上是因为WebForm中存在着数据绑定控件以及自定义控件、View控件之类可以拥有子控件的控件,这就导致在控件树中很有可能存在着2个ID完全相同的对象,但是由于HTML并不允许ID的重复,于是WebForm发明出了一个叫NamingContainer的东西,并给控件提供了UniqueID和ClientID这两个属性,用于区分彼此。

虽然这两个属性经常给我们惹来这样那样的麻烦,但从总体上来说,这是一个良好的设计,无可厚非。

UniqueID和ClientID如何计算

首先需要明白的一点是,在WebForm中所有的控件最终形成一个树,每个控件是树中的一个节点。

在树型的结构中,每一个节点都拥有0个或1个父节点,而UniqueID和ClientID正是基于“从树的根部到当前节点的路径”来计算UniqueID的,我们看一下以下的示例:

<asp:Repeater ID="MyRepeater" runat="server">
<
ItemTemplate>
<
asp:Button ID="MyButton" runat="server" Text="My Button" /></ItemTemplate>
</
asp:Repeater>

我们在MyRepeater中放置了MyButton,而这样简单的内容生成出的这个MyButton的最终HTML如下:

<input id="MyRepeater_ctl00_MyButton" type="submit" value="My Button" name="MyRepeater$ctl00$MyButton"/>

请注意看,按钮的ID变为了MyRepeater_ctl00_MyButton,Name则变为了MyRepeater$ctl00$MyButton,根据《漫话ID》一文中所述,这个ID正好是服务器端控件的ClientID,而Name又正好是服务器端控件的UniqueID。
我们现在要探索的,是为什么ID会变成这个样子,因此我们先从字面上进行理解,不能看出:

  1. 在这个ID中,有“MyRepeater”字样,而这正是MyButton的父节点元素Repeater的服务器端ID
  2. 中间有“ctl00”字样,根据我们编程的习惯,ctl应该表示着control的意思,而00自然就是索引了,这里正好表示“此MyButton是Repeater产生的列表中的第1个元素(下标为0)”
  3. 最后有“MyButton”字样,正好是MyButton的服务器端ID

好了,将ID分解以后我们就能非常清晰地理解ClientID的生成策略了:

生成ClientID的方法是,在控件本身的ID前加上此控件在数据绑定(如果有)中的索引再加上父控件的ClientID

那么这是怎么做到的呢,这里就提出了NamingContainer的概念,对于NamingContainer,只需要实现一个标记接口INamingContainer。
当控件在计算ClientID时,会查找当前控件的NamingContainer属性是否为null,如果不是则调用该NamingContainer的一个叫GetUniqueIDPrefix的方法,当然这个方法是递归的,在方法体内又会去调用另一个GetUniqueIDPrefix方法。
至于NamingContainer这个属性是怎么来的,事实上是在调用Controls.Add方法时加上去的,大家可以使用反编译看一下Control类一个叫AddedControl方法的实现,在此就不作赘述。

回到问题

现在我们回到《漫话ID》一文中的问题,为什么在DataGrid控件的ItemCreated事件中去调用ClientID会影响以后的执行。

我想令作者感到困惑的是,他仅仅获取了ClientID,而没有对其作任何的修改。作者认为他并没有影响到Button的状态,因此Button应当按其正常的方式进行渲染。

在这里就不得不说一个问题,非常多的人认为属性这东西的读取仅仅是一个简单的return,他们没有想到在属性的get体中可以进行非常多的逻辑,而这些逻辑又很有可能影响到对象的状态,而我想正是这种“想当然”致使作者没有仔细地去看ClientID的get过程中,Button到底发生了什么。那么就由我来大致地梳理一下这个思路吧。

首先我反编译了ClientID属性,可以看到他的get体的内容,其代码如下:

public virtual string ClientID
{
get
{
this.EnsureID();
string uniqueID = this.UniqueID;
if ((uniqueID != null) && (uniqueID.IndexOf(this.IdSeparator) >= 0))
{
return uniqueID.Replace(this.IdSeparator, '_');
}
return uniqueID;
}
}

可以看到,ClientID的get远不止return this._clientID;这么简单,事实是,控件根本不保存当前的ClientID,而是在每一次获取时都从UniqueID去计算,并将UniqueID中的分隔符(也就是$)替换为下划线(_)并返回。

再仔细地看代码,我们会发现首先调用了EnsureID方法,这就像我们在自定义控件的时候会调用EnsureChildControls方法一个,EnsureID会检测当前控件的ID是否已经生成,如果没有生成则会使用一定的策略进行生成,下面就是EnsureID方法的代码:

protected void EnsureID()
{
if (this._namingContainer != null)
{
if (this._id == null)
{
this.GenerateAutomaticID();
}
this.flags.Set(0x800);
}
}
我们看到,在EnsureID方法中就用到了NamingContainer,当且仅当NamingContainer不为null的时候,该方法才有作用。
什么嘛,结果在EnsureID方法中根本就没有涉及到UnqiueID的计算问题,呵呵是不是有被骗的感觉?
但是这么一来,又是什么影响着ClientID呢,再仔细地回看ClientID的get方法体,最后也只能说是UniqueID这个属性在搞鬼了。
对,UniqueID和ClientID一样,并不是一个简单的属性,他在get体内也包含了大量的逻辑,以下就是这些逻辑:
public virtual string UniqueID
{
get
{
if (this._cachedUniqueID == null)
{
Control namingContainer = this.NamingContainer;
if (namingContainer == null)
{
return this._id;
}
if (this._id == null)
{
this.GenerateAutomaticID();
}
if (this.Page == namingContainer)
{
this._cachedUniqueID = this._id;
}
else
{
string uniqueIDPrefix = namingContainer.GetUniqueIDPrefix();
if (uniqueIDPrefix.Length == 0)
{
return this._id;
}
this._cachedUniqueID = uniqueIDPrefix + this._id;
}
}
return this._cachedUniqueID;
}
}
我想代码已经非常清楚了,如果NamingContainer为null,则UniqueID只会返回当前控件的ID,否则将会通过NamingContainer的GetUniqueIDPrefix方法去计算本文一开始说的那种格式的ID并返回。
好了,至此我们已经把ClientID的生成过程分析清楚了,这里总结一下:
  1. ClientID是由UniqueID经过简单的字符串替换形成的
  2. UniqueID是通过NamingContainer形成的

看到这里我想《漫话ID》一文的作者已经明白了吧,为什么在ItemCreated事件中访问ClientID会导致最终HTML页面上的2个Button都叫“MyButton”,如果还不明白,请去断点调试ItemCreated事件,看看MyButton的NamingContainer是什么。
呵呵,你以为MyButton的NamingContainer是null?那么你肯定没调试过哦~~

在ItemCreated事件中,MyButton的NamingContainer是DataGridItem,但问题在于
DataGridItem此时并没有加入到DataGrid中(在ItemDataBound事件中才被加入到DataGrid中),
因此DataGridItem的NamingContainer是null。
而DataGridItem的GetUniqueIDPrefix方法则需要知道自己在DataGrid中的索引,
以便返回类似ctl00这样的格式,既然没有加入到DataGrid中,DataGridItem就只能返回一个空字符串。
MyButton将DataGridItem返回的空字符串和自己的ID一拼接,就形成了UniqueID,此时的UniqueID正好是自己的ID。
并且MyButton又将这个UniqueID给保存了起来,在今后的访问中不会再一次重新计算,于是这个不规范的UniqueID
也将一直被使用到成为HTML。

总结

发现自己的表达能力实在很差,不知道有没有将这个问题说清楚,总之:

  1. 对ClientID属性的访问将直接导致控件计算UniqueID
  2. 在不恰当的时候计算UniqueID,将可能导致错误的结果
  3. 要保证控件与当前页面的控件树的根连通的情况下才访问ClientID
  4. .NET中有很多属性并不是简单的return,他们会改变对象的状态,往往这就是解决问题的切入点

原文转载自:http://www.cnblogs.com/GrayZhang/archive/2009/03/05/how-uniqueid-is-generated.html

 

转载于:https://www.cnblogs.com/babycool/archive/2012/06/01/2531437.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30608503/article/details/97246724

智能推荐

python遗传算法多目标_小知识:什么是遗传算法-程序员宅基地

文章浏览阅读473次。1 什么是遗传算法遗传算法(GeneticAlgorithm, GA)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法。其主要特点是直接对结构对象进行操作,不存在求导和函数连续性的限定;具有内在的隐并行性和更好的全局寻优能力;采用概率化的寻优方法,不需要确定的规则就能自动获取和指导优化的搜索空间,自适应地调整搜索方向。遗传算..._多目标遗传算法关于gen,population,等的定义

QCheckBox配合QListWidget以及QLineEdit实现QComboBox下拉复选框功能_qtreewidget::setmodel() - changing the model of th-程序员宅基地

文章浏览阅读2.3k次,点赞3次,收藏19次。功能实现:目的效果如下:此功能是从按照网上的各种介绍实现的,详细就不做介绍了,主要是记录一下在使用过程中出现的问题 编译时总是提示: ASSERT: "!“QListWidget::setModel() - Changing the model of the QListWidget is not allowed.” ” in file itemviews\qlistwidget.cpp, ..._qtreewidget::setmodel() - changing the model of the qtreewidget is not allow

jQuery.fn.extend()方法_jquery fn.extend-程序员宅基地

文章浏览阅读176次。jQuery.fn.extend()方法 实例添加两个方法到jQuery原型($.fn)&lt;label&gt;&lt;input type="checkbox" name="foo"&gt; Foo&lt;/label&gt; &lt;label&gt;&lt;input type="checkbox" name="bar"&gt_jquery fn.extend

1-STM32物联网开发WIFI(ESP8266)+GPRS(Air202)系统方案微信小程序篇(域名备案)_stm32域名访问-程序员宅基地

文章浏览阅读320次。如果自己的域名有没备案使用域名访问http的时候会报错微信小程序如果要发布,也需要有备案过的云服务器和域名我的不能详细写了,因为已经备案过了,展示的信息不一样了,我拷贝过来别人写的 <ignore_js_op> 再往下,就根据提示让你填,你就填就..._stm32域名访问

如何做到iphone数据恢复_iphone格式化后数据还能恢复吗-程序员宅基地

文章浏览阅读3.4k次。近期小编我收到不少用户咨询误删除了苹果手机数据之后,应该如何恢复到问题。要知道,苹果手机最经典的就是他的ios系统,这套系统比较闭合,什么都是自成体系。所以有时候也让很多朋友担心,万一iphone数据丢失了,itun1youmeilaide及备份,该怎么办才好呢?安卓手机能用的数据恢复办法,苹果也能用么?一般来说,选择专业数据恢复技术苹果iphone手机数据恢复软件:http://ww_iphone格式化后数据还能恢复吗

反射如何打破封装性_打破产品建议的复杂性-程序员宅基地

文章浏览阅读83次。反射如何打破封装性当前系统的真正问题(The Real Issue With the Current Sytems)With the rise of e-commerce in this era, a new frontier has opened up. It’s called product recommendations. It’s a no brainers, you recommend..._封装 复杂

随便推点

珍藏了很久的特效例子,分享给前端感兴趣的同学_例子特效-程序员宅基地

文章浏览阅读115次。svg绘制盛开的玫瑰花动画特效https://www.mk2048.com/demo/demo_target_desc_h10jjbaaaa.htmlsvg玫瑰花、svg绘图、svg盛开的花朵动画特效。纯css3基于bootstrap响应式时间轴布局代码点击》纯css3基于bootstrap响应式时间轴布局代码css3基于Bootstrap网格样式表制作响应式时间轴布局,支持水平、垂直时间轴样式显示代码。这是一款创意的时间轴布局代码。html5 canvas黑洞扭曲动画特效点击》html5 canvas黑洞_例子特效

PHP 堆栈和队列_php堆 栈 队列-程序员宅基地

文章浏览阅读868次。在PHP中数组常被当作堆栈(后进先出:LIFO)与队列(先进先出:FIFO)结构来使用。PHP提供了一组函数可以用于push与pop(堆栈)还有shift与unshift(队列)来操作数组元素。堆栈与列队在实践中应用非常广泛。先看下堆栈: php $arr = array(); array_push($arr,'aaa'); array_push($a_php堆 栈 队列

mysql 左右拼接_MySQL左连接与右连接-程序员宅基地

文章浏览阅读805次。先通过下面两个表展示一下左连接和右连接的结果1.左连接与右连接员工表:mysql> select * from employ;+-----------+------+------+| name | id | sal |+-----------+------+------+| 小王 | 1000 | 0 || 小李 | 1001 | 90 || 小..._mysq字符串左右拼接

IDEA 插件开发 创建一个控制台窗口_idea plugin how do i print messages in the console-程序员宅基地

文章浏览阅读3k次,点赞3次,收藏2次。文章目录IDEA 插件开发 创建一个控制台窗口IDEA 插件开发 创建一个控制台窗口package com.wretchant.fredis.util;import com.intellij.openapi.project.Project;import com.intellij.openapi.wm.ToolWindow;import com.intellij.openapi.wm.ToolWindowManager;import com.intellij.openapi.wm.ex.Too_idea plugin how do i print messages in the console view?

ubuntu 安装Opencv 卡在ippicv 解决方法_ubuntu opencv ippicv wechat-程序员宅基地

文章浏览阅读2k次。ippicv_2019_lnx_intel64_general_20180723.tgz 在github地址https://github.com/opencv/opencv_3rdparty/tree/ippicv/master_20180723/ippicv  git clone [email protected]:opencv/opencv_3rdparty.git  即可下载 (有其他版本,自..._ubuntu opencv ippicv wechat

MATLAB:快速傅里叶变换(FFT)_怎样永matlab中的fft算法来识别一个正弦函数的参数-程序员宅基地

文章浏览阅读1.1w次,点赞17次,收藏159次。快速傅里叶变换介绍傅立叶原理表明:任何连续测量的时序或信号,都可以表示为不同频率的余弦(或正弦)波信号的无限叠加。FFT是离散傅立叶变换的快速算法,可以将一个信号变换到频域。那其在实际应用中,有哪些用途呢?有些信号在时域上是很难看出什么特征的,但是如果变换到频域之后,就很容易看出特征(频率,幅值,初相位);FFT可以将一个信号的频谱提取出来,进行频谱分析,为后续滤波准备;通过对一个系统的输入信号和输出信号进行快速傅里叶变换后,两者进行对比,对系统可以有一个初步认识。假设采样频率 F_怎样永matlab中的fft算法来识别一个正弦函数的参数