理解 ASP.NET 模板






4.83/5 (9投票s)
如何访问由 ASP.NET 模板创建的控件...
引言
如果您从事使用 ASP.NET 在 Web 上显示数据的业务,您可能使用过模板。所有用于显示表格数据的数据绑定控件(如 TreeView、DataList、GridView、Repeater 和 ListView)都使用模板来简化您的工作。您只需定义一次模板,数据绑定机制就会为您重复数据的所有行。这个想法非常高效,但当使用模板中的 ID 访问数据行中的控件时,会存在一些误解。这可能会令人困惑,因为您只声明了一个具有该 ID 的控件,但现在有数十个(数百或数千个)相同的块的重复。更令人困惑的是客户端呈现的重复 ID;作为一名 Web 开发人员,您可能知道 HTML 中的 ID 必须是唯一的...
背景
本文的触发因素是几个关于“为什么在对具有模板的控件运行 FindControl 后会出现错误:‘未将对象引用设置到对象的实例’”的问答问题。或者“如何在客户端使用 jQuery 获取 GridView 中的选定值”针对同一类控件。
所有这些问题都源于对具有模板的控件中 ID 创建的理解不足...
在本文中,我将解释这些问题,并提供示例说明如何访问控件 - 在服务器端和客户端...
让我们看看事实
在图像中,您可以看到两列,它们代表绑定到一个包含 5 本书数据的 XML 文件的 DataList 控件的对象层次结构。左侧列显示服务器端的 ID,而右侧列显示客户端的 ID。用于创建此对象树的 DataList 控件(以及模板!)看起来像这样
<asp:DataList ID="idBookList" runat="server">
<ItemTemplate>
<asp:Panel runat="server" ID="idGenre" />
<asp:Panel runat="server" ID="idTitle" />
<asp:Panel runat="server" ID="idAuthor" />
<asp:Panel runat="server" ID="idPrice" />
</ItemTemplate>
</asp:DataList>
正如您所见,服务器端 ID 遵循模板 - 这会创建多个具有相同 ID 的控件。同时,ASP.NET 渲染引擎在客户端创建了不同的 ID,以解决 HTML 中唯一 ID 的问题...
现在,无论是在服务器端还是客户端,如果您尝试使用模板中的 ID 选择控件(元素) - 假设是 idPrice - 您会遇到一些问题。在服务器端,FindControl 应该返回多个控件,但无法做到,所以您得到 null。在客户端,不存在这样的控件...
现在您已经看到了模板在运行时如何展开自身,我将扫描最常见的错误并给出一些解决方案。
FindControl 返回 null
让我们看一个示例(我看到的最常见的示例)
您的网页上有一个 CheckBox 和一个 DataList(来自前一个示例)。CheckBox 用于控制 DataList 中价格的显示。
protected override void OnLoad(EventArgs e)
{
base.OnLoad( e );
// here you do the data binding
idBookList.FindControl("idPrice").Visible = idShowPrice.Checked;
}
目的很清楚 - 如果 CheckBox 关闭,则隐藏价格部分;如果 CheckBox 打开,则显示价格。问题是 FindControl 返回 null!!!现在,如果您停下来思考一下,这是可以预期的。您现在有许多具有 ID idPrice 的控件,那么您想检索哪一个?答案当然是 - 全部,问题是 FindControl 只能返回一个!
对此有两种可能的解决方案
On...DataBound 事件
对于所有使用模板的控件,都存在一个事件,该事件在数据绑定过程中创建的每个项上都会触发。对于 DataList,它是 OnItemDataBound;对于 GridView,它是 OnRowDataBound,依此类推...此事件将您置于单个数据行的上下文中,在该上下文中,任何给定 ID 的控件只有一个(因为单个数据行会创建模板的单个实例),因此 FindControl 可以正常工作...
让我们用我们的示例来看看
protected override void OnLoad(EventArgs e)
{
base.OnLoad( e );
// here you do the data binding
idBookList.ItemDataBound += idBookList_ItemDataBound;
}
void idBookList_ItemDataBound(object sender, DataListItemEventArgs e)
{
e.Item.FindControl("idPrice").Visible = idShowPrice.Checked;
}
这是操作模板中控件最常见的方式,很可能是您将使用的方式。
一个智能的扩展方法
如果上述解决方案不适合您,并且您想处理所有具有相同 ID 的控件,我提供了一个扩展方法,它可以解决您的所有问题...
public static class Extensions
{
public static List<Control> FindAllControls ( this Control Parent, string ID )
{
List<Control> oControls = null;
Stack oStack = new Stack( );
oStack.Push( Parent );
while ( oStack.Count != 0 )
{
Control oCheckControl = ( Control )oStack.Pop( );
if ( oCheckControl.ID == ID )
{
if ( oControls == null )
{
oControls = new List<Control>( );
}
oControls.Add( oCheckControl );
}
if ( oCheckControl.HasControls( ) )
{
foreach ( Control oChildControl in oCheckControl.Controls )
{
oStack.Push( oChildControl );
}
}
}
return ( oControls );
}
}
这里有一个如何使用它的示例
protected override void OnLoad(EventArgs e)
{
base.OnLoad( e );
// here you do the data binding
List<Control> oPriceControls = idBookList.FindAllControls("idPrice");
foreach(Control oPriceControl in oPriceControls)
{
oPriceControl.Visible = idShowPrice.Checked;
}
}
这两种访问模板创建的控件的方法可以帮助您,更重要的是,它们可以帮助您理解 ID 的创建方式!
$('#idPrice') 是一个空选择器
[我冒昧地在客户端使用了 jQuery,因为它使我的生活更轻松 :-) 。所有示例代码都可以用纯 JavaScript 编写,但我的重点不是教您 JavaScript,而是解释 ASP.NET 中的模板,您将不得不接受这一点...]
我相信,在您看到这一切之后,搜索 ID 为 idPrice 的元素(或元素)将一无所获的原因就很清楚了!客户端上没有 ID 为 'idPrice' 的元素。有 idBookList_idPrice_0、idBookList_idPrice_1 等等。存在一个特定的模式用于创建这些 ID,您可能可以编写一个循环来创建正确的 ID 来扫描所有元素,但我希望向您展示一种更适合客户端 HTML/CSS/JavaScript 世界的方法...
<asp:DataList ID="idBookList" runat="server">
<ItemTemplate>
<asp:Panel runat="server" ID="idGenre" CssClass="genre" />
<asp:Panel runat="server" ID="idTitle" CssClass="title" />
<asp:Panel runat="server" ID="idAuthor" CssClass="author" />
<asp:Panel runat="server" ID="idPrice" CssClass="price" />
</ItemTemplate>
</asp:DataList>
正如您所见,我为每个项目添加了 CSS 类 - 我相信您已经有一些用于格式化的 CSS,您也可以使用它...
这个 CSS 类名对于从模板创建的同一控件的每个实例都是通用的,我们将使用它来一起访问它们...
$(document).ready(function () {
$('#idShowPrice').click(function (e) {
if ($(this).is(':checked')) {
$('.price').show();
}
else {
$('.price').hide();
}
});
即使不完全理解 jQuery 语法,您也可以看到发生了什么:在 CheckBox 的点击事件上,我们隐藏或显示价格元素。$('.price') 向我们返回所有由模板创建的、具有 'price' CSS 类的元素!
另一个选项是在行上下文中处理单个行的元素,比如说在行点击时...
$(document).ready(function () {
$('#idBookList TR').click(function (e) {
$(this).find('TD div.price').toggle();
});
});
在此示例中,我们在行点击时切换“price”列的可见性。这与之前的方法相同,不同之处在于,我们正在查找我们刚刚点击的行上下文中的“price” CSS 类 - $(this)。
摘要
正如您所见,访问由 ASP.NET 模板创建的控件/元素并非易事。在服务器端,您只能在正确的上下文(数据行的上下文)中使用常见的 FindControl 方法,否则您必须编写自己的方法来遍历对象树并找到您感兴趣的所有控件。
在客户端,没有实用的方法(有一些不实用的方法)可以使用 ID 来查找由模板创建的元素。因此,我们使用 CSS 类来标识同一组的元素,选择并操作它们...
使用代码
随附的示例包含所有四种解决方案的页面。还有一个 FindAllControls 扩展方法的实现。