Data

More Load, Less Code with the Data Enhancements of ASP.NET 2.0

Dino Esposito

This article is based on the March 2004 Community Technology Preview of ASP.NET 2.0. All information contained herein is subject to change.

This article discusses:

  • New data source components in ASP.NET 2.0
  • Binding to business objects and XML data
  • GridView and DetailsView controls
This article uses the following technologies:
ASP.NET, Data, C#

Code download available at:ASPNET20Data.exe(134 KB)

Contents

Data Source Components at a Glance
SQL-based Data Source Controls
Data Source Controls and Caching
Binding to Business Objects
Binding to XML Data
A Quick Look Under the Hood
Simplified Data-binding Syntax
Conclusion

The vast majority of Web applications consume data of some sort, and one of the most common uses of ASP.NET is to bind that data to user interface elements. ASP.NET 1.x provides extremely flexible, generic data binding optimized for performance and can give developers full control of the page lifecycle. Any collection of data that implements the IEnumerable interface (such as the DataView), or any objects that support the members of the IListSource interface (such as DataSet and DataTable), can be easily and quickly associated with server controls and used to populate a page.

Such a flexible approach works well as long as you implement simple read-only functions. More complicated read-only functions, such as paging, filtering, and sorting, require a little more attention. For example, to sort a data-bound control you need to know a lot about the page's architecture and behavior, including viewstate management and postback events. A deeper knowledge of the ASP.NET infrastructure is needed to enable data editing.

Page developers have to continually reimplement the same pattern: locate input controls and retrieve fresh data, prepare parameterized calls to SQL commands or stored procedures, execute the statement, and, finally, refresh the user interface. In many cases, this is boilerplate code, but for this very reason it may be more annoying to write than any other code.

The ASP.NET 1.x data-binding model allows binding to any data source that is enumerable, leaving you responsible for any other operations, such as insert, update, and sort. The data-binding model you find in ASP.NET 2.0 defines a second type of input format for data-bound controls. In addition to all objects that implement the IEnumerable interface, ASP.NET 2.0 data-bound controls accept data source controls, which are a brand new family of components just introduced in this version.

In this article, I'll take you on a tour of the main data source controls available in ASP.NET 2.0 and discuss their programming interface, usage, and implementation.

Data Source Components at a Glance

A data source control is basically a server control that wraps some basic functions of a particular data source (such as SQL Server™, Microsoft® Access, or XML documents). Basic functions include select, insert, update, and delete. A data source control has no UI and doesn't render any markup (note that there are a number of controls that have no UI, yet do render markup). It can be declaratively or programmatically bound to a data control. Through the services of a data source component, a data-bound control can insert new records or update and delete existing ones along with fetching data from a specific data source. The interface to the control is always the same regardless of the particular data source you're using, be it a SQL Server database, an XML document, a Microsoft Excel worksheet, or a sitemap description.

The data source object is self describing and lets the bound control know about the supported functionalities of the underlying data source. In this way, the control can easily adapt its own user interface based on the capabilities of the data source to which it's connected. For example, a grid component can show an Edit column only if the underlying data source is editable.

Data source controls are by far the most important change in the ASP.NET 2.0 data-binding model. In ASP.NET 2.0, data source controls are the recommended tool for performing data binding. It is important to note that data source controls work side by side with objects that expose the IEnumerable interface. In no way do data source controls replace DataView and arrays in data-driven applications. Also, backward compatibility is guaranteed. With data source controls, you now have an alternative way of binding data to any new and existing data-bound controls. Data source controls don't present any performance problems; performance is about the same as it was in version 1.x or even slightly better.

ASP.NET 2.0 adds a new property to all data-bound controls so that each control can be successfully bound to a data source control. The new property, DataSourceId, should be set to the name of a data source control defined in the same page. The following code snippet shows how to populate a DataGrid control with the results of a query against a SQL Server database:

<asp:SqlDataSource runat="server" ID="MySource" ConnectionString="...;" DataSourceMode="DataSet" SelectCommand="..." /> <asp:DataGrid runat="server" ID="data" DataSourceId="MySource" />

