1. 程式人生 > >ASP.NET 4.0配置檔案中的ClientIDMode屬性

ASP.NET 4.0配置檔案中的ClientIDMode屬性

ASP.NET 4.0配置檔案中的ClientIDMode屬性
來自森大科技官方部落格 http://www.cnsendblog.com/index.php/?p=99
時光流逝,我們心愛的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,諸如:
<script type="text/javascript">
var lblName = document.getElementById("<%=lblName.ClientID %>");
</script>

在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" SortExpression="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(grdAccount)+""+行號格式(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" SortExpression="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" SortExpression="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(grdAccount)+""+行號格式(ctl02)+""+該控制元件自身ID(Label1)+""+ClientIDRowSuffix

<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" SortExpression="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諸如:Label10正是上面所述格式:該控制元件自身ID(Label1)+""+ClientIDRowSuffix

<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" SortExpression="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(grdAccount)+""+該控制元件自身ID(Label1)+"_"+ClientIDRowSuffix

<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" SortExpression="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" SortExpression="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時,就是用這種穿透判定來判斷該控制元件是否在顯示多資料行的容器控制元件中的。