时光流逝, 我们心爱的 ASP.NET 也步入了 4.0 的时代, 微软在 ASP.NET 4.0 中对很多特性做了修改. 比如我将要讨论的控件 ID 机制就是其中之一.
在 ASP.NET 4.0 之前我们总是要为控件的 ClientID 头疼, 比如明明一个叫 lblName 的 Label 放在一个叫做 grd 的 GridView 里面后, 在页面上改 Label 的 ID 就变成了诸如 grd_clt02_lblName 的一长串字符串, 如果我们在前台想在使用 JS 的时候找到该 Label, 我们不得不用到 C# 脚本来获得该 Label 在前台的确切 ID, 诸如:
- <type="text/">
- var lblName = document.getElementById("<%=lblName.ClientID %>");
- </>
在 ASP.NET 4.0 中的每个控件上都多了一个叫做 ClientIDMode 的属性, 这就是解决上面获取控件 ID 难的解决方案. 这个属性有四个可选值, 根据所选值的不同它可以控制页面上生成控件的 ID 格式.
下面就让我们来了解下 ClientIDMode 属性的四个值:
1,AutoID:
当控件的 ClientIDMode 选中为 AutoID 时, 该控件的 ClientID 值是通过串联每个祖先容器控件 (诸如 GridView,ListView,LoginView 等就是容器性控件) 的 ID 和父容器控件的 ID 和其本身的 ID 值生成的, 当然如果该控件没有在任何容器控件中其 ClientID 值就是其本身的 ID 值, 不会做任何更改. 另外如果该控件所在的父容器控件或祖先容器控件有些是显示多个数据行的容器控件(例如 GridView,ListView 就是显示多数据行的容器控件), 那么还将在这些容器控件的 ID 值的后面会插入一个递增的行号格式. 各部分之间以下划线字符 (_) 分隔. 可见在 ASP.NET 4 之前的版本中使用的就是 AutoID 方案来生成控件的 ClientID 值.
比如下面这个 GridView 里面就有一个名叫 Label1 的 ID, 我们将 Label1 的 ClientIDMode 设置为了 AutoID:
- <asp:GridView ID="grd_Account" runat="server" AllowPaging="True"
- AutoGenerateColumns="False"
- DataKeyNames="Account Number" DataSourceID="sds_account" Height="63px"
- Width="676px" PageSize="5" ClientIDMode="AutoID">
- <Columns>
- <asp:TemplateField HeaderText="Account Number" Sort ="Account Number">
- <ItemTemplate>
- <asp:Label ID="Label1" runat="server"
- Text='<%# Bind("[Account Number]") %>' ClientIDMode="AutoID"></asp:Label>
- </ItemTemplate>
- </asp:TemplateField>
- </Columns>
- </asp:GridView>
该 GridView 生成的客户端 HTML 代码就是:
- <table cellspacing="0" rules="all" border="1" id="grd_Account" style="height:63px;width:676px;border-collapse:collapse;">
- <tr>
- <th scope="col">Account Number</th>
- </tr>
- <tr>
- <td>
- <span id="grd_Account_ctl02_Label1">1060</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="grd_Account_ctl03_Label1">1200</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="grd_Account_ctl04_Label1">1510</span>
- </td>
- </tr>
- </table>
可以看到 GirdView 里面的 Label 形成了诸如 grd_Account_ctl02_Label1 格式的 ClientID, 而这正是: 父容器 ID(grd_Account)+"_"+ 行号格式 (ctl02)+"_"+ 控件自身 ID(ClientID) 这种格式生成的.
2,Static:
当控件的 ClientIDMode 选中为 Static 时, 该控件的 ClientID 值就是其本身设置的 ID 属性值, 其 ClientID 值不会受到父容器控件的影响.
比如我们把上面的代码稍作修改, 将 Label1 的 ClientIDMode 属性改为 Static:
- <asp:GridView ID="grd_Account" runat="server" AllowPaging="True"
- AutoGenerateColumns="False"
- DataKeyNames="Account Number" DataSourceID="sds_account" Height="63px"
- Width="676px" PageSize="5">
- <Columns>
- <asp:TemplateField HeaderText="Account Number" Sort ="Account Number">
- <ItemTemplate>
- <asp:Label ID="Label1" runat="server" Text='<%# Bind("[Account Number]") %>' ClientIDMode="Static"></asp:Label>
- </ItemTemplate>
- </asp:TemplateField>
- </Columns>
- </asp:GridView>
运行后查看得到的 HTML 代码:
- <table cellspacing="0" rules="all" border="1" id="grd_Account" style="height:63px;width:676px;border-collapse:collapse;">
- <tr>
- <th scope="col">Account Number</th>
- </tr><tr>
- <td>
- <span id="Label1">1060</span>
- </td>
- </tr><tr>
- <td>
- <span id="Label1">1200</span>
- </td>
- </tr><tr>
- <td>
- <span id="Label1">1510</span>
- </td>
- </tr>
- </table>
看到了吗, GridView 里每行的 Label1 的 ClientID 都以自身 ID 的值出现了, 不会受到父级容器控件的 ID 影响, 这样在前台使用 JS 时我们就能通控件本身的 ID 值找到我们想要的控件了.
此外使用 Static 后势必页面中会出现很多同名的控件 ID, 只要这些同名 ID 的控件处于页面的不同层次 (比如某一容器控件的内部和外部就是不同层次) 上那么就不会出现问题, 但是如果页面同一层次上有多个同 ID 的控件, 那么页面就会报错.
3,Inherit:
这个属性其实没什么好说的, 如果控件的 ClientIDMode 选中为 Inherit, 那么表示该控件的 ClientIDMode 会使用父级容器控件的 ClientIDMode 值, 如果父级容器控件的 ClientIDMode 也为 Inherit, 那么会使用更上层容器控件的 ClientIDMode 值, 直到回溯到页面的 ClientIDMode 值为止, 页面的 ClientIDMode 值默认为 Predictable , 你可以在页面上的 <%@ Page%> 指令中对该值做更改. 此外 Inherit 也是 ASP.NET 4.0 中所有控件的 ClientIDMode 属性的默认值.
4,Predictable:
首先我先说明下之所以最后写 Predictable, 是因为我发现控件的 ClientIDMode 为 Predictable 时生成 ClientID 的机制会非常复杂, 要分好几个部分分别进行讨论, 其中还有特殊情况, 所以我在这里只能说尽量将我发现的 Predictable 生成 ClientID 的机制阐述清楚.
当控件的 ClientIDMode 选中为 Predictable 时, 该控件的 ClientID 值是通过串联父容器控件 (诸如 GridView,ListView,LoginView 等就是容器性控件) 的 ClientID 值生成的. 另外如果该控件是在显示多个数据行的父容器控件或祖先容器控件中(例如 GridView,ListView 就是显示多数据行的容器控件), 则还会在该控件 ClientID 值的末尾添加 ClientIDRowSuffix 属性中指定的数据字段的值. 对于 GridView 控件, ClientIDRowSuffix 属性可以指定多个数据字段. 如果 ClientIDRowSuffix 属性为空白, 则在末尾添加递增的行号, 而非数据字段值. 各部分之间以下划线字符 (_) 分隔.
以上是 MSDN 的说法, 但是经过试验, 我发现 Predictable 的特性更应该是用这么个式子来表达:
Inherit[+"_"+ClientIDRowSuffix]
意思就是说, 如果一个控件的 ClientIDMode 选中为 Predictable, 那么在 ASP.NET 生成该控件的 ClientID 时首先会去看该控件所属的父容器控件的 ClientIDMode 是什么值, 然后先用该控件父容器控件的 ClientIDMode 规则生成该控件本身的 ClientID, 最后如果该控件所属的父容器控件或祖先容器控件是显示多个数据行的容器控件, 还会根据父容器控件或祖先容器控件的 ClientIDRowSuffix 属性的值在该控件已生成的 ClientID 后面加上一个后缀字符串.
下面将几种情况逐一列出来单独解释:
<1 > 如果父容器控件的 ClientIDMode 值为 AutoID
如果父容器控件或祖先容器控件为显示多个数据行的容器控件, 那么该控件的 ClientID 格式为:[父 / 祖先容器控件的 ID+"_"+[行号格式 +"_"]]+ 该控件自身 ID+"_"+[ClientIDRowSuffix], 其中 ClientIDRowSuffix 部分是什么后面会单独说明, 其中:[父 / 祖先容器控件的 ID+"_"+[行号格式 +"_"]], 就是该控件自身 ClientIDMode 值继承父容器控件 ClientIDMode 值 AutoID 生成的 ClientID 结果, 其中的 [行号格式 +"_"] 部分是否存在依赖于 [父 / 祖先容器控件] 部分是否是显示多个数据行的容器控件(这里不明白请看前面的 AutoID 部分).
如果父容器控件或祖先容器控件都不是显示多个数据行的容器控件, 那么该控件的 ClientID 格式为:[父 / 祖先容器控件的 ID+"_"]+ 该控件自身 ID, 可见这个格式就是该控件自身 ClientIDMode 值继承父容器控件 ClientIDMode 值 AutoID 生成的 ClientID 结果(这里不明白请看前面的 AutoID 部分).
下面我就举一个父容器控件是多数据行容器控件且其 ClientIDMode 为 AutoID 的例子, 将上面的代码再做更改, 将 Label1 的 ClientIDMode 属性值改为 Predictable, 并且设置其父容器控件 grd_Account 的 ClientIDMode 为 AutoID:
- <asp:GridView ID="grd_Account" runat="server" AllowPaging="True"
- AutoGenerateColumns="False"
- DataKeyNames="Account Number" DataSourceID="sds_account" Height="63px"
- Width="676px" PageSize="5" ClientIDMode="AutoID">
- <Columns>
- <asp:TemplateField HeaderText="Account Number" Sort ="Account Number">
- <ItemTemplate>
- <asp:Label ID="Label1" runat="server" Text="Logged" ClientIDMode="Predictable"></asp:Label>
- </ItemTemplate>
- </asp:TemplateField>
- </Columns>
- </asp:GridView>
其生成的 HTML 代码为:
- <table cellspacing="0" rules="all" border="1" id="grd_Account" style="height:63px;width:676px;border-collapse:collapse;">
- <tr>
- <th scope="col">Account Number</th>
- </tr>
- <tr>
- <td>
- <span id="grd_Account_ctl02_Label1_0">Logged</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="grd_Account_ctl03_Label1_1">Logged</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="grd_Account_ctl04_Label1_2">Logged</span>
- </td>
- </tr>
- </table>
可以看到生成的 Label 的控件的 ID 诸如: grd_Account_ctl02_Label1_0 正是上面所述格式: 父容器控件的 ID(grd_Account)+"_"+ 行号格式(ctl02)+"_"+ 该控件自身 ID(Label1)+"_"+[ClientIDRowSuffix](0).
<2 > 如果父容器控件的 ClientIDMode 值为 Static
如果父容器控件或祖先容器控件为显示多个数据行的容器控件, 那么该控件的 ClientID 格式为: 该控件自身 ID+"_"+[ClientIDRowSuffix], 其中 ClientIDRowSuffix 部分是什么后面会单独说明, 其中: 该控件自身 ID, 就是该控件自身 ClientIDMode 值继承父容器控件 ClientIDMode 值 Static 生成的 ClientID 结果(这里不明白请看前面的 Static 部分).
如果父容器控件或祖先容器控件都不是显示多个数据行的容器控件, 那么该控件的 ClientID 格式为: 该控件自身 ID, 可见这个格式就是该控件自身 ClientIDMode 值继承父容器控件 ClientIDMode 值 Static 生成的 ClientID 结果(这里不明白请看前面的 Static 部分).
下面我就举一个父容器控件是多数据行容器控件且其 ClientIDMode 为 Static 的例子, 将上面的代码再做更改, 将 Label1 的 ClientIDMode 属性值改为 Predictable, 并且设置其父容器控件 grd_Account 的 ClientIDMode 为 Static:
- <asp:GridView ID="grd_Account" runat="server" AllowPaging="True"
- AutoGenerateColumns="False"
- DataKeyNames="Account Number" DataSourceID="sds_account" Height="63px"
- Width="676px" PageSize="5" ClientIDMode="Static">
- <Columns>
- <asp:TemplateField HeaderText="Account Number" Sort ="Account Number">
- <ItemTemplate>
- <asp:Label ID="Label1" runat="server" Text="Logged" ClientIDMode="Predictable"></asp:Label>
- </ItemTemplate>
- </asp:TemplateField>
- </Columns>
- </asp:GridView>
其生成的 HTML 代码为:
- <table cellspacing="0" rules="all" border="1" id="grd_Account" style="height:63px;width:676px;border-collapse:collapse;">
- <tr>
- <th scope="col">Account Number</th>
- </tr>
- <tr>
- <td>
- <span id="Label1_0">Logged</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="Label1_1">Logged</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="Label1_2">Logged</span>
- </td>
- </tr>
- </table>
可以看到生成的 Label 的控件的 ID 诸如: Label1_0 正是上面所述格式: 该控件自身 ID(Label1)+"_"+[ClientIDRowSuffix](0).
<3 > 如果父容器控件的 ClientIDMode 值为 Predictable
如果父容器控件或祖先容器控件为显示多个数据行的容器控件, 那么该控件的 ClientID 格式为: 父容器控件的 ClientID+"_"+ 该控件自身 ID+"_"+[ClientIDRowSuffix], 其中 ClientIDRowSuffix 部分是什么后面会单独说明, 可见这种情况才属于 MSDN 上所说的格式.
如果父容器控件或祖先容器控件都不是显示多个数据行的容器控件, 那么该控件的 ClientID 格式为: 父容器控件的 ClientID+"_"+ 该控件自身 ID, 可见这种情况才是 MSDN 上所说的格式.
下面我就举一个父容器控件是多数据行容器控件且其 ClientIDMode 为 Predictable 的例子, 将上面的代码再做更改, 将 Label1 的 ClientIDMode 属性值改为 Predictable, 并且设置其父容器控件 grd_Account 的 ClientIDMode 也为 Predictable:
- <asp:GridView ID="grd_Account" runat="server" AllowPaging="True"
- AutoGenerateColumns="False"
- DataKeyNames="Account Number" DataSourceID="sds_account" Height="63px"
- Width="676px" PageSize="5" ClientIDMode="Predictable">
- <Columns>
- <asp:TemplateField HeaderText="Account Number" Sort ="Account Number">
- <ItemTemplate>
- <asp:Label ID="Label1" runat="server" Text="Logged" ClientIDMode="Predictable"></asp:Label>
- </ItemTemplate>
- </asp:TemplateField>
- </Columns>
- </asp:GridView>
其生成的 HTML 代码为:
- <table cellspacing="0" rules="all" border="1" id="grd_Account" style="height:63px;width:676px;border-collapse:collapse;">
- <tr>
- <th scope="col">Account Number</th>
- </tr>
- <tr>
- <td>
- <span id="grd_Account_Label1_0">Logged</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="grd_Account_Label1_1">Logged</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="grd_Account_Label1_2">Logged</span>
- </td>
- </tr>
- </table>
可以看到生成的 Label 的控件的 ID 诸如: grd_Account_Label1_0 正是上面所述格式: 父容器控件的 ClientID(grd_Account)+"_"+ 该控件自身 ID(Label1)+"_"+[ClientIDRowSuffix](0).
<4 > 如果父容器控件的 ClientIDMode 值为 Inherit
这种情况没什么好说的, 因为父容器控件的 ClientIDMode 值会继承其所在更上层的祖先容器控件的 ClientIDMode 值, 继承后也属于上面三种情况之一.
最后来说说 ClientIDRowSuffix 部分是什么, 如果父容器控件或祖先容器控件是显示多数据行的容器控件(后面会讨论到如果控件的 ClientIDMode 为 Predictable, 在判断该控件是否在显示多数据行的容器控件中时, 会有一种特殊的穿透现象), 那么父容器控件或祖先容器控件会有个属性叫 ClientIDRowSuffix, 比如本例中的 GridView 的 ClientIDRowSuffix 属性, 这个属性的作用是为设定 ClientIDMode 值为 Predictable 的子控件生成 ClientID 的后缀字符串(就是上面那些 ClientID 格式中的 ClientIDRowSuffix 部分):
如果 ClientIDRowSuffix 属性为空白, 则在已生成的子控件 ClientID 末尾添加递增的行号并在行号前面加上下划线字符 (_) 分隔, 比如上面的例子中由于都没有在 GridView 上设置 ClientIDRowSuffix 属性, 所以 ClientIDRowSuffix 为空白, 那么生成的子控件 ClientID 最末位都有诸如_0,_1,_2 等的递增行号.
此外还可以设置 ClientIDRowSuffix 属性值为父容器控件或祖先容器控件中 DataSource 数据源中的字段, 这样生成子控件 ClientID 的后缀字符串为 ClientIDRowSuffix 指定字段在该行的数据值, 并且 ClientIDRowSuffix 属性可指定多个 DataSource 数据源中的数据字段, 那么在生成子控件 ClientID 时会将每个数据字段在该行的值用下划线字符 (_) 进行分隔然后作为 ClientID 后缀字符串.
现在就举个例子, 将上面的代码再做更改将 Label1 的 ClientIDMode 属性值改为 Predictable, 并且设置其父容器控件 grd_Account 的 ClientIDMode 也为 Predictable, 并且将 grd_Account 的 ClientIDRowSuffix 设置为数据源 sds_account 的 Account Number 字段:
- <asp:GridView ID="grd_Account" runat="server" AllowPaging="True"
- AutoGenerateColumns="False"
- DataKeyNames="Account Number" DataSourceID="sds_account" Height="63px"
- Width="676px" PageSize="5" ClientIDMode="Predictable" ClientIDRowSuffix="Account Number">
- <Columns>
- <asp:TemplateField HeaderText="Account Number" Sort ="Account Number">
- <ItemTemplate>
- <asp:Label ID="Label1" runat="server" Text="Logged" ClientIDMode="Predictable"></asp:Label>
- </ItemTemplate>
- </asp:TemplateField>
- </Columns>
- </asp:GridView>
其生成的 HTML 代码为:
- <table cellspacing="0" rules="all" border="1" id="grd_Account" style="height:63px;width:676px;border-collapse:collapse;">
- <tr>
- <th scope="col">Account Number</th>
- </tr>
- <tr>
- <td>
- <span id="grd_Account_Label1_1060">Logged</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="grd_Account_Label1_1200">Logged</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="grd_Account_Label1_1510">Logged</span>
- </td>
- </tr>
- </table>
可以看到生成子控件的 ClientID 的后缀字符串为 Account Number 字段在 GridView 上每行的值: 1060,1200,1500, 其不再是递增的行号.
EX: 最后 Predictable 还有一个很特别的特性:
当控件的 ClientIDMode 为 Predictable 且该控件在多个嵌套的容器控件中时, 判断该控件是否在显示多数据行的容器控件中时, 会具有层次穿透性, 它不但会考察父容器控件, 还会考察祖先容器控件.
下面就举个例子来说明这种情况, 首先 grd_Account 为显示多数据行的容器控件, 它的 ClientIDMode 设置为 Static, 在它内部有一个 ID 为 LoginView1 的 LoginView, 我们知道 LoginView 也是容器性控件, 只不过它不是显示多数据行的容器控件, 这里设置 LoginView1 的 ClientIDMode 属性为 Predictable, 在 LoginView1 里面再放置一个 ID 为 Label1 的 Label, 它的 ClientIDMode 没有设置, 所以其值也默认继承为 Predictable, 下面是代码:
- <asp:GridView ID="grd_Account" runat="server" AllowPaging="True"
- AutoGenerateColumns="False"
- DataKeyNames="Account Number" DataSourceID="sds_account" Height="63px"
- Width="676px" PageSize="5" ClientIDMode="Static">
- <Columns>
- <asp:TemplateField HeaderText="Account Number" Sort ="Account Number">
- <ItemTemplate>
- <asp:LoginView ID="LoginView1" runat="server" ClientIDMode="Predictable">
- <LoggedInTemplate>
- <asp:Label ID="Label1" runat="server" Text="Logged"></asp:Label>
- </LoggedInTemplate>
- </asp:LoginView>
- </ItemTemplate>
- </asp:TemplateField>
- </Columns>
- </asp:GridView>
可以看到在嵌套层次结构中, 由于 LoginView1 所属的父容器控件 grd_Account 是显示多数据行的容器控件, 所以 LoginView1 的 ClientID 应该是诸如: LoginView1_0,LoginView1_1 等有 ClientIDRowSuffix 部分的格式, 这没有问题, 但是按道理来说 Label1 所在的父容器控件 LoginView1 不是显示多数据行的容器控件, 那么 Label1 生成的 ClientID 应该是诸如 LoginView1_0_Label1,LoginView1_1_Label1 等这样的没有 ClientIDRowSuffix 部分的格式, 但是为我们来看一下生成的 HTML 代码:
- <table cellspacing="0" rules="all" border="1" id="grd_Account" style="height:63px;width:676px;border-collapse:collapse;">
- <tr>
- <th scope="col">Account Number</th>
- </tr>
- <tr>
- <td>
- <span id="LoginView1_0_Label1_0">Logged</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="LoginView1_1_Label1_1">Logged</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="LoginView1_2_Label1_2">Logged</span>
- </td>
- </tr>
- </table>
可以看到生成的 Label1 的 ClientID 都带表示递增行号的后缀字符串 0,1,2 等(此外请注意 LoginView 不会产生 HTML 代码), 也就是有 ClientIDRowSuffix 部分.
我们再将 grd_Account 的 ClientIDRowSuffix 属性更改为数据源中的 Account Number 字段后再来看看生成的 HTML 代码:
- <table cellspacing="0" rules="all" border="1" id="grd_Account" style="height:63px;width:676px;border-collapse:collapse;">
- <tr>
- <th scope="col">Account Number</th>
- </tr>
- <tr>
- <td>
- <span id="LoginView1_1060_Label1_1060">Logged</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="LoginView1_1200_Label1_1200">Logged</span>
- </td>
- </tr>
- <tr>
- <td>
- <span id="LoginView1_1510_Label1_1510">Logged</span>
- </td>
- </tr>
- </table>
可以看到生成的 Label1 的 ClientID 也都带 ClientIDRowSuffix 部分, 只不过 ClientIDRowSuffix 部分现在是数据源中 Account Number 字段的值.
由此可见在判断 Label1 是否在显示多数据行的容器控件中时, 判定机制进行了穿透, 即假如有这么一组嵌套的容器控件: Control_1<-Control_2<-Control_3<-.......<-Control1_n-1<-Control_n, 其中 Control_n 的 ClientIDMode 设置或继承为 Predictable, 且其中 Control_2 到 Control1_n-1 都不是显示多数据行的容器控件, 只有最外层的 Control_1 是显示多数据行的容器控件, 那么在判定最里面的 Control_n 是否在显示多数据行的容器控件时, 参考的是从里到外第一个是显示多数据行的容器控件 Control_1(即从里到外只要任意一个容器控件是显示多数据行的容器控件, 就认为 Control_n 是在显示多数据行的容器控件中), 并且 Control_n 的后缀字符串部分参考的也是 Control_1 的 ClientIDRowSuffix 属性, 从而忽略中间那些不是显示多数据行的容器控件(Control_2 到 Control1_n-1), 所以控件的 ClientIDMode 属性为 Predictable 时, 就是用这种穿透判定来判断该控件是否在显示多数据行的容器控件中的.
来源: https://www.jb51.net/article/150503.htm