SqlDataSource is the data source control that exposes the contents of a SQL relational database. (It is important to note that SqlDataSource is not specific to SQL Server, but more on that later.) The ConnectionString property identifies the source database while the SelectCommand property is set to the query string. As mentioned earlier, you can pass data to a data-bound control using either the classic DataSource property or the new DataSourceId property. Note that the two properties are mutually exclusive. If you set both, an exception will be thrown.

What do you gain by using data source controls over classic enumerable objects? First of all, you can declare your data source in the .aspx page using a simple tag. This enables automatic instantiation of the data source object and reduces the amount of code you need to write to completely set up the page. No longer do you need to explicitly manipulate objects like SqlConnection and SqlCommand. Data sources reduce your dependence on server data components (connection, adapters, typed DataSets) as these components are heavily reliant on the code-generation capabilities of Visual Studio® .NET. In Visual Studio 2005, the amount of auto-generated code insulated in don't-change-this-code regions diminishes significantly. This doesn't mean that design-time features are being dropped, but quite the opposite. Data source controls promote a direct and implicit binding between controls and data components. This architecture allows the development of smart designers that permit dynamic discovery of schema and data and, thus, a more accurate representation of the runtime appearance of the data-bound control. Figure 1 shows the design-time SqlDataSource control and the menu used to connect to a store.

figure 1 SqlDataSource Control at Design Time

At least for common scenarios such as selecting, sorting, paging, deleting, and basic updating, you set up data binding by simply connecting and configuring a pair of controls. Figure 2 shows the Data tab of the Visual Studio 2005 toolbox. It contains a few data source controls and data-bound controls—the only tools you need to deal with in many situations. When this is the case, your page is virtually free of data-binding code. As the page requires more complex data-binding features, though, you would need to add little bits of code.

figure 2 Data Tab

Figure 2** Data Tab **

Data source controls enable a consistent binding model across a variety of data sources. (The controls in Figure 2 are only a subset of those that will be available by the time ASP.NET 2.0 ships.) As a page developer, you will work with the same properties regardless of whether the data source is a relational table (regardless of the database system), an XML document, a custom class, or an Excel file.

Another advantage you get by using data source controls relates to data caching. Many books and articles about ASP.NET coding strategies and optimization put caching data among the best practices for building high-performance, scalable Web applications. No doubt that the caching of data represents a critical performance enhancement, even though it is not a magic wand that works by design for all pages and applications. For example, when you manage lots of session-specific, volatile data, or your requirements dictate that fresh data must always be displayed, extensive caching can be a suboptimal approach. Rest assured that in most cases, caching data is a way to improve applications.

Data source controls integrate caching capabilities too, and turning default caching features on and off is as easy as setting a Boolean property. Caching is transparent to data-bound controls and the data source control manages some aspects of it, such as the generation of the cache key and the expiration policy (time and key dependency timestamp). Other settings are left to the decision of the page developer, including the lifetime of data in the cache. Note that data source controls maintain a separate cache for each distinct combination of connection string, select query, parameters, and cache settings.

An important side note is that some data source controls, specifically the SqlDataSource, support data cache invalidation—the ability to detect database changes and invalidate the currently cached data. As you'll see later, this feature requires specific support from the underlying SQL store.

There are clearly many advantages to using data source controls over classic enumerable objects, but let's look at some of the drawbacks. As mentioned, in ASP.NET 2.0 each data-bound control supports a dual API for data binding. These APIs are quite insulated, sharing few common points. It may appear at first that data source controls tout codeless binding and visual programming done by merely pointing and clicking. It's absolutely correct that you can create a data-driven page without writing a single line of code. However, this doesn't mean that data-bound controls don't let you hook up internal events. The new architecture is more automatic, but preserves all aspects of the explicit data-binding model used in ASP.NET 1.x.

All in all, the key difference between the two models lies in the fact that data source controls act as a proxy when it comes to performing data access. If a live data source object is essential to your application, consider that a couple of pre and post events are fired for each supported data operation (such as Deleting/Deleted events). This gives you exactly the same control over the data flow that you have in ASP.NET 1.x, but through a much simpler and more compact syntax.

