尽管有丰富、功能强大的编程接口,ASP.NET1.xDataGrid控件仍需要编写大量自定义代码来处理普通操作,如分页、排序、编辑和删除数据。例如,当用户单击以保存或取消更改时,DataGrid控件能够引发事件但不提供更多的功能。如果要将更改存储到持续媒介(如一个数据库)之中,则必须自己处理UpdateCommand事件,检索更改后的值,编写一条SQL命令,然后从该处提交更新。
DataGrid控件限制普通数据操作的引发事件,因为它是一个数据源不可知的控件,能够绑定到任何可枚举的数据对象。执行数据操作(如更新或删除)需要直接连接到一个特定的数据源。在ASP.NET1.x中,则通过编写特定于应用程序的ADO.NET代码解决这个问题的。
ASP.NET2.0改进了数据绑定体系结构,引入了新的系列组件(数据源对象)作为数据绑定控件与ADO.NET对象之间的桥梁。这些源对象提升了一个略为不同的编程模型,提供了新功能和新成员。您的ASP.NET2.0应用程序应该使用最新的网格控件—GridView,显示数据报告。与之相似的DataGrid控件仍然支持,但DataGrid不能充分利用数据源组件的特定功能。
GridView控件是DataGrid的接替者,并从几个方面扩展了后者的功能。首先,它完全支持数据源组件,能够自动处理诸如分页、排序和编辑等数据操作,前提是绑定的数据源对象支持这些操作。另外,GridView控件有一些比DataGrid优越的功能上的改进。特别是,它支持多个主键字段,公开了一些用户界面的改进功能和一个处理与取消事件的新模型。
GridView附带了一对互补的视图控件:DetailsView和FormView。通过这些控件的组合,您能够轻松地建立主/详细视图,而只需少量代码,有时根本不需要代码。
GridView与DataGrid
ASP.NET2.0中数据绑定控件的类层次结构比ASP.NET1.x中的更一致。在2.0版本中,所有控件无论有什么样的实际实现过程和用户界面特点,均从同一个基类(BaseDataBoundControl类)派生。图1显示新的类关系图。DataGrid和其他1.x版本的控件(如Repeater和DataList)没有包含在该关系图中。这些现有控件的继承树与ASP.NET1.x的相同。特别是,Repeater继承了WebControl,而DataList和DataGrid继承了BaseDataList。如图1所示,GridView是一个复合数据绑定控件,它与其他所有数据绑定控件(包括DropDownList、DetailsView和ListBox)共享一组方法和属性。
图1ASP.NET类关系图
GridView和DataGrid控件的高级功能相似,但基础却不同。GridView尽可能地保留了DataGrid的对象模型,以便轻松地从现有页面进行移植。但是,基于DataGrid的代码与新的基于GridView的代码不可能100%兼容。
DataGrid与GridView控件的另一个主要差异在于自适应用户界面。与1.x版本的DataGrid不同的是,GridView也能在移动设备上显示。换句话说,您能够使用相同的用于桌面页面的网格控件在移动设备上生成报告。2.0版本的DataGrid也能自适应地显示,但是它的UI功能没有GridView丰富。
在ASP.NET2.0中,改进后的DataGrid控件支持诸如主题和个性化等通用的控件功能。此外,新的DataGrid控件可由一个数据源控件填充。但要记住,绑定到数据源对象的DataGrid只能用于读取数据。要实际修改底层数据源,仍然需要一些用户定义的代码。而GridView控件可以利用底层数据源的功能并自动删除或更新记录。注意,GridView控件也支持传统的基于DataSource属性和DataBind方法的绑定机制。尽管完全支持这种绑定机制,但是不鼓励使用这样的编程实践方法。
GridView和数据源控件
那么,数据源控件是什么?我在2004年6月一期的MSDN®Magazine中详细介绍了ASP.NET2.0的这项流行的新功能。简言之,一个数据源控件就是一组Microsoft®.NETFramework类,它有利于数据存储和数据绑定控件之间的双向绑定。现有的控件(如DataGrid)以及新的数据绑定控件(如GridView),尽管绑定能力不同,但都能绑定到一个数据源。
一个数据源控件代表了数据源的主要功能:选择、插入、更新和删除。数据源控件能代表任何数据源:从关系数据源库到XML文件,从流数据到业务对象。如果简要介绍能让您想起.NET的托管提供程序,请参见图2。
图2数据源控件、GridView和数据源
数据源控件可以位于一些.NET数据提供程序的上层,在数据绑定控件和数据源之间形成一个中间层。数据源控件也会公开一个提供基本操作的公共接口。一些数据绑定控件—特别是GridView控件,将这些命令与其他与数据有关的操作一起,绑定到适当的自动编辑。
数据源控件通过其属性和方法,将绑定内容以一组命名的视图形式公开。IDataSource接口提供从数据源检索数据视图的基本功能集,所有数据源控件都实现了这个接口。ASP.NET2.0提供一些内置数据源控件,如图3所列。图3列出的数据源控件属于两类:列表和分层组件。SiteMapDataSource和XmlDataSource组件是分层数据源控件,用于像TreeView和Menu控件这样的分层组件。其他各种组件用于管理列表数据。
图4中的代码说明如何在一个示例页面上将GridView和DataGrid绑定到同一个数据源控件。在ASP.NET2.0中,这是推荐的数据绑定方法。SqlDataSource控件的特点是一个ConnectionString属性加上SelectCommand、UpdateCommand、InsertCommand和DeleteCommand属性的任意组合。所有属性都是字符串形式,并且引用带有可选参数的命令文本:
<asp:SqlDataSourcerunat="server"
ID="MySource"
ConnectionString="SERVER=(local);
DATABASE=northwind;IntegratedSecurity=SSPI;"
SelectCommand="SELECT*FROMemployeesWHEREemployeeid>@MinID">
<SelectParameters>
<asp:ControlParameterName="MinID"
ControlId="EmpID"
PropertyName="Text"/>
</SelectParameters>
</asp:SqlDataSource>
每个数据源控件由唯一的ID表示。ID是连结数据绑定控件和数据源控件之间的纽带。通过DataSourceId属性将GridView绑定到一个数据源控件。例如,每当网格需要获取数据时,就执行与SQLDataSource控件相关联的SelectCommandSQL命令。当网格需要更新或删除一条记录时,就执行相应的UpdateCommand或DeleteCommandSQL命令。如果不存在这样的命令,则引发一个异常。在内部,当用户删除或更新一条记录时,GridView就像1.x版本的DataGrid一样引发事件。但是与DataGrid不同的是,GridView为这些事件定义内部的处理程序。默认的处理程序检索绑定数据源定义的命令来处理和执行这些操作。图4说明,在保持网格显示或更新数据的标记后无需编写代码。在更复杂的情况下,您可能需要编写一些代码。
图5GridView和DataGrid
数据源控件和GridView控件通常用于无代码数据绑定。图5显示图4的代码生成的输出结果。
在ASP.NET2.0中,除了DataSource和DataMember,DataGrid还公开了DataSourceId属性。DataSourceId属性将DataGrid连接到同一页面上定义的一个合法数据源对象。但是,DataGrid不提供与GridView同一级别的自动化操作。当用户单击以更新一条记录时,DataGrid引发UpdateCommand事件,而GridView除了引发Updating和Updated事件外,还检索和执行数据源更新命令,允许用户自定义发送到数据源控件的信息。
GridView对象模型
GridView与DataGrid的整体结果看起来相似。一些通用元素经过了重命名,一些通用功能现在需要不同的语法。总之,如果熟悉DataGrid控件,则可立即自如地运用GridView。图6详尽列出了组成GridView的新元素(请注意,其中一些元素,如DetailLinkStyle,仅用于在移动设备上显示网格)。行元素通过Rows集合中的GridViewRow类生成的实例进行显示。GridViewRow类映射到DataGridItem类,而Rows明确替代了DataGrid的项目集合。行类型由DataControlRowType枚举表示,用来指示位置和角色(例如,页脚、页眉、页导航和数据行)。GridView还引入一个新概念—行元素状态。该行状态由DataControlRowState标记的枚举值表示—通常值是Edit,可选值为Insert和Selected。有趣的是,这两类枚举恰巧由所有数据视图控件(GridView、DetailsView和FormView)共享。
除了引入符合自适应显示的元素之外,GridView仅有一个其他类型的新元素—空数据行。当GridView绑定到一个空数据源时,会选择性地显示一些默认内容,为用户提供反馈。在这种情况下显示的内容依赖于新的空数据行元素的内容。可通过一个属性(EmptyDataText)或一个模板(EmptyDataTemplate)设置该行的内容。
GridView控件的属性主要分为三种类型:行为、可视设置和状态。图7列出GridView的一些属性。请查看包括EnableSortingAndPagingCallbacks、EmptyDataText和UseAccessibleHeader在内的新属性以及被重命名或改编的属性,后者实现了DataGrid已经支持的功能。
编程模型与按钮列略有不同。在ASP.NET1.x的DataGrid中,您不得不通过添加特定的列类型来创建一个Edit按钮—EditCommandColumn。如果要创建一个Delete或Select按钮列,则必须添加通用的按钮列并预定义一个命令名。GridView对象则更一致、更简洁。它基于三个新的布尔属性:AutoGenerateEditButton、AutoGenerateDeleteButton和AutoGenerateSelectButton。当其中任何一个属性设置为真时,网格中分别添加一个Edit、Delete或Select命令按钮列。例如,当AutoGenerateEditButton属性设置为真时,在网格中自动为每个数据行添加带有Edit按钮的一列。也可以手动添加这些按钮,方法是在列集合中添加一个CommandField对象。Columns属性列出了列对象,这些对象很像DataGrid的Columns属性列出的对象。根据客户的反馈,其中也添加了几个帮助器属性。特别是,您现在能够为每个显示行存储多个键值。实际上,DataGrid的DataKeyField字符串属性已经扩展为一个字段名数组。新的属性命名为DataKey,用于存储由字段名组成的一个字符串数组,这个字符串数组唯一标识一个数据行。DataKey是特定行的值的相应数组。它返回DataKey对象的集合。每个DataKey对象包含一个键名值,DataKey的DataKey对象数量与GridView的显示行数相同。
SortDirection和SortExpression跟踪当前的网格排序。这些属性用于在内部实现自动翻转排序,标记网格当前排序次序。每个对象的PagerSettings组包含配置用户界面、行为和页导航位置的所有属性。现在,页导航支持的导航模式不但包括首行和尾行,还包括下一行和上一行。
GridView控件也能够使用一个基于回调的轻量型机制来进行排序和分页。您可以通过设置EnableSortingAndPagingCallbacks布尔属性来开启和关闭此功能。当单击排序或分页链接来启用回调时,GridView请求排序数据或下一页,不回发可视页面。(这里发生了一个往返过程,但是无页面刷新,因此您不知道。)请注意,这个功能有个警告:启用GridView中的选项时,新页面保留当前选定的索引。如果有与之相关联的详细信息页面,那么选定的内容将失去同步。处理类似PageIndexChanging这样的事件也不管用,因为如果不启用回调,则不能引发这些事件。最后,切记回调驱动的分页和排序机制需要使用MicrosoftInternetExplorer5.0及更高版本。
GridView事件
GridView控件使用的方法与我们熟知的DataBind方法不同。在ASP.NET2.0中,许多控件以及Page类本身使用的是Pre-load/Post-load事件对。控件生命周期中的关键操作被包装在一对事件中,一个在操作发生前触发,另一个在操作完成后立即触发。GridView类也是这样。图8显示的是新事件列表。使用事件来通告操作极大地增强了编程能力。例如,通过挂钩RowUpdating事件,您能够检查新值的更新内容。您可能想在客户端提供的值存留到下层数据存储之前,通过HTML编码来处理RowUpdating事件。这种简单的技巧有助于避免恶意的脚本注入。
使用pre/post事件对使您能够取消一个基于运行时条件而进行的事件。请看以下代码片段:
voidPageIndexChanging(objectsender,GridViewPageEventArgse)
{
//Isthisthesensitivepage?(>4)
boolisSensitivePage=(e.NewPageIndex>4);
if(isSensitivePage&&(User.Identity.Name!="username"))
e.Cancel=true;
return;
}
取消是一个读/写布尔属性,存在于所有从CancelEventArgs派生的事件参数类中。GridView的许多事件参数类继承了CancelEventArgs,这意味着所有这些事件都能被取消。Cancel属性值在激发“pre”事件时通常设置为假。处理事件时,您能够检查一些条件,通过将Cancel属性设置为真选择取消事件。例如,刚才的代码片段在当前用户未被授权查看索引大于4的页面时,取消了转换到新页面的操作。
显示、排序和分页
一个网格通常用于显示数据库查询的结果。使用GridView控件显示结果比以往更简单。您只需建立一个数据源对象,提供连接字符串和查询文本,为GridView的DataSourceId属性分配数据源ID。运行时,GridView自动绑定到数据源,生成正确的数据列。在默认情况下,查询的所有列均显示在网格中。
像DataGrid控件一样,GridView也支持在Columns集合中自定义列字段。如果只想显示检索到的数据字段的一个子集,或只想自定义其显示外观,则可使用代表显示数据列的对象来填充Columns集合。GridView支持多种列类型,包括新的复选框和图像列类型:
<columns>
<asp:boundfielddatafield="productname"headertext="Product"/>
<asp:checkboxfielddatafield="discontinued"
headertext="Discontinued"/>
<asp:buttonfieldbuttontype="Button"text="Buy"/>
<asp:hyperlinkfieldtext="MoreInfo..."
datanavigateurlfields="productid,discontinued"
datanavigateurlformatstring="more.aspx?id={0}&disc={1}"/>
</columns>
图9显示的活动网格配置为使用代码中列出的字段。GridView列类名与DataGrid接口中的相应类名略有不同。后缀“column”基本被替换成后缀“field”。除了名字的更改,与列类匹配的行为几乎相同。一些新的列类型使您不必经常使用模板。例如,CheckBoxField列通过一个复选框显示特定的数据字段,而改进的HyperLinkField列提供了期待已久的功能—支持多个URL参数。正如刚才的代码片段所示,DataNavigateUrlFields属性接收了一个以逗号分隔的字段名列表,并将其合并到DataNavigateUrlFormatString属性的文本中。
图9带有活动字段的GridView
请注意ButtonField与CommandField之间的差异。两列都向网格的用户界面添加了一个按钮,但是CommandField用于显示命令按钮来执行选择、编辑、插入或删除操作。ButtonField只是代表作为按钮显示的字段。最后,GridView能够通过ImageField列类型嵌入图像。
<asp:imagefielddatafield="photo"headertext="Picture"/>
图10显示活动的ImageField列,它位于Northwind雇员表的照片字段。有趣的是,ImageField通过ASP.NET2.0DynamicImage控件显示来自数据库和URL两者的图像。而且,在编辑模式下,ImageField列弹出一个Browse按钮,用于定位要上载的位于本机的新文件。
图10图像字段列
Template列也受支持,所需的语法与ASP.NET1.x的DataGrid使用的相似:
<asp:templatefieldheadertext="Product">
<itemtemplate>
<b><%#Eval("productname")%></b><br/>
availablein<%#Eval("quantityperunit")%>
</itemtemplate>
</asp:templatefield>
有趣的是,ASP.NET2.0允许的数据绑定表达式的语法更简洁。在ASP.NET1.x中生成模板化的内容需要使用下列表达式:DataBinder.Eval(Container.DataItem,"fieldname")由于使用了一个更小的数据绑定机制,现在,您能够避免使用DataBinder类中静态的Eval方法,而是调用Page类定义的新的Eval保护方法。您将计算的字段名和方法传递给Eval,决定当前的数据项并通过DataBinder.Eval准备一个常规调用。
Eval被声明为TemplateControl类的一个保护方法,Page和UserControl都从这个类派生。真正代表一个.aspx活动页面的类是从Page派生的一个类的实例;因此,它能够调入受保护的方法。ASCX用户控件也是如此。
如果焦点是显示纯数据,则不需要像GridView这样全新的网格控件。当然,现在您只需少量代码或不需要编码就能将数据源控件绑定到GridView,但是单凭这点就有必要替换DataGrid吗?如果答案是否定的,请考虑排序和分页。
在GridView控件中,只需通过开启AllowPaging和AllowSorting属性就能启用自动翻转排序和分页功能。如果在ASP.NET1.x中尝试过这项操作,您就可大概了解这项功能了。
图11活动的可分页、可排序网格
图11显示一个可分页、可排序的网格。图12显示此网格的完整代码。(值得注意的是,仅当需要标记列标头来指示排序方向时才需要使用C#代码。)因此,无需编写代码,排序和分页就能十分正常地运行。通过DataSourceMode属性控制SQLDataSource的数据检索模型。可行的值类型是DataSet(默认值)和DataReader。当DataSourceMode为DataSet时,数据源控件可能会一直选择性地缓存SELECT命令的结果。这使得GridView适应于丰富多样的使用情境,其中控件可提供无代码排序、筛选和分页功能。默认情况下禁用缓存,因此它必须在数据源控件上启用。
在内存中缓存数据能大大提高性能,但是数据会显得有些脆弱。您必须权衡利弊,因为如果系统内存运行效率低,Cache对象会自动丢弃最少使用的数据。此外,在ASP.NET2.0中,SQLDataSource控件可能选择性地建立与数据库的自动依赖关系,以便立即检测到数据变更。这确保了总是显示最新的数据。有关数据源控件功能的更多信息,请参见我在前面提到的2004年6月发表的文章。当SQLDataSource控件检索模型为DataReader时,检索数据使用IDataReader对象,它是一个只进、只读、流水游标。
编辑数据
DataGrid控件最大的缺点之一—相反却是GridView控件最大的优点之一,是处理数据源更新的能力。当绑定数据源支持更新时,GridView能够自动执行数据操作,从而提供真正的出盒解决方案。数据源控件通过一些布尔属性(例如CanUpdate、CanDelete、CanSort等)提供这些功能。
对GridView控件而言,数据编辑意味着就地编辑和记录删除。如前所述,就地编辑指网格支持更改当前显示记录的功能。启用GridView的就地编辑,需要启动AutoGenerateEditButton布尔属性:
<asp:gridviewrunat="server"id="MyGridView"
datasourceid="MySource"
autogenerateeditbutton="true">
&S226;&S226;&S226;
</asp:gridview>
当AutoGenerateEditButton属性设置为真时,GridView显示附加的一列,如图13中最左边一列。单击一行的Edit按钮将此行置于编辑模式下。当一行处于编辑模式下时,非只读行的每个绑定字段将显示适当的输入控件,通常是一个TextBox。当您单击更新时,GridView引发RowUpdating事件并检查数据源的CanUpdate属性。如果CanUpdate返回值为假,则引发一个异常。否则,在数据源对象的UpdateCommand属性后创建和配置一个命令对象。
图13GridView的Edit列
即使您对SQL的操作仅限于定义命令结构—只定义语句而让控件来完成其他操作,也无需使用ADO.NET或担心如何使用命令或连接。想在用户单击Update时保留更改,可编写以下代码:
<asp:sqldatasourcerunat="server"id="MySource"
connectionstring="SERVER=...;DATABASE=northwind;Integrated
Security=SSPI;"
updatecommand="UPDATEemployeesSET
firstname=@firstname,lastname=@lastname
WHEREemployeeid=@employeeid">
</asp:sqldatasource>
<asp:gridviewrunat="server"id="MyGridView"
DataSourceId="MySource"
DataKeyNames="employeeid"AutoGenerateEditButton="true">
&S226;&S226;&S226;
</asp:gridview>
数据源的UpdateCommand属性被设置为GridView使用的SQL命令。您能够使用所需的任意数量的参数。如果您采用一种特殊的命名规则,参数值也能够自动解析。代表更新字段的参数(例如firstname)必须与网格列的DataField属性名相匹配。用于标识工作记录的WHERE子句中使用的参数必须与DataKeyNames属性匹配,后者是显示记录的关键字段。最后,考虑这种情况:如果没有定义UpdateCommand,却提交更改,那么CanUpdate返回值为假,并引发一个异常。RowUpdated事件发出信号通知更新命令结束。通过更新命令更新的行数可在RowUpdated事件参数的AffectedRows属性中检索。
GridView自动收集输入字段的值,填充name/value对词典,这个词典指示了每个行字段的新值。GridView也公开一个RowUpdating事件,允许您修改正在传递到数据源对象的值。此外,在相关数据源上激发Update操作前,GridView将自动调用Page.IsValid。如果Page.IsValid返回值为假,将取消操作。这对使用包括验证程序在内的自定义编辑模板特别有用。
行删除操作方式与此相似。下面的SQL命令是一个数据源对象的DeleteCommand属性的合法内容:
DELETEemployeesWHEREemployeeid=@employeeid请注意,如果由于特定于数据库的约束而无法删除记录,删除操作将失败。例如,如果子记录通过某种关系引用父记录,父记录将无法删除。在这种情况下,引发一个异常。
GridView控件不自动支持向数据源插入数据。没有这项功能完全是由于实现GridView不依赖于底层数据源的功能和特性。实际上,数据源对象提供一个CanInsert属性并支持一个InsertCommand属性。请注意,通过GridView和DetailsView控件的组合能够实现这个功能,一会您就会了解到。
DetailsView控件
许多应用程序需要一次作用于一条记录。在ASP.NET1.x中,没有内置的功能支持这种情况。创建单条记录视图是可能的,但需要您自己编写代码。首先,您需要获取记录,然后,将字段绑定到数据绑定表单,选择性地提供分页按钮来浏览记录。我编写了三个CuttingEdge列的安装程序来解决这个问题—2002年4月、5月和6月。
当生成主/详细视图时,经常需要显示单条记录的内容。通常,用户从网格中选择一条主记录,让应用程序追溯所有可用字段。通过组合GridView和DetailsView,编写少量代码,就能够生成有层次结构的视图。
DetailsView控件能够自动绑定到任何数据源控件,使用其数据操作集。控件能够自动分页、更新、插入和删除底层数据源的数据项,只要数据源支持这些操作。多数情况下,建立这些操作无需编写代码,如下所示:
<asp:detailsviewrunat="server"id="det"
datasourceid="MySource"
autogenerateeditbutton="true"
autogenerateinsertbutton="true"
autogeneratedeletebutton="true"
allowpaging="true"
headertext="Employees">
<pagersettingsmode="NextPreviousFirstLast"
firstpageimageurl="images/first.gif"
lastpageimageurl="images/last.gif"
nextpageimageurl="images/next.gif"
previouspageimageurl="images/prev.gif"/>
</asp:detailsview>
DetailsView控件的用户界面能够通过使用数据字段和类型进行自定义,其方式与GridView相似。DetailsView不支持自定义模板,因为这项特殊的功能完全构造在新的FormView控件中。DetailsView具有一个命令栏,显示Edit、Delete和New按钮的任意组合。当您单击Edit或New时,控件显示Edit或Insert模式,字段内容显示在文本框中。工作模式能通过Mode和DefaultMode属性控制。
使用DetailsView控件能很好地实现无需代码的主/详细视图。除了Edit和Delete按钮,GridView控件支持Select按钮,它也是预定义的。通过设置AutoGenerateSelectButton属性为真,您能为每一行启用此按钮。当用户单击此按钮时,当前行输入选定状态,为GridView的SelectedIndex属性分配从0开始的索引值。此外,GridView控件引发SelectedIndexChanged事件。应用程序可以挂钩到这个事件,并执行自定义代码。
在ASP.NET2.0中,如果您想生成主/详细视图,则无需处理SelectedIndexChanged事件。您可以将一个GridView控件和一个DetailsView控件拖放到页面上,将两者绑定到一个数据源。生成无代码的主/详细视图的技巧是,将详细视图控件绑定到当前选定记录所代表的数据源,如下所示:
<asp:sqldatasourcerunat="server"id="MyDetailSource"
&S226;&S226;&S226;
selectcommand="SELECT*FROMcustomers"
filterexpression="customerid='@customerid'">
<filterparameters>
<asp:ControlParameterName="customerid"
ControlId="masterGrid"
PropertyName="SelectedValue"/>
</filterparameters>
</asp:sqldatasource>
数据源对象的FilterExpression属性为SelectCommand指定的基础查询定义WHERE子句。参数值能够以多种方式指定,包括直接绑定一个控件属性。对象将@customerid参数设置为主网格控件的SelectedValue属性存储的值。图14的代码显示如何配置主网格控件和详细视图控件。图15显示活动页面。请注意,无需程序代码来完成这些功能。
图15活动主网格
FormView控件
FormView是新的数据绑定控件,使用起来像是DetailsView的模板化版本。它每次从相关数据源中选择一条记录显示,选择性地提供分页按钮,用于在记录之间移动。与DetailsView控件不同的是,FormView不使用数据控件字段,而是允许用户通过模板定义每个项目的显示。FormView支持其数据源提供的任何基本操作。
FormView控件是作为通常使用的更新和插入接口而设计的,它不能验证数据源架构,不支持高级编辑功能,比如外键字段下拉。然而,使用模板来提供此功能很容易。FormView和DetailsView有两方面的功能差异。首先,FormView控件具有ItemTemplate、EditItemTemplate和InsertItemTemplate属性,而DetailsView一个也没有。其次,FormView缺少命令行—将可用功能进行分组的工具栏。与GridView和DetailsView控件不同的是,FormView没有其自己默认的显示布局。同时,它的图形化布局完全是通过模板自定义的。因此,每个模板都包括特定记录需要的所有命令按钮。下列代码片断是在页面中嵌入一个FormView的典型写法。
<asp:FormViewID="EmpDetails"runat="server"
DataSourceId="MySource"AllowPaging="true">
<ItemTemplate>
&S226;&S226;&S226;
</ItemTemplate>
<EditItemTemplate>
&S226;&S226;&S226;
</EditItemTemplate>
<InsertItemTemplate>
&S226;&S226;&S226;
</InsertItemTemplate>
</asp:FormView>
图16说明一个使用FormView控件的页面。Edit按钮通过命令名Edit的<asp:Button>元素来添加。这将导致FormView从只读模式转换到编辑模式,使用定义过的EditItemTemplate显示。New命令名将强制控件转换为插入模式,显示InsertItemTemplate的定义内容。最后,如果您将命令名为Delete的按钮添加到项目模板中,用户单击它时,FormView将调用数据源的Delete命令。
图16FormView控件
如何检索数据来更新或插入一条记录?您可以使用一个新的数据绑定关键字Bind,它是专门为双向绑定而设计的:
Bind关键字像Eval一样用于显示数据,而且能在更新或插入一条记录时检索输入值。此外,Bind对GridView和DetailsView使用的TemplateFields非常有用。
Bind将绑定控件属性值存入一个值集合,FormView控件自动检索和使用这个集合来组合插入或编辑命令的参数列表。传递到Bind的参数必须与数据容器的字段名匹配。例如,上一个代码片断中的文本框存放备注字段的值。最后,还要记住的是编辑和插入模板必须包含保存变更的按钮。这是指普通的按钮—用于保存的Update和Insert以及用于放弃操作的Cancel。
FormView事件的工作方式与DetailsView和GridView相同。因此,如果想处理像数据预处理或后处理(例如,填充下拉框)这样更复杂的操作,您应该为ItemCommand、ItemInserting和ModeChanging之类的事件编写适当的事件处理程序。
小结
数据绑定控件是大多数Web应用程序的必要组成部分。数据绑定控件应该简单但功能强大。理想的情况是,它们应该以很少的单击操作以及有限的代码数量提供高级的功能。虽然ASP.NET2.0仍然在使用,但是其新一代的数据绑定控件满足了这个需求。ASP.NET1.x数据绑定的主要缺点是需要为普通数据操作编写过多的代码。这一点已经随着数据源对象和GridView控件的引入而解决了。DetailsView和FormView是对GridView的完美补充,代表了对ASP.NET1.x数据工具箱的重大改进。
DataGrid控件限制普通数据操作的引发事件,因为它是一个数据源不可知的控件,能够绑定到任何可枚举的数据对象。执行数据操作(如更新或删除)需要直接连接到一个特定的数据源。在ASP.NET1.x中,则通过编写特定于应用程序的ADO.NET代码解决这个问题的。
ASP.NET2.0改进了数据绑定体系结构,引入了新的系列组件(数据源对象)作为数据绑定控件与ADO.NET对象之间的桥梁。这些源对象提升了一个略为不同的编程模型,提供了新功能和新成员。您的ASP.NET2.0应用程序应该使用最新的网格控件—GridView,显示数据报告。与之相似的DataGrid控件仍然支持,但DataGrid不能充分利用数据源组件的特定功能。
GridView控件是DataGrid的接替者,并从几个方面扩展了后者的功能。首先,它完全支持数据源组件,能够自动处理诸如分页、排序和编辑等数据操作,前提是绑定的数据源对象支持这些操作。另外,GridView控件有一些比DataGrid优越的功能上的改进。特别是,它支持多个主键字段,公开了一些用户界面的改进功能和一个处理与取消事件的新模型。
GridView附带了一对互补的视图控件:DetailsView和FormView。通过这些控件的组合,您能够轻松地建立主/详细视图,而只需少量代码,有时根本不需要代码。
GridView与DataGrid
ASP.NET2.0中数据绑定控件的类层次结构比ASP.NET1.x中的更一致。在2.0版本中,所有控件无论有什么样的实际实现过程和用户界面特点,均从同一个基类(BaseDataBoundControl类)派生。图1显示新的类关系图。DataGrid和其他1.x版本的控件(如Repeater和DataList)没有包含在该关系图中。这些现有控件的继承树与ASP.NET1.x的相同。特别是,Repeater继承了WebControl,而DataList和DataGrid继承了BaseDataList。如图1所示,GridView是一个复合数据绑定控件,它与其他所有数据绑定控件(包括DropDownList、DetailsView和ListBox)共享一组方法和属性。
图1ASP.NET类关系图
GridView和DataGrid控件的高级功能相似,但基础却不同。GridView尽可能地保留了DataGrid的对象模型,以便轻松地从现有页面进行移植。但是,基于DataGrid的代码与新的基于GridView的代码不可能100%兼容。
DataGrid与GridView控件的另一个主要差异在于自适应用户界面。与1.x版本的DataGrid不同的是,GridView也能在移动设备上显示。换句话说,您能够使用相同的用于桌面页面的网格控件在移动设备上生成报告。2.0版本的DataGrid也能自适应地显示,但是它的UI功能没有GridView丰富。
在ASP.NET2.0中,改进后的DataGrid控件支持诸如主题和个性化等通用的控件功能。此外,新的DataGrid控件可由一个数据源控件填充。但要记住,绑定到数据源对象的DataGrid只能用于读取数据。要实际修改底层数据源,仍然需要一些用户定义的代码。而GridView控件可以利用底层数据源的功能并自动删除或更新记录。注意,GridView控件也支持传统的基于DataSource属性和DataBind方法的绑定机制。尽管完全支持这种绑定机制,但是不鼓励使用这样的编程实践方法。
GridView和数据源控件
那么,数据源控件是什么?我在2004年6月一期的MSDN®Magazine中详细介绍了ASP.NET2.0的这项流行的新功能。简言之,一个数据源控件就是一组Microsoft®.NETFramework类,它有利于数据存储和数据绑定控件之间的双向绑定。现有的控件(如DataGrid)以及新的数据绑定控件(如GridView),尽管绑定能力不同,但都能绑定到一个数据源。
一个数据源控件代表了数据源的主要功能:选择、插入、更新和删除。数据源控件能代表任何数据源:从关系数据源库到XML文件,从流数据到业务对象。如果简要介绍能让您想起.NET的托管提供程序,请参见图2。
图2数据源控件、GridView和数据源
数据源控件可以位于一些.NET数据提供程序的上层,在数据绑定控件和数据源之间形成一个中间层。数据源控件也会公开一个提供基本操作的公共接口。一些数据绑定控件—特别是GridView控件,将这些命令与其他与数据有关的操作一起,绑定到适当的自动编辑。
数据源控件通过其属性和方法,将绑定内容以一组命名的视图形式公开。IDataSource接口提供从数据源检索数据视图的基本功能集,所有数据源控件都实现了这个接口。ASP.NET2.0提供一些内置数据源控件,如图3所列。图3列出的数据源控件属于两类:列表和分层组件。SiteMapDataSource和XmlDataSource组件是分层数据源控件,用于像TreeView和Menu控件这样的分层组件。其他各种组件用于管理列表数据。
图4中的代码说明如何在一个示例页面上将GridView和DataGrid绑定到同一个数据源控件。在ASP.NET2.0中,这是推荐的数据绑定方法。SqlDataSource控件的特点是一个ConnectionString属性加上SelectCommand、UpdateCommand、InsertCommand和DeleteCommand属性的任意组合。所有属性都是字符串形式,并且引用带有可选参数的命令文本:
<asp:SqlDataSourcerunat="server"
ID="MySource"
ConnectionString="SERVER=(local);
DATABASE=northwind;IntegratedSecurity=SSPI;"
SelectCommand="SELECT*FROMemployeesWHEREemployeeid>@MinID">
<SelectParameters>
<asp:ControlParameterName="MinID"
ControlId="EmpID"
PropertyName="Text"/>
</SelectParameters>
</asp:SqlDataSource>
每个数据源控件由唯一的ID表示。ID是连结数据绑定控件和数据源控件之间的纽带。通过DataSourceId属性将GridView绑定到一个数据源控件。例如,每当网格需要获取数据时,就执行与SQLDataSource控件相关联的SelectCommandSQL命令。当网格需要更新或删除一条记录时,就执行相应的UpdateCommand或DeleteCommandSQL命令。如果不存在这样的命令,则引发一个异常。在内部,当用户删除或更新一条记录时,GridView就像1.x版本的DataGrid一样引发事件。但是与DataGrid不同的是,GridView为这些事件定义内部的处理程序。默认的处理程序检索绑定数据源定义的命令来处理和执行这些操作。图4说明,在保持网格显示或更新数据的标记后无需编写代码。在更复杂的情况下,您可能需要编写一些代码。
图5GridView和DataGrid
数据源控件和GridView控件通常用于无代码数据绑定。图5显示图4的代码生成的输出结果。
在ASP.NET2.0中,除了DataSource和DataMember,DataGrid还公开了DataSourceId属性。DataSourceId属性将DataGrid连接到同一页面上定义的一个合法数据源对象。但是,DataGrid不提供与GridView同一级别的自动化操作。当用户单击以更新一条记录时,DataGrid引发UpdateCommand事件,而GridView除了引发Updating和Updated事件外,还检索和执行数据源更新命令,允许用户自定义发送到数据源控件的信息。
GridView对象模型
GridView与DataGrid的整体结果看起来相似。一些通用元素经过了重命名,一些通用功能现在需要不同的语法。总之,如果熟悉DataGrid控件,则可立即自如地运用GridView。图6详尽列出了组成GridView的新元素(请注意,其中一些元素,如DetailLinkStyle,仅用于在移动设备上显示网格)。行元素通过Rows集合中的GridViewRow类生成的实例进行显示。GridViewRow类映射到DataGridItem类,而Rows明确替代了DataGrid的项目集合。行类型由DataControlRowType枚举表示,用来指示位置和角色(例如,页脚、页眉、页导航和数据行)。GridView还引入一个新概念—行元素状态。该行状态由DataControlRowState标记的枚举值表示—通常值是Edit,可选值为Insert和Selected。有趣的是,这两类枚举恰巧由所有数据视图控件(GridView、DetailsView和FormView)共享。
除了引入符合自适应显示的元素之外,GridView仅有一个其他类型的新元素—空数据行。当GridView绑定到一个空数据源时,会选择性地显示一些默认内容,为用户提供反馈。在这种情况下显示的内容依赖于新的空数据行元素的内容。可通过一个属性(EmptyDataText)或一个模板(EmptyDataTemplate)设置该行的内容。
GridView控件的属性主要分为三种类型:行为、可视设置和状态。图7列出GridView的一些属性。请查看包括EnableSortingAndPagingCallbacks、EmptyDataText和UseAccessibleHeader在内的新属性以及被重命名或改编的属性,后者实现了DataGrid已经支持的功能。
编程模型与按钮列略有不同。在ASP.NET1.x的DataGrid中,您不得不通过添加特定的列类型来创建一个Edit按钮—EditCommandColumn。如果要创建一个Delete或Select按钮列,则必须添加通用的按钮列并预定义一个命令名。GridView对象则更一致、更简洁。它基于三个新的布尔属性:AutoGenerateEditButton、AutoGenerateDeleteButton和AutoGenerateSelectButton。当其中任何一个属性设置为真时,网格中分别添加一个Edit、Delete或Select命令按钮列。例如,当AutoGenerateEditButton属性设置为真时,在网格中自动为每个数据行添加带有Edit按钮的一列。也可以手动添加这些按钮,方法是在列集合中添加一个CommandField对象。Columns属性列出了列对象,这些对象很像DataGrid的Columns属性列出的对象。根据客户的反馈,其中也添加了几个帮助器属性。特别是,您现在能够为每个显示行存储多个键值。实际上,DataGrid的DataKeyField字符串属性已经扩展为一个字段名数组。新的属性命名为DataKey,用于存储由字段名组成的一个字符串数组,这个字符串数组唯一标识一个数据行。DataKey是特定行的值的相应数组。它返回DataKey对象的集合。每个DataKey对象包含一个键名值,DataKey的DataKey对象数量与GridView的显示行数相同。
SortDirection和SortExpression跟踪当前的网格排序。这些属性用于在内部实现自动翻转排序,标记网格当前排序次序。每个对象的PagerSettings组包含配置用户界面、行为和页导航位置的所有属性。现在,页导航支持的导航模式不但包括首行和尾行,还包括下一行和上一行。
GridView控件也能够使用一个基于回调的轻量型机制来进行排序和分页。您可以通过设置EnableSortingAndPagingCallbacks布尔属性来开启和关闭此功能。当单击排序或分页链接来启用回调时,GridView请求排序数据或下一页,不回发可视页面。(这里发生了一个往返过程,但是无页面刷新,因此您不知道。)请注意,这个功能有个警告:启用GridView中的选项时,新页面保留当前选定的索引。如果有与之相关联的详细信息页面,那么选定的内容将失去同步。处理类似PageIndexChanging这样的事件也不管用,因为如果不启用回调,则不能引发这些事件。最后,切记回调驱动的分页和排序机制需要使用MicrosoftInternetExplorer5.0及更高版本。
GridView事件
GridView控件使用的方法与我们熟知的DataBind方法不同。在ASP.NET2.0中,许多控件以及Page类本身使用的是Pre-load/Post-load事件对。控件生命周期中的关键操作被包装在一对事件中,一个在操作发生前触发,另一个在操作完成后立即触发。GridView类也是这样。图8显示的是新事件列表。使用事件来通告操作极大地增强了编程能力。例如,通过挂钩RowUpdating事件,您能够检查新值的更新内容。您可能想在客户端提供的值存留到下层数据存储之前,通过HTML编码来处理RowUpdating事件。这种简单的技巧有助于避免恶意的脚本注入。
使用pre/post事件对使您能够取消一个基于运行时条件而进行的事件。请看以下代码片段:
voidPageIndexChanging(objectsender,GridViewPageEventArgse)
{
//Isthisthesensitivepage?(>4)
boolisSensitivePage=(e.NewPageIndex>4);
if(isSensitivePage&&(User.Identity.Name!="username"))
e.Cancel=true;
return;
}
取消是一个读/写布尔属性,存在于所有从CancelEventArgs派生的事件参数类中。GridView的许多事件参数类继承了CancelEventArgs,这意味着所有这些事件都能被取消。Cancel属性值在激发“pre”事件时通常设置为假。处理事件时,您能够检查一些条件,通过将Cancel属性设置为真选择取消事件。例如,刚才的代码片段在当前用户未被授权查看索引大于4的页面时,取消了转换到新页面的操作。
显示、排序和分页
一个网格通常用于显示数据库查询的结果。使用GridView控件显示结果比以往更简单。您只需建立一个数据源对象,提供连接字符串和查询文本,为GridView的DataSourceId属性分配数据源ID。运行时,GridView自动绑定到数据源,生成正确的数据列。在默认情况下,查询的所有列均显示在网格中。
像DataGrid控件一样,GridView也支持在Columns集合中自定义列字段。如果只想显示检索到的数据字段的一个子集,或只想自定义其显示外观,则可使用代表显示数据列的对象来填充Columns集合。GridView支持多种列类型,包括新的复选框和图像列类型:
<columns>
<asp:boundfielddatafield="productname"headertext="Product"/>
<asp:checkboxfielddatafield="discontinued"
headertext="Discontinued"/>
<asp:buttonfieldbuttontype="Button"text="Buy"/>
<asp:hyperlinkfieldtext="MoreInfo..."
datanavigateurlfields="productid,discontinued"
datanavigateurlformatstring="more.aspx?id={0}&disc={1}"/>
</columns>
图9显示的活动网格配置为使用代码中列出的字段。GridView列类名与DataGrid接口中的相应类名略有不同。后缀“column”基本被替换成后缀“field”。除了名字的更改,与列类匹配的行为几乎相同。一些新的列类型使您不必经常使用模板。例如,CheckBoxField列通过一个复选框显示特定的数据字段,而改进的HyperLinkField列提供了期待已久的功能—支持多个URL参数。正如刚才的代码片段所示,DataNavigateUrlFields属性接收了一个以逗号分隔的字段名列表,并将其合并到DataNavigateUrlFormatString属性的文本中。
图9带有活动字段的GridView
请注意ButtonField与CommandField之间的差异。两列都向网格的用户界面添加了一个按钮,但是CommandField用于显示命令按钮来执行选择、编辑、插入或删除操作。ButtonField只是代表作为按钮显示的字段。最后,GridView能够通过ImageField列类型嵌入图像。
<asp:imagefielddatafield="photo"headertext="Picture"/>
图10显示活动的ImageField列,它位于Northwind雇员表的照片字段。有趣的是,ImageField通过ASP.NET2.0DynamicImage控件显示来自数据库和URL两者的图像。而且,在编辑模式下,ImageField列弹出一个Browse按钮,用于定位要上载的位于本机的新文件。
图10图像字段列
Template列也受支持,所需的语法与ASP.NET1.x的DataGrid使用的相似:
<asp:templatefieldheadertext="Product">
<itemtemplate>
<b><%#Eval("productname")%></b><br/>
availablein<%#Eval("quantityperunit")%>
</itemtemplate>
</asp:templatefield>
有趣的是,ASP.NET2.0允许的数据绑定表达式的语法更简洁。在ASP.NET1.x中生成模板化的内容需要使用下列表达式:DataBinder.Eval(Container.DataItem,"fieldname")由于使用了一个更小的数据绑定机制,现在,您能够避免使用DataBinder类中静态的Eval方法,而是调用Page类定义的新的Eval保护方法。您将计算的字段名和方法传递给Eval,决定当前的数据项并通过DataBinder.Eval准备一个常规调用。
Eval被声明为TemplateControl类的一个保护方法,Page和UserControl都从这个类派生。真正代表一个.aspx活动页面的类是从Page派生的一个类的实例;因此,它能够调入受保护的方法。ASCX用户控件也是如此。
如果焦点是显示纯数据,则不需要像GridView这样全新的网格控件。当然,现在您只需少量代码或不需要编码就能将数据源控件绑定到GridView,但是单凭这点就有必要替换DataGrid吗?如果答案是否定的,请考虑排序和分页。
在GridView控件中,只需通过开启AllowPaging和AllowSorting属性就能启用自动翻转排序和分页功能。如果在ASP.NET1.x中尝试过这项操作,您就可大概了解这项功能了。
图11活动的可分页、可排序网格
图11显示一个可分页、可排序的网格。图12显示此网格的完整代码。(值得注意的是,仅当需要标记列标头来指示排序方向时才需要使用C#代码。)因此,无需编写代码,排序和分页就能十分正常地运行。通过DataSourceMode属性控制SQLDataSource的数据检索模型。可行的值类型是DataSet(默认值)和DataReader。当DataSourceMode为DataSet时,数据源控件可能会一直选择性地缓存SELECT命令的结果。这使得GridView适应于丰富多样的使用情境,其中控件可提供无代码排序、筛选和分页功能。默认情况下禁用缓存,因此它必须在数据源控件上启用。
在内存中缓存数据能大大提高性能,但是数据会显得有些脆弱。您必须权衡利弊,因为如果系统内存运行效率低,Cache对象会自动丢弃最少使用的数据。此外,在ASP.NET2.0中,SQLDataSource控件可能选择性地建立与数据库的自动依赖关系,以便立即检测到数据变更。这确保了总是显示最新的数据。有关数据源控件功能的更多信息,请参见我在前面提到的2004年6月发表的文章。当SQLDataSource控件检索模型为DataReader时,检索数据使用IDataReader对象,它是一个只进、只读、流水游标。
编辑数据
DataGrid控件最大的缺点之一—相反却是GridView控件最大的优点之一,是处理数据源更新的能力。当绑定数据源支持更新时,GridView能够自动执行数据操作,从而提供真正的出盒解决方案。数据源控件通过一些布尔属性(例如CanUpdate、CanDelete、CanSort等)提供这些功能。
对GridView控件而言,数据编辑意味着就地编辑和记录删除。如前所述,就地编辑指网格支持更改当前显示记录的功能。启用GridView的就地编辑,需要启动AutoGenerateEditButton布尔属性:
<asp:gridviewrunat="server"id="MyGridView"
datasourceid="MySource"
autogenerateeditbutton="true">
&S226;&S226;&S226;
</asp:gridview>
当AutoGenerateEditButton属性设置为真时,GridView显示附加的一列,如图13中最左边一列。单击一行的Edit按钮将此行置于编辑模式下。当一行处于编辑模式下时,非只读行的每个绑定字段将显示适当的输入控件,通常是一个TextBox。当您单击更新时,GridView引发RowUpdating事件并检查数据源的CanUpdate属性。如果CanUpdate返回值为假,则引发一个异常。否则,在数据源对象的UpdateCommand属性后创建和配置一个命令对象。
图13GridView的Edit列
即使您对SQL的操作仅限于定义命令结构—只定义语句而让控件来完成其他操作,也无需使用ADO.NET或担心如何使用命令或连接。想在用户单击Update时保留更改,可编写以下代码:
<asp:sqldatasourcerunat="server"id="MySource"
connectionstring="SERVER=...;DATABASE=northwind;Integrated
Security=SSPI;"
updatecommand="UPDATEemployeesSET
firstname=@firstname,lastname=@lastname
WHEREemployeeid=@employeeid">
</asp:sqldatasource>
<asp:gridviewrunat="server"id="MyGridView"
DataSourceId="MySource"
DataKeyNames="employeeid"AutoGenerateEditButton="true">
&S226;&S226;&S226;
</asp:gridview>
数据源的UpdateCommand属性被设置为GridView使用的SQL命令。您能够使用所需的任意数量的参数。如果您采用一种特殊的命名规则,参数值也能够自动解析。代表更新字段的参数(例如firstname)必须与网格列的DataField属性名相匹配。用于标识工作记录的WHERE子句中使用的参数必须与DataKeyNames属性匹配,后者是显示记录的关键字段。最后,考虑这种情况:如果没有定义UpdateCommand,却提交更改,那么CanUpdate返回值为假,并引发一个异常。RowUpdated事件发出信号通知更新命令结束。通过更新命令更新的行数可在RowUpdated事件参数的AffectedRows属性中检索。
GridView自动收集输入字段的值,填充name/value对词典,这个词典指示了每个行字段的新值。GridView也公开一个RowUpdating事件,允许您修改正在传递到数据源对象的值。此外,在相关数据源上激发Update操作前,GridView将自动调用Page.IsValid。如果Page.IsValid返回值为假,将取消操作。这对使用包括验证程序在内的自定义编辑模板特别有用。
行删除操作方式与此相似。下面的SQL命令是一个数据源对象的DeleteCommand属性的合法内容:
DELETEemployeesWHEREemployeeid=@employeeid请注意,如果由于特定于数据库的约束而无法删除记录,删除操作将失败。例如,如果子记录通过某种关系引用父记录,父记录将无法删除。在这种情况下,引发一个异常。
GridView控件不自动支持向数据源插入数据。没有这项功能完全是由于实现GridView不依赖于底层数据源的功能和特性。实际上,数据源对象提供一个CanInsert属性并支持一个InsertCommand属性。请注意,通过GridView和DetailsView控件的组合能够实现这个功能,一会您就会了解到。
DetailsView控件
许多应用程序需要一次作用于一条记录。在ASP.NET1.x中,没有内置的功能支持这种情况。创建单条记录视图是可能的,但需要您自己编写代码。首先,您需要获取记录,然后,将字段绑定到数据绑定表单,选择性地提供分页按钮来浏览记录。我编写了三个CuttingEdge列的安装程序来解决这个问题—2002年4月、5月和6月。
当生成主/详细视图时,经常需要显示单条记录的内容。通常,用户从网格中选择一条主记录,让应用程序追溯所有可用字段。通过组合GridView和DetailsView,编写少量代码,就能够生成有层次结构的视图。
DetailsView控件能够自动绑定到任何数据源控件,使用其数据操作集。控件能够自动分页、更新、插入和删除底层数据源的数据项,只要数据源支持这些操作。多数情况下,建立这些操作无需编写代码,如下所示:
<asp:detailsviewrunat="server"id="det"
datasourceid="MySource"
autogenerateeditbutton="true"
autogenerateinsertbutton="true"
autogeneratedeletebutton="true"
allowpaging="true"
headertext="Employees">
<pagersettingsmode="NextPreviousFirstLast"
firstpageimageurl="images/first.gif"
lastpageimageurl="images/last.gif"
nextpageimageurl="images/next.gif"
previouspageimageurl="images/prev.gif"/>
</asp:detailsview>
DetailsView控件的用户界面能够通过使用数据字段和类型进行自定义,其方式与GridView相似。DetailsView不支持自定义模板,因为这项特殊的功能完全构造在新的FormView控件中。DetailsView具有一个命令栏,显示Edit、Delete和New按钮的任意组合。当您单击Edit或New时,控件显示Edit或Insert模式,字段内容显示在文本框中。工作模式能通过Mode和DefaultMode属性控制。
使用DetailsView控件能很好地实现无需代码的主/详细视图。除了Edit和Delete按钮,GridView控件支持Select按钮,它也是预定义的。通过设置AutoGenerateSelectButton属性为真,您能为每一行启用此按钮。当用户单击此按钮时,当前行输入选定状态,为GridView的SelectedIndex属性分配从0开始的索引值。此外,GridView控件引发SelectedIndexChanged事件。应用程序可以挂钩到这个事件,并执行自定义代码。
在ASP.NET2.0中,如果您想生成主/详细视图,则无需处理SelectedIndexChanged事件。您可以将一个GridView控件和一个DetailsView控件拖放到页面上,将两者绑定到一个数据源。生成无代码的主/详细视图的技巧是,将详细视图控件绑定到当前选定记录所代表的数据源,如下所示:
<asp:sqldatasourcerunat="server"id="MyDetailSource"
&S226;&S226;&S226;
selectcommand="SELECT*FROMcustomers"
filterexpression="customerid='@customerid'">
<filterparameters>
<asp:ControlParameterName="customerid"
ControlId="masterGrid"
PropertyName="SelectedValue"/>
</filterparameters>
</asp:sqldatasource>
数据源对象的FilterExpression属性为SelectCommand指定的基础查询定义WHERE子句。参数值能够以多种方式指定,包括直接绑定一个控件属性。对象将@customerid参数设置为主网格控件的SelectedValue属性存储的值。图14的代码显示如何配置主网格控件和详细视图控件。图15显示活动页面。请注意,无需程序代码来完成这些功能。
图15活动主网格
FormView控件
FormView是新的数据绑定控件,使用起来像是DetailsView的模板化版本。它每次从相关数据源中选择一条记录显示,选择性地提供分页按钮,用于在记录之间移动。与DetailsView控件不同的是,FormView不使用数据控件字段,而是允许用户通过模板定义每个项目的显示。FormView支持其数据源提供的任何基本操作。
FormView控件是作为通常使用的更新和插入接口而设计的,它不能验证数据源架构,不支持高级编辑功能,比如外键字段下拉。然而,使用模板来提供此功能很容易。FormView和DetailsView有两方面的功能差异。首先,FormView控件具有ItemTemplate、EditItemTemplate和InsertItemTemplate属性,而DetailsView一个也没有。其次,FormView缺少命令行—将可用功能进行分组的工具栏。与GridView和DetailsView控件不同的是,FormView没有其自己默认的显示布局。同时,它的图形化布局完全是通过模板自定义的。因此,每个模板都包括特定记录需要的所有命令按钮。下列代码片断是在页面中嵌入一个FormView的典型写法。
<asp:FormViewID="EmpDetails"runat="server"
DataSourceId="MySource"AllowPaging="true">
<ItemTemplate>
&S226;&S226;&S226;
</ItemTemplate>
<EditItemTemplate>
&S226;&S226;&S226;
</EditItemTemplate>
<InsertItemTemplate>
&S226;&S226;&S226;
</InsertItemTemplate>
</asp:FormView>
图16说明一个使用FormView控件的页面。Edit按钮通过命令名Edit的<asp:Button>元素来添加。这将导致FormView从只读模式转换到编辑模式,使用定义过的EditItemTemplate显示。New命令名将强制控件转换为插入模式,显示InsertItemTemplate的定义内容。最后,如果您将命令名为Delete的按钮添加到项目模板中,用户单击它时,FormView将调用数据源的Delete命令。
图16FormView控件
如何检索数据来更新或插入一条记录?您可以使用一个新的数据绑定关键字Bind,它是专门为双向绑定而设计的:
Bind关键字像Eval一样用于显示数据,而且能在更新或插入一条记录时检索输入值。此外,Bind对GridView和DetailsView使用的TemplateFields非常有用。
Bind将绑定控件属性值存入一个值集合,FormView控件自动检索和使用这个集合来组合插入或编辑命令的参数列表。传递到Bind的参数必须与数据容器的字段名匹配。例如,上一个代码片断中的文本框存放备注字段的值。最后,还要记住的是编辑和插入模板必须包含保存变更的按钮。这是指普通的按钮—用于保存的Update和Insert以及用于放弃操作的Cancel。
FormView事件的工作方式与DetailsView和GridView相同。因此,如果想处理像数据预处理或后处理(例如,填充下拉框)这样更复杂的操作,您应该为ItemCommand、ItemInserting和ModeChanging之类的事件编写适当的事件处理程序。
小结
数据绑定控件是大多数Web应用程序的必要组成部分。数据绑定控件应该简单但功能强大。理想的情况是,它们应该以很少的单击操作以及有限的代码数量提供高级的功能。虽然ASP.NET2.0仍然在使用,但是其新一代的数据绑定控件满足了这个需求。ASP.NET1.x数据绑定的主要缺点是需要为普通数据操作编写过多的代码。这一点已经随着数据源对象和GridView控件的引入而解决了。DetailsView和FormView是对GridView的完美补充,代表了对ASP.NET1.x数据工具箱的重大改进。