在《漫话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

本文转自 酷小孩 博客园博客,原文链接:http://www.cnblogs.com/babycool/archive/2012/06/01/2531437.html  ,如需转载请自行联系原作者

[转载]答《漫话ID》中的疑问:UniqueID和ClientID的来源相关推荐

  1. 转:UniqueID和ClientID的来源

    转:http://www.cnblogs.com/GrayZhang/archive/2009/03/05/how-uniqueid-is-generated.html 在<漫话ID>一文 ...

  2. 用sql语句获取连续整数id中,缺失的最小id和最大id

    2019独角兽企业重金招聘Python工程师标准>>> 例如数据库表 table 结构和数据如下,要求使用sql语句查询出连续整数id中,缺失的最小和最大id. 从数据来看,最终结果 ...

  3. 错误 0xc0202049: 数据流任务 1: 无法在只读列“ID”中插入数据

    数据库导入导出时总失败,错误信息如下: 正在验证 (错误) 消息 错误 0xc0202049: 数据流任务 1: 无法在只读列"ID"中插入数据.  (SQL Server 导入和 ...

  4. mysql 中序号要怎么写_如何在mysql的字段ID中插入自动编号?

    如何在mysql的字段ID中插入自动编号?我已经有idmember作为主键 这是我尝试过的代码,请更正它 UPDATE member SET id = Row_number()over ORDER B ...

  5. 【转载】VMware vSphere中三种磁盘规格的解释说明

    在VMware vSphere中,不管是以前的5.1版本,或者是现在的6.5版本,创建虚拟机时,在创建磁盘时,都会让选择磁盘的置备类型,如下图所示,分为: 厚置备延迟置零 厚置备置零 Thin Pro ...

  6. 关于/etc/init.d/nfs脚本解读中的疑问解答

    解答学生关于/etc/init.d/nfs脚本解读中的疑问 一份老男孩的早期讲课历史解答,昨天整理NFS课程发现. 解读/etc/init.d/nfs脚本是给学生留的一个课后作业. 1. killpr ...

  7. [转载]在Vmware ESXI中安装群晖Synology DSM 5.0 (4528)

    转载 在Vmware ESXI中安装群晖Synology DSM 5.0 (4528) 文件准备 Vmware ESXi用户安装需要的文件 NB_x64_5032_DSM_50-4528_Xpenol ...

  8. 对于山东威海-无线充电组技术报告中的疑问及其回复

    简 介: 本文收录了第十七届全国大学智能车京赛山魂八队关于技术报告中的疑问及其回复内容. 关键词: 无线充电,技术报告,智能车竞赛 技术报告中的疑问 目 录 Contents 电机驱动电源 LED灯板 ...

  9. mac 系统偏好设置的“安全与隐私”中默认已经去除了允许“任何来源”App的选项

    安装macOS Sierra后,会发现系统偏好设置的" 安全与隐私"中默认已经去除了允许"任何来源"App的选项, 无法运行一些第三方应用. 如果需要恢复允许& ...

最新文章

  1. 第16届信息安全与对抗技术竞赛-Misc
  2. Jmeter组件执行顺序与作用域
  3. 艾伟也谈项目管理,创业公司技术选型参考
  4. java反射创建对象_java8反射创建对象
  5. k8s的pod资源管理与配置使用凭证的harbor仓库
  6. java安卓浏览器下载文件,JAVA实现文件下载,浏览器端得到数据没反应解决方案
  7. win7怎么把计算机放到桌面6,win7系统如何设置更改桌面图标?
  8. 都说90后不好管?聊聊跳槽这件事儿
  9. 美国航空航天局(NASA)高度集成WebFOCUS和SharePoint
  10. PHPnow中ZendDebugger与ZendOptimizer 共存
  11. 风控中英文术语手册(银行_消费金融信贷业务)_v4
  12. 火山安卓自定义组件封装源码讲解
  13. 零基础自学SQL课程 | SQL中的日期函数大全
  14. [KALI] 开启ssh远程连接
  15. matlab里的计算符号,Matlab符号运算总结
  16. 脚本框架源码,多线程,完美框架,极限多开,随意游戏可套入,端游手游
  17. Mac不能复制拷贝写入文件到移动硬盘,U盘怎么办
  18. Apple Pencil 一代和二代有什么区别
  19. 网络小贷风控有哪些数据接口?
  20. 正则表达式替换字符串中的${}里面的数据

热门文章

  1. 〔译〕TypeScript 2.0 候选版发布
  2. android实现类似于支付宝余额快速闪动的效果
  3. Windows Server 2012体验之卸载辅助域控制器
  4. 【一】Drupal 入门之新建主题
  5. idea:忽略大小写提示设置
  6. BZOJ 1396:识别子串 SA+树状数组+单调队列
  7. 如何实现MindManager数据库导入数据连接
  8. 个人学习进度条------第八周
  9. 根据上边栏和下边栏的高度进行布局
  10. 排错-tcpreplay回放错误:send() [218] Message too long (errno = 90)