Finally, bear in mind that data source controls are just a set of classes. As such, they can be instantiated and manipulated programmatically under your total control. In doing so, you use data source controls as a more abstract API working atop the raw ADO.NET classes. In a way, data source controls represent the evolution of the Data Application block, an API introduced for the .NET Framework 1.x to implement common ADO.NET best practices and reduce the code you need to write.

SQL-based Data Source Controls

ASP.NET 2.0 differentiates data source controls as tabular or hierarchical components based on the nature of the data they represent. Figure 3 lists the tabular components and hierarchical controls supported in the Community Technology Preview build of ASP.NET 2.0 released in March 2004. Let's review the data source controls in more detail starting with the one you'll probably use most often: SqlDataSource.

figure 3 Tabular and Hierarchical Components

Tabular Components Description
AccessDataSource Represents a connection to an Access database. Inherits from the SqlDataSource control but omits the ConnectionString and ProviderName properties in favor of a simpler DataFile property that will point to the MDB file. The control uses the Jet 4.0 OLE DB provider to connect to the database. If required for the MDB file, you can specify a user name and password. A ShareMode property allows you to specify whether the MDB file is read-only or read-write.
DataSetDataSource Works with the XML representation of a DataSet object. The XML data can be specified as a string or through a file name. You can't bind this control to a DataSet object, not even programmatically. The class features methods to retrieve the corresponding DataSet object and set schema information. Although XML based, the control supports only the tabular interface and can only be bound to list controls. Mainly used to display XML data in read-only scenarios, the control also supports editing of the underlying XML data.
ObjectDataSource Allows binding to a custom .NET business object that returns data. The class is specified by name through the TypeName property. The control allows developers to structure applications using a three-tier architecture and still take advantage of the ASP.NET 2.0 declarative data-binding model. The class is expected to follow a specific design pattern and include, for example, a parameterless constructor and methods with a well-known behavior.
SqlDataSource Represents a connection to an ADO.NET data provider that returns SQL data, including data sources accessible through OLE DB and ODBC. The name of the provider and the connection string are specified through properties. Do not use this class to connect to an Access database.
Hierarchical Controls Description
SiteMapDataSource Gets data from an ASP.NET 2.0 sitemap source. The actual source depends on the site provider that the application uses. In the default case, the data source is an XML file with a .sitemap extension. The control pumps site map information into hierarchical data-bound controls such as the new TreeView control.
XmlDataSource Loads XML input data from a file, URL, or as a string containing the XML content. If the XML data does not also contain schema information, the XML schema may be additionally specified as a separate file or string. The control can be used to populate hierarchical controls such as TreeView or Menu.

SqlDataSource is a data source control that represents a connection to a relational data store such as SQL Server, Oracle, DB2, or any data source accessible through OLE DB and ODBC bridges. For the control to work, it is essential that a .NET managed data provider exists and that the data source can return SQL resultsets. As mentioned in Figure 3, a separate data source control is available to connect to Access databases.

You set up the connection to the data store using two main properties: ConnectionString and ProviderName. The former represents the connection string to the actual data source; the latter specifies the fully qualified name of the ADO.NET managed provider class to use for the operation. The ProviderName property defaults to "System.Data.SqlClient", which means that the default data store is SQL Server. To target an Oracle database, you use the "System.Data.OracleClient" string. If you change the value of the ProviderName property, the DataSourceChanged event will be raised. Note that this event causes any controls bound to the SQLDataSource object to rebind.

The DataSourceMode property controls how the Select command retrieves data. The property decides whether a data adapter or a command object is used and accepts values defined by the SqlDataSourceMode enumeration: DataSet and DataReader. In the former case, the results of the query are cached in a DataSet object and stored on the Web server. When working in DataSet mode, the data source control supports advanced scenarios in which caching, sorting, and filtering are enabled. When the DataSourceMode property is set to DataReader, a data reader is used to read the rows one at a time in a read-only, forward-only way. The value of the DataSourceMode property has no effect on other operations such as insert, update, or delete.

The data source supports a few data operations such as insert, delete, update, and select. Figure 4 lists the properties involved with the operations. Each xxxCommand property is a string that contains the command text (or the stored procedure name) to be used. The command—with the exception of SelectCountCommand—can optionally contain parameters listed in the associated parameter collection. A parameter collection is a collection class named ParameterCollection which stores objects whose base class is Parameter. The Parameter class represents a parameter in a parameterized query, filter expression, or command executed by a data source control.

