[导读]在ASP.NET 2.0中引入了数据源组件,相对于1.x版本,在开发效率和运行效率上都得到了极大的提升,而本文将详细介绍新的数据控件,包括编程接口、用法和实现。
绝大多数Web应用程序都会使用某种类型的数据,而ASP.NET最常见的用途之一就是将该数据绑定到用户界面元素。ASP.NET 1.x提供了针对性能进行优化的极为灵活且通用的数据绑定,并且可让开发人员完全控制页面生命周期。可以将实现IEnumerable接口的任何数据集合(如DataView)或支持IListSource接口成员的任何对象(如DataSet和DataTable)方便而快速地与服务器控件相关联,并且可以使用它们来填充页面。
只要您实现了简单的只读函数,这种灵活的方法就可以很好地工作。比较复杂的只读函数(如分页、筛选和排序)要求更多一些的关注。例如,要对数据绑定控件进行排序,您就需要了解有关该页面的体系结构和行为的大量信息(包括视图状态管理和回发事件)。要启用数据编辑,需要比较深入地了解ASP.NET基础结构。
页面开发人员必须不断地重新实现同一模式:定位输入控件和检索新数据,准备对SQL命令或存储过程的参数化调用,执行语句,最后刷新用户界面。在许多情况下,这些代码是一成不变的,但正是由于这个原因,编写这些代码可能比编写任何其他代码都更加恼人。
通过ASP.NET 1.x数据绑定模型,可以绑定到任何可枚举的数据源,从而使您可以专心地负责任何其他操作,如插入、更新和排序。ASP.NET 2.0中的数据绑定模型为数据绑定控件定义了第二种输入格式。除了所有实现IEnumerable接口的对象以外,ASP.NET 2.0数据绑定控件还接受数据源控件——这是该版本中刚刚引入的全新组件系列。
在本文中,我将引导您浏览ASP.NET 2.0中的主要数据源控件,并讨论它们的编程接口、用法和实现。
数据源组件一览
从根本上说,数据源控件就是包装了特定数据源(如SQL Server、Microsoft Access或XML文档)的一些基本函数的服务器控件。这些基本函数包括查询、插入、更新和删除。数据源控件不具有UI并且不呈现任何标记(请注意,有许多控件都不具有UI,然而却呈现标记)。可以用声明方式或编程方式将其绑定到数据控件。通过数据源组件的服务,数据绑定控件除了可以从特定数据源获取数据以外,还可以插入新记录或者更新和删除现有记录。控件的接口总是相同的,无论您使用哪种特定的数据源(即,无论是SQL Server数据库、XML文档、Microsoft Excel工作表还是站点图描述)。
数据源对象是自描述性的,并且能够让绑定控件了解基础数据源中受支持的功能。这样,控件就可以方便地基于它所连接的数据源的功能来调整它自己的用户界面。例如,网格组件可以仅在基础数据源可编辑时才显示Edit列。
数据源控件是ASP.NET 2.0数据绑定模型中迄今为止最为重要的更改。在ASP.NET 2.0中,数据源控件是推荐的用于执行数据绑定的工具。需要注意的是,数据源控件与公开IEnumerable接口的对象一起工作。在数据驱动的应用程序中,数据源控件绝不会取代DataView和数组。而且,可以保证向后兼容性。使用数据源控件,您现在就具有了一种将数据绑定到任何新增和现有数据绑定控件的备用方法。数据源控件不会带来任何性能问题,性能基本与版本1.x中的相同甚至略高一筹。
ASP.NET 2.0向所有数据绑定控件中添加了一个新属性,以便每个控件都可以成功地绑定到数据源控件。应该将这一新属性—DataSourceId—设置为在同一页面中定义的数据源控件的名称。下面的代码片段显示了如何用针对SQL Server数据库执行的查询结果来填充DataGrid控件:
<asp:SqlDataSource
runat="server"
ID="MySource"
ConnectionString="...;"
DataSourceMode="DataSet"
SelectCommand="..."
/>
<asp:DataGrid
runat="server"
ID="data"
DataSourceId="MySource"
/>
SqlDataSource是公开SQL关系数据库内容的数据源控件。(需要注意的是,SqlDataSource不是特定于SQL Server的,但有关该问题的详细信息留待稍后讨论。)ConnectionString属性标识源数据库,而SelectCommand属性被设置为查询字符串。正如前面所提到的,您可以使用传统的DataSource属性或新的DataSourceId属性将数据传递给数据绑定控件。请注意,这两个属性是互斥的。如果您同时设置这两个属性,将会引发异常。
通过使用数据源控件而不是传统的可枚举对象,您会得到什么呢?首先,您可以在.aspx页中使用一个简单的标记来声明数据源。这样可以实现数据源对象的自动实例化,并减少为完整设置该页面而需要编写的代码数量。您不再需要显式地操纵如SqlConnection和SqlCommand这样的对象。数据源减少了对服务器数据组件(连接、适配器、类型化数据集)的依赖性,这是因为这些组件极度依赖于Visual Studio .NET的代码生成功能。在Visual Studio 2005中,被隔离在don't-change-this-code(不要更改以下代码)区域中的自动生成的代码数量明显减少。这并不意味着要丢弃设计时功能,而是刚好相反。数据源控件促成了控件和数据组件之间的直接和隐式绑定。通过这一体系结构,可以开发智能设计器,以便动态发现架构和数据,从而更为准确地表示数据绑定控件的运行时外观。图1 显示了设计时SqlDataSource控件以及用于连接到存储区的菜单。
图1 设计时的 SqlDataSource 控件
至少对于常见方案(如选择、排序、分页、删除和基本更新)而言,您可以通过简单地连接和配置一对控件来设置数据绑定。图2显示了Visual Studio 2005工具箱的Data选项卡。它包含了一些数据源控件和数据绑定控件—您在许多情况下需要涉及的唯一工具。如果是这种情况,您的页面几乎不需要任何数据绑定代码。尽管如此,由于页面需要更为复杂的数据绑定功能,因此需要您添加少量代码。
图2 Data选项卡
通过数据源控件,可以在多种数据源中实现一致的绑定模型。(图2中的控件只是将在ASP.NET 2.0发布时可用的控件的子集。)作为页面开发人员,您将使用相同的属性,而无论数据源是关系表(无论是哪种数据库系统)、XML文档、自定义类还是Excel文件。
您通过使用数据源控件而得到的另一项优势与数据缓存有关。许多关于ASP.NET编码策略和优化的书籍和文章都将缓存数据列为构建高性能、可伸缩Web应用程序的最佳做法。毫无疑问,数据缓存意味着关键的性能增强,即使它不是对所有页面和应用程序都有效的魔杖。例如,当您管理大量特定于会话的不稳定数据时,或者当您的要求规定必须始终显示新数据时,广泛使用缓存可能不是最佳的方法。请放心,在大多数情况下,缓存数据都是一种改进应用程序的方法。
数据源控件也集成了缓存功能,并且打开和关闭默认缓存功能与设置Boolean属性一样容易。缓存对于数据绑定控件而言是透明的,并且数据源控件会管理它的某些方面,如生成缓存密钥和过期策略(时间和密钥依赖时间戳)。其他设置由页面开发人员来决定,包括数据在缓存中的生存期。请注意,数据源控件可为每个独特的连接字符串、选择查询、参数和缓存设置组合维护单独的缓存。
还应该注意的一个方面是,某些数据源控件(尤其是SqlDataSource)支持数据缓存过期,即能够检测数据库更改并使当前缓存的数据过期。稍后,您将会看到,该功能要求基础SQL存储区提供特定的支持。
使用数据源控件而不是传统的可枚举对象显然有许多优点,下面让我们考察一下它的某些缺点。正如曾经提到的那样,在ASP.NET 2.0中,每个数据绑定控件都支持双重API以便进行数据绑定。这些API彼此几乎完全隔离,基本上没有共同点。乍看起来,似乎数据源控件支持无代码绑定,并且只需通过指向和单击操作就可以完成可视化编程。勿庸置疑,您无需编写任何代码就可以创建数据驱动的页面。但是,这并不意味着数据绑定控件不允许您挂钩内部事件。新的体系结构具有更高的自动化程度,但它保留了ASP.NET 1.x中使用的显式数据绑定模型的所有方面。
总而言之,这两种模型之间的主要区别在于:当执行数据访问时,数据源控件将充当代理。如果活动的数据源对象对于您的应用程序至关重要,请考虑为每个受支持的数据操作激发一对操作前和操作后事件(如Deleting/Deleted事件)。这会给予您与ASP.NET 1.x中完全相同的数据流控制权,但这次是通过简单得多且更为紧凑的语法实现的。
最后,请记住数据源控件只是一组类。因此,您可以对它们进行完全的控制,并以编程方式实例化和操纵它们。这样做的时候,您将数据源控件用作在原始ADO.NET类之上工作的更为抽象的API。在某种程度上,数据源控件代表着Data Application块的发展,后者是为.NET Framework 1.x引入的API,目的是实现常见的ADO.NET最佳做法,并且减少您需要编写的代码。
基于SQL的数据源控件
ASP.NET 2.0根据数据源控件所表示的数据的性质将其区分为表格式组件或分层式组件。图3 列出了在2004年3月发布的ASP.NET 2.0社区技术预览版中支持的表格式组件和分层式控件。让我们从您可能最常使用的数据源控件(SqlDataSource)开始,来详细考察一下数据源控件。
表格式组件 | 说明 |
---|---|
AccessDataSource | 表示到Access数据库的连接。从SqlDataSource控件继承,但省略了ConnectionString和ProviderName属性,这有利于较简单的、指向MDB文件的DataFile属性。控件使用Jet 4.0 OLE DB提供程序以与数据库连接。如果MDB文件需要,可以指定用户名和密码。ShareMode属性使您能指定MDB文件是否为只读或只写。 |
DataSetDataSource | 与DataSet对象的XML表示形式一起使用。XML数据可以被指定为字符串或文件名。不能将该控件绑定到DataSet对象(以编程方式也不可以)。类功能方法用于检索相应的DataSet对象和设置架构信息。尽管基于XML,但控件仅支持表格式界面并且只能绑定到列表控件。主要用于显示只读的XML数据,控件还支持基础XML数据的编辑。 |
ObjectDataSource | 允许绑定到返回数据的自定义的.NET业务对象。类通过TypeName属性的名称指定。控件允许开发人员使用三层体系结构实现应用程序的结构化,并且仍然使用ASP.NET 2.0声明数据绑定模型。类被希望遵循指定的设计模式并包括,如无参数的构造函数和具有已知行为的方法。 |
SqlDataSource | 表示到返回SQL数据(包括,通过OLE DB和ODBC可访问的数据资源)的ADO.NET数据提供程序的连接。数据提供程序的名称和连接字符串通过属性指定。不要使用该类与Access数据库连接。 |
分层式组件 | Description |
SiteMapDataSource | 从ASP.NET 2.0站点地图资源中获取数据。实际资源取决于应用程序所使用的站点提供程序。在默认情况下,数据源是以.sitemap为扩展名的XML文件。该控件将站点地图的信息发送至分层数据绑定控件(例如,新的TreeView控件)中。 |
XmlDataSource | 从文件、URL或包含XML内容的字符串中加载XML数据数据。如果XML数据还未包含架构信息,则可以额外地将XML架构指定为一个单独的文件或字符串。该控件可以用于填充分层的控件(例如,TreeView或Menu)。 |
图3 表格式组件和分层式组件
SqlDataSource是一个表示指向关系数据存储区(如SQL Server、Oracle、DB2或任何可以通过OLE DB和ODBC桥访问的数据源)的连接的数据源控件。要使控件能够正常工作,必须存在.NET托管数据提供程序,并且数据源能够返回SQL结果集。正如图3 中提到的那样,可以使用单独的数据源控件连接到Access数据库。
您可以使用以下两个主要属性来设置到数据存储区的连接:ConnectionString和ProviderName。前者表示到实际数据源的连接字符串;后者指定用于操作的ADO.NET托管提供程序类的完全限定名。ProviderName属性默认为“System.Data.SqlClient”,这意味着默认数据存储区是SQL Server。要以Oracle为目标数据库,您可以使用“System.Data.OracleClient”字符串。如果您更改ProviderName属性的值,则会引发DataSourceChanged事件。请注意,该事件会导致任何绑定到SQLDataSource对象的控件重新绑定。
DataSourceMode属性可控制Select命令检索数据的方式。该属性决定是否使用数据适配器或命令对象,并且接受以下SqlDataSourceMode枚举所定义的值:DataSet和DataReader。在前一种情况下,查询的结果将缓存在DataSet对象中,并存储在Web服务器上。当以DataSet模式工作时,数据源控件支持启用了缓存、排序和筛选的高级方案。当DataSourceMode属性被设置为DataReader时,将使用数据读取器以只读、只进方式一次一行地读取行。DataSourceMode属性的值对其他操作(如插入、更新或删除)没有任何影响。
属性 | 说明 |
---|---|
DeleteCommand,DeleteParameters | 获取或设置SQL语句(以及相关参数),用于删除基础数据存储中的行 |
InsertCommand,InsertParameters | 获取或设置SQL语句(以及相关参数),用于在基础数据存储中插入新行 |
SelectCommand,SelectParameters | 获取或设置SQL语句(以及相关参数),用于从基础数据存储中检索数据 |
SelectCountCommand | 获取或设置SQL语句(以及相关参数),用于从基础数据存储中检索行计数 |
UpdateCommand, UpdateParameters | 获取或设置SQL语句(以及相关参数),用于更新基础数据存储中的数据 |
图4 数据操作
数据源支持多个数据操作,如插入、删除、更新和选择。图4 列出了这些操作所涉及的属性。每个xxxCommand属性都是一个包含要使用的命令文本(或存储过程名称)的字符串。该命令(SelectCountCommand除外)还可以包含关联的参数集合中列出的参数。参数集合是一个名为ParameterCollection的集合类,它存储了基类为Parameter的对象。Parameter类表示数据源控件所执行的参数化查询、筛选表达式或命令中的参数。
托管提供程序及其基础关系引擎决定要使用的SQL的确切语法以及嵌入式参数的语法。例如,如果数据源控件指向SQL Server,则必须为命令参数名加上前缀@符号。如果目标数据源是OLE DB提供程序或ODBC驱动程序,则用一个问号(?)标识参数并且按位置定位参数。图5 中列出的代码显示了一个公开Customers数据库内容的SQL数据源。
<asp:SqlDataSource
runat="server"
ID="MySource"
ConnectionString="SERVER=...;DATABASE=...;Integrated Security=SSPI;"
SelectCommand="SELECT * FROM customers"
UpdateCommand="UPDATE customers SET country=@country"
DeleteCommand="DELETE FROM customers WHERE customerid=@custid"
>
<UpdateParameters>
<asp:QueryStringParameter
Name="country"
QueryStringField="Country"
DefaultValue=""
/>
</UpdateParameters>
<DeleteParameters>
<asp:QueryStringParameter
Name="custid"
QueryStringField="ID"
DefaultValue=""
/>
</DeleteParameters>
</asp:SqlDataSource>
图5 到自定义数据库的连接
参数如何绑定到值取决于该参数的类型。ASP.NET 2.0支持大量参数类型(参见图6)。图5 中使用的QueryStringParameter表示来自URL查询字符串的输入数据。DefaultValue属性表示在未指定该参数时要使用的默认值。输入参数的其他来源有Cookie、会话数据、窗体输入字段以及其他服务器控件。
参数 | 说明 |
---|---|
ControlParameter | 从任意服务器控件的公共属性从获取参数值。 |
CookieParameter | 设置基于指定的 HTTP cookie 内容的参数值。 |
FormParameter | 从指定的 HTTP 请求表的输入字段获取参数值。 |
ProfileParameter | 从配置文件对象内指定的属性名中获取参数值。(该对象由应用程序的个性化架构创建) |
QueryStringParameter | 从请求查询字符串中指定的变量获取参数值。 |
SessionParameter | 获取基于指定的会话槽内容的参数值。 |
图6 参数类型
请注意,这个小小的功能还可以防止重大的安全过失。它可使您避免编写错误且可能不安全的代码,如下所示:
MySource.SelectCommand += TextBox1.Text
这一特定的代码行会在应用程序中打开严重的安全漏洞,因为如果没有正确地验证TextBox控件的内容,它就会允许SQL注入式攻击发生。SelectParameters集合的内容(以及任何其他数据源参数集合的内容)会被自动冲刷到实际用于执行数据操作的SqlCommand对象的Parameters集合中。
正如曾经提到的那样,SelectCommand和其他命令属性是字符串而非对象。有没有将SQL命令与存储过程进行区分的方法呢?如果您要使用存储过程,只须将所选择的命令属性设置为该存储过程的名称(当然,要假设目标数据库支持存储过程)。数据源控件将在内部分析该字符串,以便查找SELECT关键字。任何不以SELECT开头的文本都被假设为存储过程的名称。
SqlDataSource控件所特有的另一对属性是FilterExpression和FilterParameters。筛选表达式是在使用指定的Select命令检索到的数据之上创建筛选器的字符串。用于FilterExpression属性的语法与用于DataView类的RowFilter属性的语法相同,并且类似于SQL WHERE子句的语法。FilterExpression属性还可以包含参数。筛选器参数以@字符为前缀,并且包含在单引号中。FilterParameters表示为筛选表达式中找到的占位符进行计算的参数集合。
<%@ page language="C#" theme="SmokeAndGlass" %>
<%@ import namespace="System.Data" %>
<%@ import namespace="System.Data.SqlClient" %>
<html>
<head id="Head1" runat="server">
<title>Edit Customers</title>
</head>
<body>
<form id="Form1" runat="server">
<asp:sqldatasource
runat="server"
id="Customers"
connectionstring="SERVER=(local);DATABASE=northwind;Integrated Security=SSPI;"
selectcommand="SELECT customerid, companyname FROM customers"
/>
<asp:sqldatasource
runat="server"
id="CustomerDetails"
connectionstring="SERVER=localhost;DATABASE=northwind;Integrated Security=SSPI;"
selectcommand="SELECT * FROM customers"
filterexpression="customerid='@customerid'"
>
<filterparameters>
<asp:ControlParameter
Name="customerid"
ControlId="MyGrid"
PropertyName="SelectedValue"
/>
</filterparameters>
</asp:sqldatasource>
<table style="border:black 1px solid;">
<tr>
<td valign="top">
<asp:gridview
runat="server"
id="MyGrid"
datakeynames="customerid"
datasourceid="Customers"
allowpaging="true"
autogenerateselectbutton="true"
>
<pagersettings mode="NextPreviousFirstLast"/>
</asp:gridview>
</td>
<td valign="top">
<asp:detailsview
runat="server"
id="MyDetail"
defaultmode="Edit"
autogeneraterows="false"
autogenerateeditbutton="true"
datasourceid="CustomerDetails"
>
<Fields>
<asp:boundfield
datafield="companyname"
headertext="Company"
/>
<asp:boundfield
datafield="contactname"
headertext="Contact"
/>
<asp:boundfield
datafield="address"
headertext="Address"
/>
<asp:boundfield
datafield="city"
headertext="City"
/>
<asp:boundfield
datafield="country"
headertext="Country"
/>
</Fields>
</asp:detailsview>
</td>
</tr></table>
</form>
</body>
</html>
图7 使用数据绑定的GridView和DetailsView
为了完全理解数据源控件的潜能以及ASP.NET 2.0的新数据绑定模型,下面我们分析一下图7中的代码。它所呈现的页面显示在图8中。该页面由结合在一起的一个GridView控件和一个DetailsView控件组成,以形成主/从架构。GridView中选择的记录在DetailsView中以编辑模式显示,并且允许您输入更改。正如您看到的那样,无需任何Visual Basic或C#代码即可实现这一点。使这一切成为可能的原因在于,所有经过正确参数化和范化的粘接代码都埋藏在数据源控件中。该页面包含两个SqlDataSource控件。第一个控件检索随后由GridView控件显示的客户列表。第二个数据源控件在DetailsView控件绑定到的Customers表上检索经过筛选的视图:
<asp:sqldatasource
runat="server"
...
selectcommand="SELECT * FROM customers"
filterexpression="customerid='@customerid'"
>
<filterparameters>
<asp:ControlParameter
Name="customerid"
ControlId="MyGrid"
PropertyName="SelectedValue"
/>
</filterparameters>
</asp:sqldatasource>
将查询只限制到一个客户的参数来自MyGrid控件(页面上的GridView控件)的SelectedValue属性。当用户在网格上选择一个新客户时,SelectedValue属性将发生更改,并且DetailsView将自动绑定到一个新记录。要使这一切能够正常工作,您必须将GridView的DataKeyNames属性设置为在详细信息查询(在此例中为customerid)中使用的字段的名称。
图8 GridView和DetailsView控件
在图8中,DetailsView控件处于编辑模式,并且仅显示几个表列:
<asp:detailsview
...
defaultmode="Edit"
autogeneraterows="false"
autogenerateeditbutton="true"
>
<fields>
...
</fields>
</asp:detailsview>
通过DefaultMode属性,您可以决定视图最初是以只读模式、编辑模式还是插入模式显示。AutoGenerateEditButton属性将Edit按钮添加到详细信息视图的底部。如果用户单击Update按钮,则该控件会执行与数据源控件相关联的更新命令(如果有)。同样,您可以通过绑定的数据源组件上的命令属性启用DetailsView界面上的删除和插入按钮,并且让控件自动解析它们。当命令在数据源控件上执行时,实际发生的情况取决于基础数据源(无论是SQL Server表、XML文档还是托管业务对象)的性质。
在图7 所示的代码中,绑定到DetailsView控件的数据源组件缺少UpdateCommand属性。因此,如果用户单击Update按钮,将引发异常。图7 中代码的另一个小缺点是:当详细信息视图显示时,不会将焦点自动给予任何输入字段。在ASP.NET 1.x中,您必须手动插入脚本代码以完成该工作。在ASP.NET 2.0中,Page类具有一个全新的SetFocus方法,使用它将输入焦点设置到特定控件更为方便。此外,ASP.NET 2.0中的每个控件都可以导出一个能够将浏览器焦点分配给它自身的Focus方法。避免异常和设置焦点是两项需要编写代码的额外任务。让我们首先解决异常。
正如事实所表明的那样,需要提醒您的是,在没有事先定义有效更新命令的情况下启用更新绝对不是一个好的编程习惯。但是,遍历您在其末尾找到异常的堆栈跟踪会很有趣。数据源控件将数据操作包装到一对在该操作之前和之后激发的事件中。您可能认为处理数据源的Updating事件可以完美地解决该问题。在事件处理程序中,您可以检查UpdateCommand字符串,并可以在该字符串为空时取消事件。尽管该方法在理论上是正确的,但它只是纸上谈兵。实际上,我在前面提到的异常已经在Updating事件激发之前引发,但实际发生了什么事情呢?
当您单击Update按钮时,DetailsView控件会激发它自己的预备事件,即ItemUpdating事件。如果该操作未被取消,则该控件随后会确定它所绑定到的数据源对象,并且调用该数据源控件上的Update方法。该方法将检查UpdateCommand字符串,如果该字符串为空,则会引发异常。仅当UpdateCommand非空时,Update方法才会继续执行并将Updating事件激发到页面。总之,要挂钩的事件是DetailsView的ItemUpdating事件,如下所示:
void ItemUpdating(object sender, DetailsViewUpdateEventArgs e)
{
if(CustomerDetails.UpdateCommand == string.Empty)
e.Cancel = true;
}
要完全设置更新操作,您需要将UpdateCommand属性添加到数据源控件:
<asp:sqldatasource
runat="server"
...
updatecommand="UPDATE customers SET country=@country WHERE customerid=@customerid"
>
</asp:sqldatasource>
有趣的是,您并不是一定要定义更新参数。新的数据绑定控件(如GridView和DetailsView)足够智能,能够从文本框和其他输入控件中提取值,以及创建特定的参数列表。默认情况下,参数名与绑定字段的名称相匹配。例如,@country参数被绑定到用于显示country列内容的文本框。customerid主键字段又如何呢?只须将DataKeyNames属性添加到DetailsView控件就可以了:
<asp:detailsview
runat="server"
id="MyDetail"
...
datakeynames="customerid"
>
一个现实的更新方案是您希望检查操作的结果并验证该操作成功完成。在此例中,您可以处理SqlDataSource控件的Updated事件,如下所示:
void ItemUpdated(object sender, SqlDataSourceStatusEventArgs e)
{
if(e.RowsAffected == 0)
{
Response.Write("No rows affected");
}
}
事件的事件参数类提供了一个名为RowsAffected的成员,它指示已经有多少个行受到所执行命令的影响。
要在DetailsView进入编辑模式时将焦点给予特定文本框,请使用下面的代码:
void ItemCreated(object sender, EventArgs e)
{
TextBox txt;
txt = MyDetail.Rows[0].Cells[1].Controls[0] as TextBox;
if(txt != null)
SetFocus(txt.UniqueID);
}
您为DetailsView控件编写一个ItemCreated事件处理程序,然后检索包含您要考虑的文本框的行。Rows集合返回表示数据字段的行集合。每个行都由两个单元格组成。第一个单元格显示标头文本,因此我将讨论刚刚显示的代码片段中的第二个单元格。该单元格中的第一个控件是文本框,除非使用了自定义模板。但是,如果使用了模板,事情将变得更为简单,因为您可以知道要查找的控件的ID。在您获得文本框的ID之后,就可以调用该页面的新SetFocus方法。
数据源控件和缓存
数据源控件可以检索将提供给应用程序中其他组件的数据。当多个页面需要访问该信息时,最新的缓存可以提供快得多的响应。ASP.NET Cache对象是存储内存中数据的首选位置。您可以指示SqlDataSource控件在特定时间内缓存查询的结果,但前提是数据源模式为DataSet。
默认情况下,在SqlDataSource控件中禁用缓存,但您可以通过将EnableCaching属性设置为真来启用缓存。您还应该赋予CacheDuration属性一个非零值。CacheDuration属性指示数据源内容在内存中保留的时间长度(秒)。下面的代码显示了一个SqlDataSource控件,它缓存的数据每五分钟(300秒)就会过期:
<asp:SqlDataSource
runat="server"
ConnectionString="..."
SelectCommand="SELECT * FROM products"
EnableCaching="true"
CacheDuration="300"
/>
默认情况下使用绝对过期策略,但您可以通过设置CacheExpirationPolicy将控件配置为使用可调整策略。针对每个缓存参数、连接字符串和SelectParameters与SelectCommand值的组合,都创建了唯一的缓存密钥。该缓存密钥随后被散列,以保护用于生成它的源数据。
SqlDataSource控件还支持一种基于其内容与SQL Server数据库中的某个表之间的逻辑依赖项集合的缓存过期形式。用于实现此功能的类是SqlCacheDependency。该功能有两个不同的实现—一个用于SQL Server 2005,一个用于SQL Server 2000和SQL Server 7.0。这两个实现彼此完全独立。目前,您可以通过SqlDataSource控件访问的其他数据关系存储区不支持该功能。
在SQL Server 7.0和SQL Server 2000中,依赖项被指定为“数据库:表”格式的字符串属性。该字符串的数据库部分必须引用在web.config文件的新区段下面列出的数据库,而表部分必须是该数据库中的表的名称。您可以通过用分号分隔来指定多个表依赖项,如下面的示例所示:
<asp:SqlDataSource
runat="server"
ConnectionString="..."
SelectCommand="sp_getdata"
SqlCacheDependency="pubs:Authors;pubs:TitleAuthor"
EnableCaching="true"
CacheDuration="300"
/>
对Authors或TitleAuthor表进行任何更改以后,都将自动使数据源控件通过sp_getdata存储过程检索到的缓存内容无效。数据库中的更改可以使缓存中的某些值无效的可能性在Web场方案中尤其有用—此时,一台计算机上的更新可以自动强行刷新所有其他计算机的数据。
绑定到业务对象
数据绑定控件本能地在Web应用程序的表示层和后端之间强制进行直接绑定。如果您将数据检索逻辑(可能还有业务逻辑)封装到一组类中,会怎样呢?可以将这些对象连接到数据绑定控件吗?还是应该只将业务对象和数据绑定视为互相排斥的选择?ObjectDataSource控件使您可以使用三层体系结构来设计应用程序,并且仍然可以受益于声明性数据绑定模型。图9显示了ObjectDataSource如何使业务组件能够将它们的内容与数据绑定控件相关联。
图9
ObjectDataSource控件通过TypeName属性获取该类的名称。它还支持像SelectMethod、InsertMethod、UpdateMethod和DeleteMethod这样的声明性属性,这些属性可让您指定要用于给定操作的方法的名称。每个方法都具有它自己的参数集合。以下是一个如何使用ObjectDataSource控件的示例:
<asp:objectdatasource
runat="server"
id="MyObjectDS"
updatemethod="Update"
typename="Samples.Employee"
>
<updateparameters>
<asp:controlparameter
name="empID"
controlid="ddEmpList"
propertyname="SelectedValue"
/>
<asp:controlparameter
name="address"
controlid="txtAddress"
propertyname="Text"
/>
...
</updateparameters>
</asp:objectdatasource>
在该代码片段中,数据源控件映射到一个名为Samples.Employee的类。您需要负责将正确的程序集链接到应用程序。最简单(但未必在所有情况下都理想)的方法是将该类的源代码放在应用程序的Code子目录中。在该文件夹中部署的任何源文件都被动态编译为程序集,并且被加载到Web空间中。该类通过反射进行实例化。请注意,该数据源组件使用的类必须满足一些要求;您不能使用.NET Framework中的任意类。要成功绑定,类必须满足下列条件:具有默认的无参数构造函数;无状态;具有能够方便地映射到选择、更新、插入和删除语义的方法。在上面显示的代码中,系统期望Update方法具有下面的签名:
public void Update(string empID, string address, ...)
参数标记的名称必须与参数在该方法签名中的形式名称相匹配。此外,对象必须一次一个项目地执行更新;不支持使用批处理操作更新其状态的对象。
ObjectDataSource控件是ASP.NET 2.0控件和组件库中最为引人注目的新项目之一。它促进了强对象模型在分布式应用程序中间层中的应用,并且使得在业务逻辑和表示之间设置直接链接比以前更加容易。在将来的文章中,我将讨论ObjectDataSource控件。
绑定到XML数据
将数据源控件绑定到XML数据的方法有两种:使用任意XML文档和使用DataSet对象的XML表示形式。在前一种情况下,您可以使用分层的数据源控件(XmlDataSource);而在后一种情况下,您可以使用表格式数据源组件(DataSetDataSource)。可以通过URL(DataFile属性)或字符串(Data属性)来指定XML数据。可以使用SchemaFile或Schema属性以相同的方式来提供架构信息。
XmlDataSource控件可以绑定到表格式和分层式数据绑定控件。XML数据的表格式视图只是处于层次结构给定级别的一系列节点,而分层式视图则显示了完整的层次结构,但需要一个多级控件,如TreeView、Menu或其他任何继承了HierarchicalDataBoundControl类的控件。
相反,DataSetDataSource类只能绑定到GridView和其他列表控件。您不能用DataSet类的活动实例来填充DataSetDataSource。但是,在您成功初始化该控件之后,就可以使用GetDataSet方法检索内部的DataSet对象。
内幕初探
数据源控件继承了基类Control,并且具有两种形式:表格式和分层式。DataSourceControl抽象类充当所有数据源控件的基类,并且定义了数据绑定控件和基础数据之间的接口。尽管数据源控件不具有可视化表现形式,但仍然将其实现为控件,以便使“声明性持久性”(在请求处理过程中自动实例化)成为.aspx源代码的原生部分,并且获得对页面视图状态的访问权限。
数据源控件通过一组属性和方法来公开它的基础数据源的内容。这些成员中的一部分是特定于该控件的,而其他成员则为所有源控件所共有,并且被定义为IDataSource接口的一部分。所有数据源控件都实现了IDataSource接口,并且使用该接口的属性和方法来将绑定的内容公开为一组命名的视图。
IDataSource接口很简单,它只包含一个事件(DataSourceChanged)和几个方法(GetView和GetViewNames)。当绑定的数据源更改时(例如,当您更改连接字符串时),将引发DataSourceChanged事件。GetView获得要检索的数据源视图的名称,并且将其作为DataSourceView对象返回。GetViewNames返回表示与该控件关联的视图对象列表的名称集合。因此,数据源控件的内部体系结构好像一个命名的视图集合。
命名的视图用DataSourceView类的实例表示,后者类似于ADO.NET DataView类。DataSourceView表示已经在其中定义了排序、筛选和其他数据操作特殊设置的数据的自定义视图。在其核心,数据源控件可以简单地管理从基础数据源加载的数据的视图。
DataSourceView类是与数据源控件相关联的所有视图的基类。数据源控件中的视图数量取决于连接字符串、特征以及基础数据源的实际内容。图10 列出了DataSourceView类的成员。每个数据源控件都从DataSourceView派生它自己的视图类。
成员 | 说明 |
---|---|
CanDelete | Boolean 属性。指示是否允许对基础数据源进行删除操作。通过调用 Delete 方法进行删除。 |
CanInsert | Boolean 属性。指示是否允许对基础数据源进行插入操作。通过调用 Insert 方法进行插入。 |
CanPage | Boolean 属性。指示基础数据源是否支持分页。 |
CanRetrieveTotalRowCount | Boolean 属性。指示基础数据源是否能够检索数据行(而不是数据)的总数。 |
CanSort | Boolean 属性。指示是否能够存储视图中的数据。 |
CanUpdate | Boolean 属性。指示是否允许对基础数据源进行更新操作。通过调用 Update 方法进行更新。 |
Name | 字符串属性。返回当前视图名。 |
SortExpression | 字符串属性。获取并设置用于创建基础数据排序视图的排序表达式。 |
Delete | 方法,执行与视图相关数据的删除操作。 |
Insert | 方法,执行与视图相关数据的插入操作。 |
Select | 方法,返回一个枚举对象(以基础数据源存储中包含的数据进行填充)。 |
Update | 方法,执行与视图相关数据的更新操作。 |
图10 DataSourceView 类成员
简化的数据绑定语法
无可否认,数据绑定方面的重大更改就是引入了数据源控件。那么,对于以前支持的数据绑定表达式而言,会发生什么情况呢?该功能在ASP.NET 2.0中保持不变,但所使用的语法已经进行了简化,不像原来在ASP.NET版本1.x中那样繁琐了。
在ASP.NET 1.x中,您通常使用静态方法DataBinder.Eval将数据存储区字段晚期绑定到对象属性,如下面的示例所示:
<%# DataBinder.Eval(Container.DataItem, fieldName, formatString) %>
Container.DataItem表达式引用在其上计算该表达式的对象。该表达式通常是一个字符串,该字符串具有要在数据项对象上访问的字段的名称。通常,该表达式可以包含索引和属性名称。这些常用代码可能频繁地在页面的同一表单中重复使用。只有表达式和格式字符串各不相同。ASP.NET 2.0支持经过简化的语法,如下面的代码片段所示:
<%# Eval(fieldName, formatString) %>
当页面被编译以供使用时,会将Eval调用作为一个独立调用插入该页面的源代码中。下面的代码可以使您了解实际发生的事情:
object o = Eval(fieldName);
string result = Convert.ToString(o);
该调用的结果被转换为字符串并分配给一个数据绑定文本控件(它是DataBoundLiteralControl类的实例)。最后,将数据绑定文本插入该页面的控件树中。在ASP.NET 2.0中,Page类新增了一个名为Eval的受保护方法,该方法可确定当前的数据项对象,并调入原来的DataBinder.Eval静态方法。
除了DataBinder类,ASP.NET 2.0还提供了一个对象,该对象能够绑定到针对实现IXPathNavigable接口的对象而执行的XPath表达式的结果。该类为XPathBinder,并且与DataBinder起到相同的作用,不同之处在于它在XML数据上工作:
<%# XPath("Orders/Order/Customer/LastName") %>
与Eval一样,该表达式中的XPath关键字是Page类上的一个新的受保护方法。它可以调入静态方法XPathBinder.Eval。在内部,XPathBinder.Eval从数据源中获取一个导航器对象,并且计算指定的表达式。
XPathBinder类还具有一个Select方法。该方法可以执行XPath查询,并检索节点集(XML节点的可枚举集合)。该集合可以作为晚期绑定值分配给像Repeater控件这样的数据绑定控件。对于该方案,也存在等效的简化语法:
<asp:Repeater
runat="server"
DataSource='<%# XPathSelect("orders/order/summary") %>'
>
...
</asp:Repeater>
XpathSelect是您在数据绑定表达式中使用的关键字,用于指示在容器对象上执行的XPath查询的结果。如果该容器对象未实现IXPathNavigable,则会引发异常。
小结
数据源控件的出现不会使ADO.NET对象受到冷落,这些对象仍然是.NET Framework必不可少的组成部分,但它们的黄金时段已经过去了,并且被推到大多数常见数据绑定操作的后端基础结构中。在ASP.NET 2.0中,您需要使用原始ADO.NET对象的场合大大减少了。
数据源控件使得无代码绑定成为可能,但在实际情况下却不太适用。您总是需要编写一些代码以微调页面,但数据源控件显著减少了代码数量。数据源控件还集成了缓存功能,并使您可以用声明方式启用缓存。所有操作都在幕后发生,因此您几乎不用付出任何努力就可以提供融合了最佳做法的代码。
数据源控件使您可以在多种数据源中实现一致的模型。您可以使用相同的数据绑定模型,而无论数据源是SQL表、XML文档、业务对象、站点映射还是Excel工作表。新的体系结构具有更强的自动化,但也可以与基于可枚举对象的旧模型一起工作。