This document discusses data-driven applications in ASP.NET 2.0. It covers topics like simplified data binding, data source controls for connecting to SQL databases, XML data, and business objects. It also covers data controls like GridView and DetailsView for displaying and editing data. Specific features covered include declarative data binding, caching, parameters, editing, updating and deleting records from data sources.
Developers new to ASP.NET are likely to find the new "Eval" syntax less intimidating than the old. In addition, the new XPath and XPathSelect operators (the latter of which is not shown here) provide a convenient mechanism for targeting data in XML data sources.
Data source controls reduce the amount of code you write by replacing code that queries data sources and binds the results to controls with <asp:> tags. In Visual Studio 2005, you can create these tags using drag-and-drop. You can also use the Visual Studio IDE to configure data sources. The first four data source controls listed in this table are introduced in this lecture; the final one (SiteMapDataSource) is introduced in the Site Navigation module since it's a special-purpose control that's specifically designed to interface with site maps.
NOTE: Beta 1 contains an additional data source control named DataSetDataSource. That control will be dropped from beta 2 (too much overlap with XmlDataSource), so it isn't mentioned here.
SqlDataSource isn't limited to SQL Server databases; it can connect to any database served by a managed provider, including Oracle databases.
In this example (and others in this deck), the connection string is hard-coded into the ASPX file. In practice, you should put connection strings in the <connectionStrings> section of Web.config instead and load them with $ expressions. Among other things, putting connection strings in Web.config gives you the option of encrypting them.
SqlDataSource implements LOTS of public properties. These are some of the ones that developers are most likely to use, and ones that unlock some of SqlDataSource's key features. In particular, SelectCommand defines the query that a SqlDataSource uses to fetch data from a database, while InsertCommand, UpdateCommand, and DeleteCommand define the commands it uses to update the database when 2-way data binding is performed. InsertCommand, UpdateCommand, and DeleteCommand are discussed in detail later in this lecture.
Note that EnableCaching="true" is only valid if DataSourceMode="DataSet". That's because DataSets can be cached but DataReaders cannot. DataSourceMode="DataSet" is the default, but if you're not caching, you can make SqlDataSource slightly more efficient by setting DataSourceMode="DataReader."
Caching is enabled by setting EnableCaching="true" and CacheDuration to the number of seconds you want the data cached. In this example, the SqlDataSource caches its query results for up to 1 minute (60 seconds) at a time. Under the hood, SqlDataSource uses the ASP.NET application cache to store the data, so the cached data could be removed from the cache before 60 seconds is up if ASP.NET senses memory pressure on the server and decides to evict items from the cache early in order to reclaim memory.
Data source controls would have limited value if SelectCommand, InsertCommand, UpdateCommand, and DeleteCommand could not be parameterized. A great example is the case where data displayed in a GridView is keyed to the value currently selected in a DropDownList. In that case, the SqlDataSource serving the GridView needs a SelectCommand containing a WHERE clause with a replaceable parameter whose value comes from the DropDownList. Fortunately, this scenario is supported, meaning SqlDataSource controls can perform SELECTs, INSERTs, UPDATEs, and DELETEs of arbitrary complexity.
The SelectParameters, InsertParameters, UpdateParameters, and DeleteParameters properties provide a handy mechanism for declaratively adding parameters to a SqlDataSource's SelectCommand, InsertCommand, UpdateCommand, and DeleteCommand. FilterParameters defines parameters for SqlDataSource.FilterExpression. FilterExpression filters the data returned by SelectCommand. On the surface, it might seem as if defining a FilterExpression is no different than including a WHERE clause in a SelectCommand. However, FilterExpression works even if the SqlDataSource is caching query results.
These types are used with SelectParameters, InsertParameters, UpdateParameters, DeleteParameters, and FilterParameters to define where the values for replaceable parameters come from. ControlParameter, for example, keys a replaceable parameter in a database command to a value in a control. QueryStringParameter, on the other hand, keys a replaceable parameter to a value in a query string.
This example uses the value of the item currently selected in a drop-down list to fill a parameter in a SELECT statement's WHERE clause. Note the <SelectParameters> element adding parameters to SelectCommand, and the <asp:ControlParameter> element corresponding to the @Country parameter in the SelectCommand. If SelectCommand contained additional parameters, <SelectParameters> would contain additional <asp:XxxParameter> elements.
A common question regarding SqlDataSource controls is "Can they call stored procedures?" The answer is yes, and this example demonstrates how. One of the stored procedures used in the example has no parameters. The other has a SELECT parameter, so a <SelectParameters> element is used to tie the value of that parameter to the item selected in a drop-down list. This example is functionally equivalent to the one on the previous slide.
XmlDataSource is functionally similar to SqlDataSource, but it's designed to read data from XML data sources rather than databases. One use for XmlDataSource is to read hierarchical data from an XML file and then render that data using a TreeView control, as shown here.
The DataFile and TransformFile properties refer to files containing XML and XSL data. XmlDataSource has complementary properties named Data and Transform that allow XML and XSL to be loaded from strings. Not surprisingly, you use XPath expressions to filter data in an XmlDataSource. Using the XPath property of an XmlDataSource is morally equivalent to using the FilterExpression property of a SqlDataSource.
Next to SqlDataSource, ObjectDataSource is probably the data source control that will garner the most attention from developers. Many applications (especially multi-tier applications) isolate logic for talking to databases and other data sources in a data access layer, the goal being to isolate the UI layer from changes that occur to the data source on the back end. ObjectDataSource permits you to practice declarative data binding without sacrificing the ability to use middle-tier data access components to access data sources. The downside to SqlDataSource is that it creates an explicit connection between code in the UI layer and the data on the back end. ObjectDataSource provides a solution to that problem.
Besides the SelectMethod, InsertMethod, UpdateMethod, and DeleteMethod properties, which identify the methods than an ObjectDataSource calls on a data component to perform SELECTs, INSERTs, UPDATEs, and DELETEs, the most important property in an ObjectDataSource is the TypeName property, which identifies the data component's type (class). ObjectDataSource uses TypeName to instantiate the data component, or, in the case where SelectMethod, InsertMethod, UpdateMethod, or DeleteMethod identifies a static method, to determine what type name to prefix method calls with.
ObjectDataSources can use parameterized commands like SqlDataSources, as evidenced by the presence of the SelectParameters, InsertParameters, UpdateParameters, and DeleteParameters properties. They also support caching like SqlDataSources.
The fact that ObjectDataSource creates a new instance of the data component that it wraps on each call has significant performance implications if the data component does time-consuming work at creation time. The solution to that problem is to use static methods instead of instance methods, since static methods don't require that a class instance be created.
ObjectDataSource fires an ObjectCreated event after creating an instance of the data component that it wraps. You can process this event to custom-initialize the data component right after it's created. Similarly, ObjectDataSource fires an ObjectDisposing event just before disposing of the data component IF the data component implements IDisposable. You can process this event and do clean-up work of your own before the data component's Dispose method is called.
GridView is best described as a super DataGrid: it does everything DataGrid does and then some. Unlike the DataGrid, which requires code to do sorting, paging, selecting, and editing, the GridView control can perform all these actions innately--that is, with no code. GridView also supports a richer assortment of column types than DataGrid. The ImageField column type, for example, enables GridViews to display images--something the DataGrid requires help to do.
GridView field types are analogous to DataGrid column types. CheckBoxField and ImageField are especially significant because there are no equivalent DataGrid column types.
Just as DataGrids can be customized by setting AutoGenerateColumns="false" and declaring columns in a <Columns> element, GridViews can be customized by setting AutoGenerateColumns="false" and declaring fields in a <Columns> element. This example uses an ImageField column to display employee photos obtained from Northwind's Employees table, a TemplateField to combine first names and last names, and a BoundField to display the contents of each record's "Title" field. The output appears on the next slide.
Whereas GridView is designed to render sets of records into HTML tables, DetailsView is designed to take one record or a set of records and display them one at a time. Built-in paging support enables records bound to a DetailsView to be browsed with no code. Built-in editing support allows records to be inserted, updated, and deleted, again with no code (although the reality is that you'll end up writing some code to handle concurrency issues and respond to update failures). The same field types--ImageField, CheckBoxField, and so on--that can be declared in a GridView can also be declared in a DetailsView. In demonstrations, DetailsView controls are often paired with GridView controls to implement master-detail views. However, DetailsView is perfectly capable of working without GridView.
This example declares a DetailsView control and uses a <Fields> element to declare the fields that the DetailsView displays. Like GridView, DetailsView automatically populates itself with fields if you leave AutoGenerateRows set to false (the default). The output appears on the next slide.
RE: "Visual Studio supplies the glue." There can be a lot of work involved in defining the InsertCommand, UpdateCommand, and DeleteCommand properties of a data source control, not to mention the InsertParameters, UpdateParameters, and DeleteParameters sections that accompany them. Visual Studio's Configure Data Source command will do most of that work for you. It even lets you decide whether the UpdateCommands and DeleteCommands that it generates implement a first-in wins or last-in wins strategy, which determines what happens when multiple users edit the same data at the same time.
This example demonstrates how to add Edit buttons to a GridView (AutoGenerateEditButton="true") and how to configure a SqlDataSource to support updates initiated by one of those buttons. UpdateCommand is structured so that the update succeeds even if one or more of the original fields are modified before the update takes place, commonly known as "last-in wins." Note that no code is required to display an editing UI or invoke the data source's UpdateCommand; all necessary logic is provided by the GridView.
Concurrency issues complicate updates. What happens, for example, if two users edit the same data at the same time? The answer is driven by the data source control's UpdateCommand. For example, update employees set lastname=@lastname, firstname=@firstname where employeeid=@original_employeeid implements a last-in-wins update strategy because updates succeed even if the original data has changed. By contrast, update employees set lastname=@lastname, firstname=@firstname where employeeid=@original_employeeid and lastname=@original_lastname and firstname=@original_firstname implements a first-in-wins update strategy because updates fail if the original data has changed.
In addition to structuring UpdateCommands to implement the desired update strategy, data source controls also need you to set their ConflictDetection property to indicate which strategy you've chosen. Data source controls uses this property to determine whether to add parameters representing the fields' original values to the Parameters collection of the underyling Command object.
In this example, UpdateCommand's WHERE clause has been modified so that the update fails if any of the original fields are modified before the update takes place, commonly known as "first-in wins." ConflictDetection="CompareAllValues" instructs the SqlDataSource to include original_employeeid, original_lastname, and original_firstname parameters in the Parameters collection of the underlying Command object. While the need for these parameters could probably be inferred from the UpdateCommand in this example, inferring parameters is much harder with other data source types and other providers. As an aside, the "original_" prefix can be changed using the data source controls' OldValuesParameterFormatString property.
In this example, UpdateCommand's WHERE clause has been modified so that the update fails if any of the original fields are modified before the update takes place, commonly known as "first-in wins." ConflictDetection="CompareAllValues" instructs the SqlDataSource to include original_employeeid, original_lastname, and original_firstname parameters in the Parameters collection of the underlying Command object. While the need for these parameters could probably be inferred from the UpdateCommand in this example, inferring parameters is much harder with other data source types and other providers. As an aside, the "original_" prefix can be changed using the data source controls' OldValuesParameterFormatString property.
When you edit data and submit the changes to the database on the back end, you have no guarantee that the updates will work. If you opt for a first-in-wins update strategy, for example, and user A submits changes before user B, user B's update will fail (assuming, of course, that A and B are editing the same data). Robust applications respond elegantly to update failures. One way to handle update failures when using data and data source controls is to process the events that the controls fire following an attempt to update the database and see if the attempt succeeded. The event handlers receive "status" objects (for example, SqlDataSourceStatusEventArgs) denoting the outcome of the update. If the update attempt threw an exception, the status object reports the exception and lets you specify through its ExceptionHandled property whether the exception should be rethrown.
If an update operation throws an exception, the SqlDataSource catches the exception and passes it to the Updated event handler in e.Exception. The update handler can determine whether an exception was thrown by checking e.Exception for null. If e.Exception != null, then by default the SqlDataSource will rethrow the exception when the event handler returns. If you'd prefer to handle the exception yourself, you can do so in the event handler and prevent the SqlDataSource from rethrowing the exception by setting e.ExceptionHandled to true.
Why XML? Sparse, unstructured, hierarchical or recursive data
Why store in SQL Server 2005? Transacted management, optimised queries, backup, interoperability, schema validation.
SQL cache dependencies are one of ASP.NET 2.0's coolest new features. In ASP.NET 1.x, you could place items in the application cache and create dependencies between those items and objects in the file system or other objects in the cache. Missing, however, was a means for creating dependencies between cached items and database entities, which would enable cached query results to be automatically evicted from the cache if the underlying data changes. SQL cache dependencies do exactly that.
Enabling notifications for a database adds a table named AspNet_SqlCacheTablesForChangeNotification that ASP.NET polls to see if the table has been modified. It also adds stored procedures for polling the table, enabling tables to support cache dependencies, and more. Databases (and tables) can be prepared declaratively using the aspnet_regsql.exe admin tool or programmatically using the SqlCacheDependencyAdmin class. SqlCacheDependencyAdmin has methods named EnableNotifications and EnableTableForNotifications for enabling databases and tables for notifications. It also has methods for disabling notifications for databases and tables and enumerating tables that are enabled for notification. Internally, aspnet_regsql.exe uses SqlCacheDependencyAdmin to do its work.
Enabling notifications for a table adds an insert/update/delete trigger to the table that writes to AspNet_SqlCacheTablesForChangeNotification when the table's contents change. When a SqlCacheDependency refering to that table is created, ASP.NET polls the table periodically to check for changes. The polling interval is configured through the <sqlCacheDependency> element in Web.config.
Once the database is prepared, SQL cache dependencies are enabled through Web.config. enabled="true" enables them; pollTime="5000" sets the polling interval to 5 seconds (5,000 milliseconds). For each database that you plan to use SQL cache dependencies with, you must add an <add> element to the <databases> section of the <sqlCacheDependency> element giving the database a name and identifying a connection string in the <connectionStrings> configuration section. The database name in the <add> element doesn't have to be the actual database name; it's a logical name used to create SqlCacheDependency objects (as demonstrated in the next three slides).
"Database name" is the database name registered in the <databases> section of <sqlCacheDependency>, which may or may not equal the actual database name.
ASP.NET's @ OutputCache directive now supports a SqlDependency attribute enabling SQL cache dependencies to be created declaratively.
When you use caching with data source controls, you can also use SQL cache dependencies to refresh the data source if and when the underlying data changes.
ASP.NET 1.x provided very little control over cache settings. It was impossible, for example, to limit the output cache to a specified percentage of available memory. ASP.NET 2.0 fixes this error-by-omission by supporting a wide range of configuration settings for the application cache and the output cache. Ultimately, these configuration settings will be exposed through the ASP.NET MMC snap-in or the Web Site Administration Tool. However, it's uncertain right now whether that will happen before ASP.NET 2.0 ships. For now, you can learn about these settings by examining the <caching> section of Machine.config.comments.