figure 4 Data Operations

Properties Description
DeleteCommand, DeleteParameters Gets or sets the SQL statement (and related parameters) used to delete rows in the underlying data store
InsertCommand, InsertParameters Gets or sets the SQL statement (and related parameters) used to insert new rows in the underlying data store
SelectCommand, SelectParameters Gets or sets the SQL statement (and related parameters) used to retrieve data from the underlying data store
SelectCountCommand Gets or sets the SQL statement used to retrieve a row count from the underlying data store
UpdateCommand, UpdateParameters Gets or sets the SQL statement (and related parameters) used to update rows in the underlying data store

The managed provider and its underlying relational engine determine the exact syntax of the SQL to use and the syntax of the embedded parameters. For example, if the data source control points to SQL Server, command parameter names must be prefixed with the @ symbol. If the target data source is an OLE DB provider or an ODBC driver, parameters are identified with a question mark symbol (?) and located by position. The code that is listed in Figure 5 shows a SQL data source that exposes the contents of the Customers database.

figure 5 Connecting to Customers Database

<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>

How a parameter is bound to a value depends on the type of the parameter. ASP.NET 2.0 supports quite a few parameter types, which (see Figure 6). The QueryStringParameter used in Figure 5 represents an input data coming from the URL query string. The DefaultValue attribute represents the default value to use if the parameter isn't specified. Other sources of input parameters are cookies, session data, form input fields, and other server controls.

figure 6 Parameter Types

Parameter Description
ControlParameter Gets the parameter value from any public property of a server control
CookieParameter Sets the parameter value based on the content of the specified HTTP cookie
FormParameter Gets the parameter value from the specified input field in the HTTP request form
ProfileParameter Gets the parameter value from the specified property name in the profile object created from the application's personalization scheme
QueryStringParameter Gets the parameter value from the specified variable in the request query string
SessionParameter Gets the parameter value based on the content of the specified Session slot

Note that this little feature also guards against a significant security lapse. It shields you from writing bad, potentially insecure code, such as the following:

MySource.SelectCommand += TextBox1.Text

This particular line of code opens up a serious security hole in the application because it allows SQL injections to occur if the content of the TextBox control is not properly validated. The content of the SelectParameters collection (as well as the content of any other data source parameter collection) is automatically flushed into the Parameters collection of the SqlCommand object that is actually used to perform the data operation.

As mentioned, SelectCommand and other command properties are strings, not objects. Is there a way to distinguish SQL commands from stored procedures? If you want to use stored procedures, just set the command property of choice to the name of the stored procedure (assuming of course that the target database supports stored procedures). The data source control will internally parse the string, looking for the SELECT keyword. Any text that doesn't begin with SELECT is assumed to be the name of a stored procedure.

Another pair of properties that characterize the SqlDataSource control are FilterExpression and FilterParameters. A filter expression is a string that creates a filter on top of the data retrieved using the specified Select command. The syntax used for the FilterExpression property is the same as the syntax used for the RowFilter property of the DataView class and is similar to that used with the SQL WHERE clause. The FilterExpression property can also contain parameters. Filter parameters are prefixed with the @ character and enclosed in single quotation marks. FilterParameters represents the collection of parameters that are evaluated for the placeholders found in the filter expression.

To fully understand the potential of data source controls and the new data-binding model of ASP.NET 2.0, let's analyze the code in Figure 7. The page it renders is shown in Figure 8. The page comprises a GridView and a DetailsView control joined together to form a master/detail schema. The record selected in the GridView shows up in edit mode in the DetailsView and allows you to enter changes. As you can see, no Visual Basic® or C# code is needed to achieve this. It's possible because all the glue code, properly parameterized and generalized, is buried in the data source controls. The page includes two SqlDataSource controls. The first one retrieves the customers list that is then displayed by the GridView control. The second data source control retrieves a filtered view on the Customers table that the DetailsView control binds to:

<asp:sqldatasource runat="server" ••• selectcommand="SELECT * FROM customers" filterexpression="customerid='@customerid'"> <filterparameters> <asp:ControlParameter Name="customerid" ControlId="MyGrid" PropertyName="SelectedValue" /> </filterparameters> </asp:sqldatasource>

figure 7 GridView and DetailsView Using Data Binding

<%@ 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>

The parameter that restricts the query to just one customer comes from the SelectedValue property of the MyGrid control—the GridView control on the page. When the user selects a new customer on the grid, the SelectedValue property changes and the DetailsView gets automatically bound to a new record. For this to work, it is essential that you set the DataKeyNames property of the GridView to the name of the field used in the details query—customerid in this case.

figure 8 GridView and DetailsView Controls

Figure 8** GridView and DetailsView Controls **

In Figure 8, the DetailsView control is in edit mode and displays only a few of the table columns:

<asp:detailsview ... defaultmode="Edit" autogeneraterows="false" autogenerateeditbutton="true"> <fields> ••• </fields> </asp:detailsview>

Through the DefaultMode property, you can decide if the view initially displays in read-only, edit, or insert mode. The AutoGenerateEditButton property adds the Edit buttons to the bottom of the details view. If the user clicks on the Update button, the control executes the update command associated with the data source control, if any. Likewise, you can enable delete and insert buttons on the DetailsView interface and have the control automatically resolve them through the command properties on the bound data source component. What really happens when a command executes on a data source control depends on the nature of the underlying data source, be it a SQL Server table, an XML document, or a managed business object.

In the code shown in Figure 7, the data source component bound to the DetailsView control lacks an UpdateCommand property. As a result, if users click the Update button, an exception is thrown. Another little wrinkle with the code in Figure 7 is that when the details view displays, no input field is automatically given focus. In ASP.NET 1.x, you had to inject script code manually to have this done. In ASP.NET 2.0, the Page class features a brand new SetFocus method that is more convenient to use to set the input focus to a particular control. In addition, each control in ASP.NET 2.0 exports a Focus method that assigns the browser focus to itself. Avoiding exceptions and setting the focus are two extra tasks that require code to be written. Let's tackle exceptions first.

As obvious as it may seem, it's worth a reminder that enabling updates without first defining a valid update command is definitely not a good programming practice. However, it is interesting to walk the stack trace at the end of which you find the exception. Data source controls wrap data operations in a pair of events that fire before and after the operation. You may think that handling the data source's Updating event would brilliantly solve the issue. In the event handler, you could check the UpdateCommand string and cancel the event if the string is empty. While theoretically correct, this approach would only work on paper. In reality, the exception I referred to earlier is thrown before the Updating event fires, but what is really happening?

When you click the Update button, the DetailsView control fires its own preliminary event, the ItemUpdating event. If the operation is not canceled, the control then determines the data source object it is bound to, and invokes the Update method on the data source control. The method checks the UpdateCommand string and, if empty, throws the exception. Only if UpdateCommand is not empty does the Update method proceed and fire the Updating event to the page. In summary, the event to hook up is the DetailsView's ItemUpdating event, like so:

void ItemUpdating(object sender, DetailsViewUpdateEventArgs e) { if (CustomerDetails.UpdateCommand == string.Empty) e.Cancel = true; }

To fully set up the update operation, you add the UpdateCommand property to the data source control:

<asp:sqldatasource runat="server" ••• updatecommand="UPDATE customers SET country=@country WHERE customerid=@customerid"> </asp:sqldatasource>

It is interesting to note that you don't strictly need to define update parameters. New data-bound controls (like GridView and DetailsView) are smart enough to extract values from textboxes and other input controls and to create an ad hoc parameter list. By default, the parameter name matches the name of the bound field. For example, the @country parameter is bound to the textbox used to show the content of the country column. What about the customerid primary key field? Add the DataKeyNames attribute to the DetailsView control and you're finished:

<asp:detailsview runat="server" id="MyDetail" ••• datakeynames="customerid">

A realistic scenario for update is when you want to check the result of the operation and verify that it completed successfully. In this case, you can handle the Updated event of the SqlDataSource control, as shown here:

void ItemUpdated(object sender, SqlDataSourceStatusEventArgs e) { if (e.RowsAffected == 0) { Response.Write("No rows affected"); } }

The event argument class for the event provides a member named RowsAffected that indicates how many rows have been affected by the executed command.

To give focus to a particular textbox when the DetailsView gets into edit mode, use the following code:

void ItemCreated(object sender, EventArgs e) { TextBox txt; txt = MyDetail.Rows[0].Cells[1].Controls[0] as TextBox; if (txt != null) SetFocus(txt.UniqueID); }

You write an ItemCreated event handler for the DetailsView control and then retrieve the row that contains the textbox you want to consider. The Rows collection returns the collection of rows that represents the data fields. Each row is made of two cells. The first displays the header text so I'll take the second one in the code snippet just shown. The first control in this cell is the textbox unless a custom template is used. But if templates are used, things get even easier because you know the ID of the control you're looking for. Once you've got the ID of the textbox, you call the page's new SetFocus method.

Data Source Controls and Caching

A data source control retrieves data that will be made available to other components within the application. When multiple pages need to access this information, an up-to-date cache provides for a significantly faster response. The ASP.NET Cache object is the preferred place to store in-memory data. You can instruct the SqlDataSource control to cache the results of a query for a certain amount of time, but only if the data source mode is DataSet.

Caching is disabled by default in the SqlDataSource control, but you can enable it by setting the EnableCaching property to true. You should also give the CacheDuration property a nonzero value. The CacheDuration property indicates (in seconds) how long the contents of the data source are maintained in memory. The following code shows a SqlDataSource control that caches data to expire every five minutes (300 seconds):

<asp:SqlDataSource runat="server" ConnectionString="..." SelectCommand="SELECT * FROM products" EnableCaching="true" CacheDuration="300" />

An absolute expiration policy is used by default, but you can configure the control to use a sliding policy by setting CacheExpirationPolicy. A unique cache key is created for each combination of caching parameters, connection string, and the values of SelectParameters and SelectCommand. The cache key is then hashed to protect the source data used to generate it.

The SqlDataSource control also supports a form of cache expiration based on a logical dependency set between its contents and a table in a SQL Server database. The class that makes this possible is SqlCacheDependency. There are two separate implementations for the feature—one for SQL Server 2005, and one for SQL Server 2000 and SQL Server 7.0. The implementation of the two are completely independent from one another. At this time, the feature is not supported by other data relational stores you can access through the SqlDataSource control.

In SQL Server 7.0 and SQL Server 2000, the dependency is specified as a string property of the form "database:table". The database part of the string must refer to a database listed under the new <sqlCacheDependency> section of the web.config file, and the table part must be the name of a table in that database. You can specify multiple table dependencies by separating them with semicolons, as in the following example:

<asp:SqlDataSource runat="server" ConnectionString="..." SelectCommand="sp_getdata" SqlCacheDependency="pubs:Authors;pubs:TitleAuthor" EnableCaching="true" CacheDuration="300" />

Any change to Authors or TitleAuthor tables would automatically invalidate cached content retrieved by the data source control through the sp_getdata stored procedure. The possibility that a change in the database can invalidate some values in the cache is particularly helpful in Web farm scenarios when an update on a machine can automatically force a refresh on the data of all others.

Binding to Business Objects

Data-bound controls inherently force a direct binding between the presentation layer and the back end of a Web application. What if you encapsulate the data-retrieval logic, and optionally the business logic too, into a suite of classes? Can you connect these objects to data-bound controls or should you just consider business objects and data binding as mutually exclusive choices? The ObjectDataSource control allows you to design the application using a three-tier architecture and still benefit from the declarative data-binding model. Figure 9 shows how ObjectDataSource enables business components to associate their contents to data-bound controls.

figure 9

Figure 9  

The ObjectDataSource control takes the name of the class through the TypeName attribute. It also supports declarative attributes like SelectMethod, InsertMethod, UpdateMethod, and DeleteMethod, which let you specify the name of the method to use for a given operation. Each method has its own collection of parameters. Here's an example of how to use the ObjectDataSource control:

<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>

In that code snippet, the data source control maps to a class named Samples.Employee. It is your responsibility to link the proper assembly to the application. The easiest way, but not necessarily ideal in all situations, is placing the source code of the class in the Code subdirectory of the application. Any source file deployed in that folder gets dynamically compiled to an assembly and loaded in the Web space. The class is instantiated through reflection. Note that the class used by this data source component must meet a few requirements; you can't use an arbitrary class in the .NET Framework. To bind successfully, classes must have a default, parameterless constructor, be stateless, and have methods that easily map to select, update, insert, and delete semantics. In the code just shown, the Update method is expected to have the following signature:

public void Update(string empID, string address, ...)

The name of the parameter tag must match the formal name of a parameter in the method's signature. In addition, the object must perform updates one item at a time; objects that update their state using batch operations are not supported.

The ObjectDataSource control is one of the most compelling new entries in the ASP.NET 2.0 arsenal of controls and components. It promotes the use of a strong object model in the middle tier of distributed applications and makes it easier than ever to set up a direct link between business logic and presentation. In a future article, I'll cover the ObjectDataSource control.

Binding to XML Data

There are two ways you can bind data source controls to XML data: using any XML document and using the XML representation of a DataSet object. In the former case, you use a hierarchical data source control (XmlDataSource) while in the latter, you use a tabular data source component (DataSetDataSource). The XML data can be specified either through a URL (the DataFile property) or a string (the Data property). Schema information can be provided in the same way using the SchemaFile or the Schema property.

The XmlDataSource control can be bound to both tabular and hierarchical data-bound controls. The tabular view of XML data is just a list of nodes at a given level of the hierarchy, whereas the hierarchical view shows the complete hierarchy but requires a multilevel control like TreeView, Menu, or any other control that inherits the HierarchicalDataBoundControl class.

The DataSetDataSource class, instead, can only be bound to GridView and other list controls. You can't populate DataSetDataSource with a live instance of the DataSet class. However, once you've successfully initialized the control you can retrieve the internal DataSet object using the GetDataSet method.

<asp:DataSetDataSource ID="MySource" runat="server" DataFile="data.xml" />

A Quick Look Under the Hood

Data source controls inherit the base class Control and come in two flavors: tabular and hierarchical. The DataSourceControl abstract class serves as the base class for all data source controls and defines the interface between data-bound controls and the underlying data. Although the data source control has no visual rendering, it is implemented as a control to allow for "declarative persistence" (automatic instantiation during the request processing) as a native part of the .aspx source code and to gain access to the page view state.

A data source control exposes the contents of its underlying data source through a set of properties and methods. Some of these members are specific to the control, while others are common to all source controls and are defined as part of the IDataSource interface. All data source controls implement the IDataSource interface and use the interface's properties and methods to expose the bound content as a set of named views.

The IDataSource interface is simple and includes one event (DataSourceChanged) and a few methods (GetView and GetViewNames). The DataSourceChanged event is raised when the bound data source changes, such as when you change the connection string. GetView takes the name of the data source view to retrieve and returns it as a DataSourceView object. GetViewNames returns a collection of names representing the list of view objects associated with the control. As a result, the internal architecture of a data source control looks like a collection of named views.

A named view is represented with an instance of the DataSourceView class, which is similar to the ADO.NET DataView class. DataSourceView represents a customized view of data in which special settings for sorting, filtering, and other data operations have been defined. At its core, a data source control simply manages views of data loaded from the underlying data source.

The DataSourceView class is the base class for all views associated with a data source control. The number of views in a data source control depends on the connection string, the characteristics, and the actual contents of the underlying data source. Figure 10 lists the members of the DataSourceView class. Each data source control derives its own view class from DataSourceView.

figure 10 DataSourceView Class Members

Member Description
CanDelete Boolean property. Indicates whether deletions are allowed on the underlying data source. The deletion is performed by invoking the Delete method.
CanInsert Boolean property. Indicates whether insertions are allowed on the underlying data source. The insertion is performed by invoking the Insert method.
CanPage Boolean property. Indicates whether the underlying data source supports paging.
CanRetrieveTotalRowCount Boolean property. Indicates whether the underlying data source can retrieve the total number of data rows instead of the data.
CanSort Boolean property. Indicates whether the data in the view can be sorted.
CanUpdate Boolean property. Indicates whether updates are allowed on the underlying data source. The update is performed by invoking the Update method.
Name Readonly string property. Returns the name of the current view.
SortExpression String property. Gets and sets the sort expression used to create a sorted view on the underlying data.
Delete Method that performs a delete operation on the data associated with the view.
Insert Method that performs an insert operation on the data associated with the view.
Select Method that returns an enumerable object filled with the data contained in the underlying data store.
Update Method that performs an update operation on the data associated with the view.

Simplified Data-binding Syntax

Admittedly, the big change in data binding is the introduction of data source controls. So what happened with the previously supported <%# ... %> data-bound expressions? The functionality remains the same in ASP.NET 2.0, but the syntax that is used has been simplified and made less verbose than it was originally in ASP.NET version 1.x.

In ASP.NET 1.x, you typically use the static method DataBinder.Eval for the late binding of data store fields to object properties, as in this example:

<%# DataBinder.Eval(Container.DataItem, fieldName, formatString) %>

The Container.DataItem expression references the object upon which the expression is evaluated. The expression is typically a string with the name of the field to access on the data item object. In general, it can be an expression that includes indexes and property names. This commonly used code can frequently be found repeated in the same form in pages. Only the expression and format string change from place to place. ASP.NET 2.0 supports a simplified syntax, as in the following code snippet:

<%# Eval(fieldName, formatString) %>

When the page is compiled for use, the Eval call is inserted in the source code of the page as a standalone call. The following code gives you an idea of what really happens:

object o = Eval(fieldName); string result = Convert.ToString(o);

The result of the call is converted to a string and assigned to a data-bound literal control, which is an instance of the DataBoundLiteralControl class. Finally, the data-bound literal is inserted in the page's control tree. In ASP.NET 2.0, the Page class is enriched with a new protected method named Eval which determines the current data item object and calls into the original DataBinder.Eval static method.

Side-by-side with the DataBinder class, ASP.NET 2.0 provides an object capable of binding to the result of XPath expressions executed against an object that implements the IXPathNavigable interface. This class is XPathBinder and it plays the same role as DataBinder, except that it works on XML data:

<%# XPath("Orders/Order/Customer/LastName") %>

Like Eval, the XPath keyword in the expression is a new protected method on the Page class. It calls into the static method XPathBinder.Eval. Internally, XPathBinder.Eval gets a navigator object from the data source and evaluates the specified expression.

The XPathBinder class also features a Select method. The method executes an XPath query and retrieves a nodeset (an enumerable collection of XML nodes). This collection can be assigned as a late-bound value to data-bound controls such as the Repeater control. An equivalent simplified syntax exists for this scenario too:

<asp:Repeater runat="server" DataSource='<%# XPathSelect("orders/order/summary") %>'> ••• </asp:Repeater>

XPathSelect is the keyword you use in data-binding expressions to indicate the results of an XPath query conducted on the container object. If the container object does not implement IXPathNavigable, an exception is thrown.

Conclusion

The advent of data source controls do not make ADO.NET objects unnecessary; they are still essential pieces of the .NET Framework, but they have been removed from prime time and pushed to the back-end infrastructure of most common data binding operations. In ASP.NET 2.0, you need to use raw ADO.NET objects much less frequently.

Data source controls make codeless binding possible, but seldom practical in real-world situations. Some code must always be written to fine tune the page, but data source controls significantly reduce the quantity. Data source controls also integrate caching capabilities and allow you to enable caching declaratively. Everything happens behind the scenes so you can deliver best-practice code with almost no effort.

Data source controls enable a consistent model across a variety of data sources. You can use the same data-binding model regardless of whether the data source is a SQL table, an XML document, a business object, a site map, or even an Excel worksheet. The new architecture is more automatic in nature, but works side-by-side with the old model based on enumerable objects.

Dino Esposito is an instructor and consultant based in Rome, Italy. Author of Programming ASP.NET and Introducing ASP.NET 2.0, he also teaches classes on ADO.NET and ASP.NET and speaks at conferences. Dino cofounded www.vb2themax.com. Get in touch with him at cutting@microsoft.com or join the blog at https://weblogs.asp.net/despos.