Chapter 8

Data Binding

WHAT’S IN THIS CHAPTER?

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

Please note that all the code examples in this chapter are available as a part of this chapter’s code download on the book’s website at www.wrox.com on the Download Code tab.

When it was originally released, one of the most exciting features of ASP.NET was its ability to bind entire collections of data to controls at run time without requiring you to write large amounts of code. The controls understood they were data-bound and would render the appropriate HTML for each item in the data collection. Additionally, you could bind the controls to any type of data sources, from simple arrays to complex Oracle database query results. This was a huge step forward from classic ASP, in which each developer was responsible for writing all the data access code, looping through a recordset, and manually rendering the appropriate HTML code for each record of data.

This chapter explores the server-side data controls. It shows how you can use the data source controls to easily and quickly bind data to data-bound controls. It also focuses on the power of the data-bound list controls included in ASP.NET. Finally, you take a look at changes in the inline data-binding syntax and inline XML data binding.

DATA SOURCE CONTROLS

In ASP.NET 1.0/1.1, you typically performed a data-binding operation by writing some data access code to retrieve a DataReader or a DataSet object; then you bound that data object to a server control such as a DataGrid, DropDownList, or ListBox. If you wanted to update or delete the bound data, you were then responsible for writing the data access code to do that. Listing 8-1 shows a typical example of a data-binding operation in ASP.NET 1.0/1.1.

LISTING 8-1: Typical data-binding operation in ASP.NET 1.0/1.1

VB

Dim conn As New SqlConnection()
Dim cmd As New SqlCommand("SELECT * FROM Customers", conn)
    Dim da As New SqlDataAdapter(cmd)
    Dim ds As New DataSet()
da.Fill(ds)
    DataGrid1.DataSource = ds
DataGrid1.DataBind()

C#

SqlConnection conn = new SqlConnection();
SqlCommand cmd = new SqlCommand("SELECT * FROM Customers", conn);
    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataSet ds = new DataSet();
da.Fill(ds);
    DataGrid1.DataSource = ds;
DataGrid1.DataBind();

Since ASP.NET 1.0/1.1, ASP.NET has introduced an additional layer of abstraction through the use of data source controls. As shown in Figure 8-1, these controls abstract the use of an underlying data provider, such as the SQL Data Provider or the OLE DB Data Provider. This means you no longer need to concern yourself with the hows and whys of using the data providers, instead letting the data source controls do the heavy lifting for you. You need to know only where your data is and, if necessary, how to construct a query for performing CRUD (Create, Retrieve, Update, and Delete) operations.

FIGURE 8-1

image

Additionally, because the data source controls all derive from the Control class, you can use them much as you would any other web server control. For instance, you can define and control the behavior of the data source control either declaratively in declarative markup or programmatically. In fact, although you certainly can control the data source controls from code, most of the examples in this chapter show you how to perform powerful database queries using nothing more than the Visual Studio 2012 wizards and declarative syntax.

ASP.NET has seven built-in data source controls, each used for a specific type of data access. Table 8-1 lists and describes each data source control.

TABLE 8-1

CONTROL NAME DESCRIPTION
SqlDataSource Provides access to any data source that has an ADO.NET Data Provider available; by default, the control has access to the ODBC, OLE DB, SQL Server, Oracle, and SQL Server Compact providers.
LinqDataSource Provides access to different types of LinqToSql objects using LINQ queries.
ObjectDataSource Provides specialized data access to business objects or other classes that return data.
XmlDataSource Provides specialized data access to XML documents, either physically on disk or in memory.
SiteMapDataSource Provides specialized access to sitemap data for a website that is stored by the sitemap provider.
AccessDataSource Provides specialized access to Access databases.
EntityDataSource Provides specialized access to Entity Framework models.

All the data source controls are derived from either the DataSourceControl class or the HierarchicalDataSourceControl class, both of which are derived from Control and implement the IDataSource and IListSource interfaces. This means that although each control is designed for use with a specific source of data, they all share a basic set of core functionality. It also means that should you need to, you can easily create your own custom data source controls based on the structure of your specific data sources.

SqlDataSource Control

The SqlDataSource control is the data source control to use if your data is stored in SQL Server, SQL Server Express, SQL Server LocalDb, SQL Server Compact, or Oracle. The control provides an easy-to-use wizard that walks you through the configuration process, or you can modify the control manually by changing the control attributes directly in Source view. In this section you will see how you can create and configure a SqlDataSource control, as well as how you can filter the results. In later sections of this book, you see how in conjunction with other data controls like GridView, you can allow users to update and delete data through the SqlDataSource control.

Begin using the control by opening an ASP.NET WebForm page and dragging the SqlDataSource control from the toolbox onto the form. You find all the data-related controls located under the Data section in the Visual Studio toolbox.

Configuring a Data Connection

After the control has been dropped onto the web page, you need to tell it what database connection it should use. The easiest way to do this is by using the Configure Data Source wizard, shown in Figure 8-2. Launch this wizard by selecting the Configure Data Source option from the data source control’s smart tag menu.

FIGURE 8-2

image

After the wizard opens, you can select an existing database connection from the drop-down list or create a new connection. Most of the examples shown in this chapter use the Northwind database as their data source, so if it does not already exist, you can create a new connection to the Northwind database.


NOTE The Northwind database is included as part of the download of the code for this chapter as well.

To create a new connection, click the New Connection button to open the Choose Data Source dialog box. This dialog box allows you to select the specific data source for this connection and the data provider to use for the data source.


NOTE The list of providers is generated from the data contained in the DbProviderFactory node of the machine.config file. If you have additional providers to display in the wizard you can modify your machine.config file to include specific providers’ information.

After you’ve selected the source and provider, click the Continue button to open the Add Connection dialog box. This dialog box allows you to set all the properties of the new database connection. Figure 8-3 shows the window for configuring a SQL Server database connection.

FIGURE 8-3

image

Simply fill in the appropriate information for your database connection, click the Test Connection button to verify that your connection information is correct, and then click OK to return to the Configure Data Source wizard.

Click the Next button to continue to the wizard’s next step, which allows you to save your database connection information to your web.config file. Storing the connection here can make maintenance and deployment of your application easier. This screen allows you to specify the key under which the connection information should be stored in the configuration file. Should you choose not to store your connection information in the web.config file, it is stored in the actual .aspx page as a property of the SqlDataSource control named ConnectionString. If the provider chosen was not the SQL Data Provider, a property named ProviderName will be used to store that setting.

The next step in the wizard allows you to select the data to retrieve from the database. As you can see in Figure 8-4, a drop-down list of all the tables and views available in the database is shown. You can select a table or view, and the specific columns you want to include in the query. Select all columns available using an asterisk (*), or choose specific columns by selecting the check box located next to each column name.

FIGURE 8-4

image

The WHERE and ORDER BY buttons allow you to specify WHERE and ORDER BY clauses for filtering and sorting the results of the query. For now, do not enter any additional WHERE or ORDER BY settings.

The Advanced button contains two advanced options. You can have the wizard generate INSERT, UPDATE, and DELETE statements for your data, based on the SELECT statement you created. You can also configure the data source control to use Optimistic Concurrency.


NOTE Optimistic Concurrency is a database technique that can help you prevent the accidental overwriting of data. When Optimistic Concurrency is enabled, the UPDATE and DELETE SQL statements used by the SqlDataSource control are modified so that they include both the original and updated values. When the queries are executed, the data in the targeted record is compared to the SqlDataSource controls’ original values and if a difference is found, which indicates that the data has changed since it was originally retrieved by the SqlDataSource control, the update or delete will not occur.

The final screen of the wizard allows you to preview the data selected by your data source control to verify that the query is working as you expect it to. Simply click the Finish button to complete the wizard.

When you are done configuring your data connection, change to Source view in Visual Studio to see how the wizard has generated the appropriate attributes for your control. It should look something like the code in Listing 8-2.

LISTING 8-2: Typical SqlDataSource control generated by Visual Studio

<asp:SqlDataSource ID="SqlDataSource1" Runat="server"
  SelectCommand="SELECT * FROM [Customers]"
  ConnectionString="<%$ ConnectionStrings:ConnectionString %>" />

You can see that the control uses a declarative syntax to configure which connection it should use by creating a ConnectionString attribute, and which query to execute by creating a SelectCommand attribute. A little later in this chapter, you look at how to configure the SqlDataSource control to execute INSERT, UPDATE, and DELETE commands as this data changes.

Data Source Mode Property

After you’ve set up a basic SqlDataSource control, one of many important properties you can configure is the DataSourceMode property. This property allows you to tell the control whether it should use a DataSet (the default selection) or a DataReader internally when retrieving the data. If you choose to use a DataReader, data is retrieved using what is commonly known as firehose mode, or using a forward-only, read-only cursor. This is the fastest and most efficient way to read data from your data source because a DataReader does not have the memory and processing overhead of a DataSet.

Choosing to use a DataSet makes the data source control more powerful by enabling the control to perform other operations such as filtering, sorting, and paging. It also enables the built-in caching capabilities of the control.

Filtering Data Using SelectParameters

Of course, when selecting data from your data source, you might not want to get every single row of data from a view or table. You want to be able to specify parameters in your query to limit the data that is returned. You saw that by using the Configure Data Source wizard you can add WHERE clauses to your query. “Under the hood” the wizard is actually using the SqlDataSource’s SelectParameters collection to create parameters that it uses at run time to filter the data returned from the query.

The SelectParameters collection consists of types that derive from the Parameters class. You can combine any number of parameters in the collection. The data source control then uses these to create a dynamic SQL query. Table 8-2 lists and describes the available parameter types.

TABLE 8-2

PARAMETER DESCRIPTION
ControlParameter Uses the value of a property of the specified control
CookieParameter Uses the key value of a cookie
FormParameter Uses the key value from the Forms collection
QueryStringParameter Uses a key value from the QueryString collection
ProfileParameter Uses a key value from the user’s profile
RouteParameter Uses a key value from the route segment of the requested URL
SessionParameter Uses a key value from the current user’s session

Because all the parameter controls derive from the Parameter class, they all contain several useful common properties. Some of these properties are shown in Table 8-3.

TABLE 8-3

PROPERTY DESCRIPTION
Type Allows you to strongly type the value of the parameter
ConvertEmptyStringToNull Indicates the control should convert the value assigned to it to Null if it is equal to System.String.Empty
DefaultValue Allows you to specify a default value for the parameter if it is evaluated as Null

The code in Listing 8-3 shows an example of adding a QueryStringParameter to the SelectParameters collection of the SqlDataSource control. As you can see, the SelectCommand query has been modified to include a WHERE clause. When you run this code, the value of the query string field ID is bound to the @CustomerID placeholder in your SelectCommand, allowing you to select only those customers whose CustomerID field matches the value of the query string field.

LISTING 8-3: Filtering select data using SelectParameter controls

<asp:SqlDataSource ID="SqlDataSource1" Runat="server"
  SelectCommand="SELECT * FROM [Customers]
          WHERE ([CustomerID] = @CustomerID)"
  ConnectionString="<%$ ConnectionStrings:ConnectionString %>"
  DataSourceMode="DataSet">
  <SelectParameters>
    <asp:QueryStringParameter Name="CustomerID"
      QueryStringField="ID" Type="String">
    </asp:QueryStringParameter>
  </SelectParameters>
</asp:SqlDataSource>

In addition to using the Configure Data Source wizard to create the SelectParameters collection, you can manually define them in markup, or create parameters using the Command and Parameter Editor dialog box, which you access by modifying the SelectQuery property of the SqlDataSource control while you are viewing the web page in design mode.

The SqlDataSource control includes an additional way to filter results called the FilterParameters. FilterParameters provide the same basic filtering capabilities as SelectParameters, but use a different technique. The specific differences between SelectParameters and FilterParameters are discussed later in this chapter.

Conflict Detection

The Conflict Detection property allows you to tell the control whether there is a conflict between the value that you are updating and the one in the database. It allows you to prevent users from accidently overriding each other’s updates. When this property is set to OverwriteChanges, the user who updated the value last wins.

If the value is set to CompareAllValues, the control compares the original values in the database to the ones received from the user. If the control detects that there is a conflict, it does not update the database; otherwise, it updates the database.

One way to determine whether your update has encountered a concurrency error is by testing the AffectedRows property in the SqlDataSource’s Updated event. Listing 8-4 shows one way to do this.

LISTING 8-4: Detecting concurrency errors after updating data

VB

Protected Sub SqlDataSource1_Updated(ByVal sender as Object,
  ByVal e As System.Web.UI.WebControls.SqlDataSourceStatusEventArgs)
  If (e.AffectedRows > 0) Then
      Me.lblMessage.Text = "The record has been updated"
  Else
      Me.lblMessage.Text = "Possible concurrency violation"
  End If
End Sub

C#

protected void SqlDataSource1_Updated(object sender,
  SqlDataSourceStatusEventArgs e)
{
  if (e.AffectedRows > 0)
    this.lblMessage.Text = "The record has been updated";
  else
    this.lblMessage.Text = "Possible concurrency violation";
}

Handling Database Errors

The data source control’s events are very useful for trapping and handling errors that occur while you are attempting to execute a SQL command against the database. For instance, Listing 8-5 demonstrates how you can use the SqlDataSource control’s Updated event to handle a database error that has bubbled back to the application as an exception.

LISTING 8-5: Using the SqlDataSource control’s Updated event to handle database errors

VB

Protected Sub SqlDataSource1_Updated(ByVal sender As Object,
  ByVal e As System.Web.UI.WebControls.SqlDataSourceStatusEventArgs)
      If (e.Exception IsNot Nothing) Then
    Me.lblMessage.Text = e.Exception.Message
    e.ExceptionHandled = True
  End If
End Sub

C#

protected void SqlDataSource1_Updated(object sender,
  System.Web.UI.WebControls.SqlDataSourceStatusEventArgs e)
{
  if (e.Exception != null)
  {
    this.lblMessage.Text = e.Exception.Message;
    e.ExceptionHandled = true;
  }
}

An extremely important part of this example is the code that sets the ExceptionHandled property. By default, this property returns False. Therefore, even if you detect that the Exception property is not null, and you attempt to handle the error, the exception still bubbles out of the application. Setting the ExceptionHandled property to True tells .NET that you have successfully handled the exception and that it is safe to continue executing.

AccessDataSource Control

This control gives you specialized access to Access databases using the Jet Data provider, but it still uses SQL commands to perform data retrieval because it is derived from the SqlDataSource.

LinqDataSource Control

Much like the SqlDataSource control, which generates queries for SQL databases by converting its property settings into SQL queries, the LinqDataSource generates Linq queries for LinqToSql data objects in your application.

EntityDataSource Control

The EntityDataSource is a specialized data source control designed for applications that make use of the ADO.NET Entity Framework.


NOTE For an in-depth discussion of the Entity Framework, see Chapter 11.

The EntityDataSource control handles selecting, updating, inserting, and deleting data for data controls on a web page as well as automatically enabling sorting and paging of data, allowing you to easily bind to and navigate data from an Entity Framework model. Like all other data source controls, you get started using the EntityDataSource control by dragging it from the Visual Studio toolbox onto the design surface and then selecting the Configure option from the control’s smart tag to load the control’s Configuration wizard. After the wizard opens, select or supply a connection string for the control, then select the Default Container Name and click Next. An example of selecting a connection to a data model of the Northwind database is shown in Figure 8-5.

FIGURE 8-5

image

The next screen allows you to select the specific EntitySetName you want to expose through the data source control. If you want to ensure that only a specific type is returned from the query, you can also specify an EntityTypeFilter. After an EntitySetName is selected, you can create custom projections of properties by selecting them from the list. Figure 8-6 demonstrates selecting the Customers EntitySetName from the Northwind data model.

FIGURE 8-6

image

Finally on this screen you can configure the control to allow automatic inserts, updates, and deletes of data. Note that if you have created a custom projection by selecting specific columns, these options will be disabled.

After you’ve completed the configuration of the data source control you can now bind any data-bound control to it as normal. Listing 8-6 shows an example of the markup generated by the configuration wizard.

LISTING 8-6: Markup generated by the EntityDataSource Configuration Wizard

<asp:EntityDataSource ID="EntityDataSource1" runat="server"
  ConnectionString="name=NorthwindEntities"
  DefaultContainerName="NorthwindEntities" EnableDelete="True"
  EnableFlattening="False" EnableInsert="True" EnableUpdate="True"
  EntitySetName="Customers" EntityTypeFilter="Customer">
</asp:EntityDataSource>

The EntityDataSource control includes a variety of other properties and events that you can use to customize even further the behavior of the control.

You can specify parameters that can be used in any of the control’s queries as you can in any data source control.

Using the QueryExtender for Complex Filters

Although the EntityDataSource and LinqDataSource controls include built-in capabilities for filtering data, these capabilities do not expose the full power that LINQ provides to create queries that include filters. This is where the QueryExtender comes into play, by allowing you to define complex searches, data range filters, complex multicolumn OrderBy clauses, and even completely custom expressions. Table 8-4 shows the available filter expression types.

TABLE 8-4

EXPRESSION TYPE DESCRIPTION
SearchExpression Searches a field or fields for string values and compares them to a specified string. The expression can perform “StartsWith,” “EndsWith,” or “Contains” searches.
RangeExpression Like the SearchExpression, but uses a pair of values to define a minimum and maximum range.
PropertyExpression Compares a property value of a column to a specified value.
OrderByExpression Enables you to sort data by a specified column and sort direction.
CustomExpression Enables you to provide a custom LINQ expression.
MethodExpression Enables you to invoke a method containing a custom LINQ query.
OfTypeExpression Enables you to filter elements in the data source by type.

The QueryExtender works with any data source control that implements the IQueryableDataSource interface. By default this includes the LinqDataSource and EntityDataSource controls.

To see how you can use the QueryExtender, start by dragging a QueryExtender control onto the design surface from the toolbox. In the page markup, connect the QueryExtender to the EntityDataSource by specifying the ID of the EntityDataSource control as the QueryExtender’s TargetControlID property value. Now all you have to do is define the filter expressions you want to use within the QueryExtender. Listing 8-7 demonstrates using a SearchExpression.

LISTING 8-7: Using the QueryExtender control to filter query results

<asp:EntityDataSource ID="EntityDataSource1" runat="server"
  ConnectionString="name=NorthwindEntities"
  DefaultContainerName="NorthwindEntities" EnableDelete="True"
  EnableFlattening="False" EnableInsert="True" EnableUpdate="True"
  EntitySetName="Customers" EntityTypeFilter="Customer">
</asp:EntityDataSource>
<asp:QueryExtender ID="QueryExtender2"
  runat="server" TargetControlID=" EntityDataSource1">
  <asp:SearchExpression SearchType="StartsWith"
    DataFields="CustomerID">
    <asp:QueryStringParameter DefaultValue="A"
      QueryStringField="search" />
  </asp:SearchExpression>
</asp:QueryExtender>

As you can see, using a QueryStringParameter, the expression filters the query results to only CustomerIDs that start with the value specified by the query string field "search".

XmlDataSource Control

The XmlDataSource control provides you with a simple way of binding XML documents, either in-memory or located on a physical drive. The control provides you with a number of properties that make specifying an XML file containing data and an XSLT transform file for converting the source XML into a more suitable format easy. You can also provide an XPath query to select only a certain subset of data.

Listing 8-8 shows how you might consume an RSS feed from the MSDN website, selecting all the item nodes within it for binding to a bound list control such as the GridView.

LISTING 8-8: Using the XmlDataSource control to consume an RSS feed

<asp:XmlDataSource ID="XmlDataSource1" Runat="server"
  DataFile="http://msdn.microsoft.com/rss.xml"
  XPath="rss/channel/item" />

In addition to the declarative attributes you can use with the XmlDataSource, a number of other helpful properties and events are available.

Many times your XML is not stored in a physical file, but rather is simply a string stored in your application memory or possibly in a database. The control provides the Data property, which accepts a simple string of XML to which the control can bind. Note that if both the Data and DataFile properties are set, the DataFile property takes precedence over the Data property.

Additionally, in certain scenarios, you may want to export the bound XML out of the XmlDataSource control to other objects or even save any changes that have been made to the underlying XML if it has been bound to a control such as a GridView. The XmlDataSource control provides two methods to accommodate this. One method is GetXmlDocument, which allows you to export the XML by returning a basic System.Xml.XmlDocument object that contains the XML loaded in the data source control.

The other way is by using the control’s Save method to persist changes made to the XmlDataSource control’s loaded XML back to disk. Executing this method assumes you have provided a file path in the DataFile property.

The XmlDataSource control also provides you with a number of specialized events. The Transforming event that is raised before the XSLT transform specified in the Transform or TransformFile properties is applied and allows you to supply custom arguments to the transform.

ObjectDataSource Control

The ObjectDataSource control gives you the power to bind data controls directly to middle-layer business objects that can be hard-coded or automatically generated from programs such as Object Relational (O/R) mappers.

To demonstrate how to use the ObjectDataSource control, create a class in the project that represents a customer and a class that contains methods that allow you to select, insert, update, and delete customers from a collection. Listing 8-9 shows a class that you can use for this demonstration.

LISTING 8-9: A Customer class and CustomerRepository class

VB

Public Class Customer
  Public Property CustomerID() As String
  Public Property CompanyName() As String
  Public Property ContactName() As String
  Public Property ContactTitle() As String
End Class
    Public Class CustomerRepository
      Public Function [Select](
    ByVal customerID As String) As List(Of Customer)
    ' Implement logic here to retrieve the Customer
    ' data based on the methods customerID parameter
    Dim _customers As New List(Of Customer)
    _customers.Add(New Customer() With {
          .CompanyName = "Acme", .ContactName = "Wiley Cyote",
          .ContactTitle = "President", .CustomerID = "ACMEC"})
    Return _customers
  End Function
      Public Sub Insert(ByVal c As Customer)
    ' Implement Insert logic
  End Sub
      Public Sub Update(ByVal c As Customer)
    ' Implement Update logic
  End Sub
      Public Sub Delete(ByVal c As Customer)
    ' Implement Delete logic
  End Sub
    End Class

C#

public class Customer
{
  public string CustomerID { get; set; }
  public string CompanyName { get; set; }
  public string ContactName { get; set; }
  public string ContactTitle { get; set; }
}
    public class CustomerRepository
{
  public CustomerRepository()
  {
  }
      public List<Customer> Select(string customerId)
  {
    // Implement logic here to retrieve the Customer
    // data based on the methods customerId parameter
    List<Customer> _customers = new List<Customer>();
    _customers.Add(new Customer() {
      CompanyName = "Acme", ContactName = "Wiley Cyote",
      ContactTitle = "President", CustomerID = "ACMEC" });
    return _customers;
  }
      public void Insert(Customer c)
  {
    // Implement Insert logic
  }
      public void Update(Customer c)
  {
    // Implement Update logic
  }
      public void Delete(Customer c)
  {
    // Implement Delete logic
  }
    }

To start using the ObjectDataSource, drag the control onto the design surface and open the Configuration wizard from the control’s smart tag. When the wizard opens, select the business object you want to use as your data source from the drop-down list. The list shows all the classes located in the App_Code folder of your website that can be successfully compiled. In this case, you want to use the CustomerRepository class shown in Listing 8-9.

The methods that the ObjectDataSource uses to perform CRUD operations must follow certain rules in order for the control to understand. For example, the control’s SELECT method must return a DataSet, DataReader, or a strongly typed collection. Each of the control’s operation tabs explains what the control expects of the method you specify for it to use. Additionally, if a method does not conform to the rules that specific operation expects, it is not listed in the drop-down list on that tab.

Finally, if your SELECT method contains parameters, the wizard lets you create SelectParameters you can use to provide the method parameter data.

When you have completed configuring the ObjectDataSource, you should have code in your page source like that shown in Listing 8-10.

LISTING 8-10: The ObjectDataSource code generated by the Configuration Wizard

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
  DeleteMethod="Delete" InsertMethod="Insert"
  SelectMethod="Select" TypeName="CustomerRepository"
  UpdateMethod="Update" DataObjectTypeName="Customer">
  <SelectParameters>
    <asp:QueryStringParameter Name="customerId"
      QueryStringField="ID" Type="string" />
  </SelectParameters>
</asp:ObjectDataSource>

As you can see, the wizard has generated the attributes for the SELECT, UPDATE, INSERT, and DELETE methods you specified in the wizard. Also notice that it has added the Select parameter. Depending on your application, you could change this to any of the Parameter objects discussed earlier, such as a ControlParameter or QueryStringParameter object.

SiteMapDataSource Control

The SiteMapDataSource enables you to work with data stored in your website’s SiteMap configuration file if you have one. This can be useful if you are changing your sitemap data at run time, perhaps based on user rights or status.

Note two items regarding the SiteMapDataSource control.


NOTE The SiteMapDataSource control has a property called SiteMapProvider, which allows you to specify which sitemap provider to use. As a rule, it will use the default sitemap provider, but you are free to specify others. See http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.sitemapdatasource.sitemapprovider for more information.

DATA SOURCE CONTROL CACHING

ASP.NET includes a great caching infrastructure that allows you to cache on the server arbitrary objects for set periods of time. You can learn more about ASP.NET caching in Chapter 22.

The data source controls leverage this built-in caching infrastructure to allow you to easily cache their query results. Each control uses the same basic syntax for configuring caching, allowing you to create simple caching policies including a cache direction, expiration policies, and key dependencies.


NOTE The SqlDataSource control’s caching features are available only if you have set the DataSourceMode property to DataSet. If it is set to DataReader, the control will throw a NotSupportedException.

The cache duration can be set to a specific number of seconds, or you can set it to Infinite to force the cached data to never expire. Listing 8-11 shows how you can easily add caching features to a data source control.

LISTING 8-11: Enabling caching on a SqlDataSource control

<asp:SqlDataSource ID="SqlDataSource1" Runat="server"
  SelectCommand="SELECT * FROM [Customers]"
  ConnectionString="<%$ ConnectionStrings:ConnectionString %>"
  DataSourceMode="DataSet" ConflictDetection="CompareAllValues"
  EnableCaching="True" CacheKeyDependency="SomeKey"
  CacheDuration="Infinite" />

Some controls also extend the core set of caching features with additional caching functionality specific to their data sources. For example, if you are using the SqlDataSource control you can use that control’s SqlCacheDependency property to create SQL dependencies.

DATA-BOUND CONTROLS

This section looks at some data-bound controls that can be bound to data source controls and can be used to display, create, edit, and delete data.

GridView

The GridView control is a powerful data grid control that allows you to display an entire collection of data, add sorting and paging, and perform inline editing.

Start using the GridView by dragging the control onto the design surface of an ASP.NET web page. When you do this, you will be prompted to select a data source control to bind to the grid. You can use the SqlDataSource control created earlier in the chapter.

Listing 8-12 shows a simple use of the GridView with a SqlDataSource control. In this example the explicit field definitions have been removed to allow the control to automatically generate columns based on the structure of the data source.

LISTING 8-12: Using the GridView control in an ASP.NET web page

<html>
<head runat="server">
  <title>Using the GridView Control</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:GridView ID="GridView1" runat="server"
      DataSourceID="SqlDataSource1">
    </asp:GridView>
        <asp:SqlDataSource ID="SqlDataSource1" Runat="server"
      SelectCommand="SELECT * FROM [Customers]"
      ConnectionString=
        "<%$ ConnectionStrings:ConnectionString %>"
      DataSourceMode="DataSet"
      ConflictDetection="CompareAllValues" EnableCaching="True"
      CacheKeyDependency="MyKey" CacheDuration="Infinite">
    </asp:SqlDataSource>
  </div>
  </form>
</body>
</html>

When you run the page, ASP.NET executes the database query using the SqlDataSource control and then binds the results to the GridView control. The GridView control generates a table layout containing all the data returned from the query.

After you assign the GridView a data source, normally the grid updates itself to match the data source schema, setting its AutoGenerateFields property to False and generating a field in the GridView’s Columns collection for each public property or database table column exposed by the data source. Listing 8-13 shows the collection of explicit column definitions.

LISTING 8-13: Explicitly defined GridView columns

<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1"
  AutoGenerateColumns="False" DataKeyNames="CustomerID">
  <Columns>
    <asp:BoundField DataField="CustomerID"
      HeaderText="CustomerID" ReadOnly="True"
      SortExpression="CustomerID" />
    <asp:BoundField DataField="CompanyName"
      HeaderText="CompanyName" SortExpression="CompanyName" />
    <asp:BoundField DataField="ContactName"
      HeaderText="ContactName" SortExpression="ContactName" />
    <asp:BoundField DataField="ContactTitle"
      HeaderText="ContactTitle" SortExpression="ContactTitle" />
    <asp:BoundField DataField="Address" HeaderText="Address"
      SortExpression="Address" />
    <asp:BoundField DataField="City" HeaderText="City"
      SortExpression="City" />
    <asp:BoundField DataField="Region" HeaderText="Region"
      SortExpression="Region" />
    <asp:BoundField DataField="PostalCode" HeaderText="PostalCode"
      SortExpression="PostalCode" />
    <asp:BoundField DataField="Country" HeaderText="Country"
      SortExpression="Country" />
    <asp:BoundField DataField="Phone" HeaderText="Phone"
      SortExpression="Phone" />
    <asp:BoundField DataField="Fax" HeaderText="Fax"
      SortExpression="Fax" />
  </Columns>
</asp:GridView>

Notice that when creating the column definitions, the control by default uses the BoundField type for each column. Each BoundField has the DataField property, which connects the field to a property of the data source, and the SortExpression defined. The control also detects read-only properties in the data source and sets the field’s ReadOnly property.

When the GridView is rendering, it raises a number of events that you can use to alter the control’s output or add custom logic to your application. These are described in Table 8-5.

TABLE 8-5

EVENT NAME DESCRIPTION
DataBinding Raised as the GridView’s data-binding expressions are about to be evaluated.
RowCreated Raised each time a new row is created in the grid. Before the grid can be rendered, a GridViewRow object must be created for each row in the control. The RowCreated event allows you to insert custom content into the row as it is being created.
RowDataBound Raised as each GridViewRow is bound to the corresponding data in the data source. This event allows you to evaluate the data being bound to the current row and to affect the output if you need to.
DataBound Raised after the binding is completed and the GridView is ready to be rendered.

The RowDataBound event is especially useful, enabling you to inject logic into the binding process for each data source item being bound to the GridView. Listing 8-14 shows how you can use this event to examine the data being bound to the current grid row and to insert special logic, in this example checking to see whether the item’s Region value is Null. If a null value is found, logic changes the ForeColor and BackColor properties of the GridView’s row.

LISTING 8-14: Using the RowDataBound to insert custom rendering logic

VB

<script runat="server">
  Protected Sub GridView1_RowDataBound(ByVal sender As Object,
    ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs)
        If (e.Row.DataItem IsNot Nothing) Then
      Dim drv As System.Data.DataRowView =
        CType(e.Row.DataItem, System.Data.DataRowView)
          If (drv("Region") Is System.DBNull.Value) Then
        e.Row.BackColor = System.Drawing.Color.Red
        e.Row.ForeColor = System.Drawing.Color.White
      End If
    End If
  End Sub
</script>

C#

<script runat="server">
  protected void GridView1_RowDataBound(object sender,
                     GridViewRowEventArgs e)
  {
    if (e.Row.DataItem != null)
    {
      System.Data.DataRowView drv =
        (System.Data.DataRowView)e.Row.DataItem;
          if (drv["Region"] == System.DBNull.Value)
      {
        e.Row.BackColor = System.Drawing.Color.Red;
        e.Row.ForeColor = System.Drawing.Color.White;
      }
    }
  }
</script>

The GridView also includes events that correspond to selecting, inserting, updating, and deleting data. You will learn more about these events later in the chapter.

Handling Null and Empty Data Conditions

In some cases, the data source bound to the GridView may not contain any data for the control to bind to. In these cases you may want to provide the end users with some feedback informing them of this situation. The GridView includes two mechanisms to do this.

One option is to use the EmptyDataText property. This property allows you to specify a string of text that is displayed to the user when no data is present for the GridView to bind to. When the ASP.NET page loads and the GridView determines that no data is available in its bound data source, it creates a special DataRow containing the EmptyDataText value and displays that to the users. Listing 8-15 shows how you can add this property to the GridView.

LISTING 8-15: Adding EmptyDataText to the GridView

<asp:GridView ID="GridView1" Runat="server"
  DataSourceID="SqlDataSource1" DataKeyNames="CustomerID"
  AutoGenerateColumns="True"
  EmptyDataText="No data was found using your query"></asp:GridView>

The other option is to use the EmptyDataTemplate control template to completely customize the special row the user sees when no data exists for the control to bind to.


NOTE A control template is simply a container that gives you the capability to add other content such as text, HTML controls, or even ASP.NET controls. The GridView control provides you with a variety of templates for various situations, including the EmptyDataTemplate template. This chapter examines these templates throughout the rest of this section.

You can access the template from the Visual Studio design surface in two ways. One option is to right-click the GridView control, expand the Edit Template option in the context menu, and select the EmptyDataTemplate item from the menu. The other option is to select the Edit Templates option from the GridView’s smart tag. Selecting this option puts the GridView into template editing mode and presents you with a dialog box from which you can choose the specific template you want to edit. Simply select EmptyDataTemplate from the drop-down list, as shown in Figure 8-7.

FIGURE 8-7

image

After you have entered template editing mode, you can add custom text and/or controls to the template editor on the design surface. When you have finished editing the template, right-click, or open the GridView’s smart tag and select the End Template Editing option.

Switching to Source view, you see that an <EmptyDataTemplate> element has been added to the GridView control. The element contains all the content you added while editing the template. Listing 8-16 shows an example of an EmptyDataTemplate.

LISTING 8-16: Using EmptyDataTemplate

<EmptyDataTemplate>
        No data could be found based on your query parameters.
        Please enter a new query.
</EmptyDataTemplate>

You could, of course, have also added the template and its contents while in Source view.

The GridView also allows you to configure a value to display if the GridView encounters a Null value when binding to a data source. For an example of this, add a column using a <asp:BoundField> control, as shown in Listing 8-17.

LISTING 8-17: Using the Null value

<asp:BoundField DataField="Region" HeaderText="Region"
   NullDisplayText="N/A" SortExpression="Region" />

The <asp:BoundField> is configured to display the Region column from the Customers table. As you look through the data in the Region column, notice that not every row has a value in it. If you don’t want to display just a blank cell, you can use the NullDisplayText property to configure the GridView to display text in place of the empty items in the column.

Column Sorting

The capability to sort data is one of the most basic tools users have to navigate through data. To enable sorting in the GridView control just set the AllowSorting attribute to True. The control takes care of all the sorting logic for you internally. Listing 8-18 shows how to add this attribute to your grid.

LISTING 8-18: Adding sorting to the GridView Control

<asp:GridView ID="GridView1" Runat="server"
   DataSourceID="SqlDataSource1" DataKeyNames="CustomerID"
   AutoGenerateColumns="True" AllowSorting="True"></asp:GridView>

After enabling sorting, you will see that all the grid’s column headers have now become hyperlinks.

The GridView sorting can handle both ascending and descending sorting. Repeatedly click on a column header to cause the sort order to switch back and forth between ascending and descending. The GridView also includes a Sort method that can accept multiple SortExpressions to enable multicolumn sorting. Listing 8-19 shows how you can use the GridView’s Sorting event to implement multicolumn sorting.

LISTING 8-19: Adding multicolumn sorting to the GridView

VB

<script runat="server">
  Protected Sub GridView1_Sorting(ByVal sender As Object,
                  ByVal e As GridViewSortEventArgs)
        Dim oldExpression As String = GridView1.SortExpression
    Dim newExpression As String = e.SortExpression
        If (oldExpression.IndexOf(newExpression) < 0) Then
      If (oldExpression.Length > 0) Then
        e.SortExpression = newExpression & "," & oldExpression
      Else
        e.SortExpression = newExpression
      End If
    Else
      e.SortExpression = oldExpression
    End If
  End Sub
</script>

C#

<script runat="server">
  protected void GridView1_Sorting(object sender,
                   GridViewSortEventArgs e)
  {
    string oldExpression = GridView1.SortExpression;
    string newExpression = e.SortExpression;
    if (oldExpression.IndexOf(newExpression) < 0)
    {
      if (oldExpression.Length > 0)
        e.SortExpression = newExpression + "," + oldExpression;
      else
        e.SortExpression = newExpression;
    }
    else
    {
      e.SortExpression = oldExpression;
    }
  }
</script>

The listing uses the Sorting event to manipulate the value of the control’s SortExpression property. The event’s parameters enable you to examine the current sort expression, direction of the sort, or even cancel the sort action altogether. The GridView also offers a Sorted event, which is raised after the sort has completed.

Paging GridView Data

The GridView also allows you to easily add another common feature — paging. To enable paging, set the AllowPaging property to True or select the Enable Paging check box in the GridView’s smart tag. The control defaults to a page size of 10 records and adds the pager to the bottom of the grid. Listing 8-20 shows an example of modifying your grid to enable sorting and paging.

LISTING 8-20: Enabling sorting and paging on the GridView Control

<asp:GridView ID="GridView1" Runat="server"
   DataSourceID="SqlDataSource1" DataKeyNames="CustomerID"
   AutoGenerateColumns="True" AllowSorting="True"
   AllowPaging="True"></asp:GridView>

The GridView includes a variety of properties that allow you to customize paging. For instance, you can control the number of records displayed on the page using the GridView’s PageSize attribute. The PagerSettings Mode attribute allows you to dictate how the grid’s pager is displayed using the various pager modes including NextPrevious, NextPreviousFirstLast, Numeric (the default value), or NumericFirstLast. Additionally, specifying the PagerStyle element in the GridView, you can customize how the grid displays the pager text, including font color, size, and type, as well as text alignment and a variety of other style options. Listing 8-21 shows how you can customize your GridView control to use the NextPrevious mode and style the pager text using the PagerStyle element.

LISTING 8-21: Using the PagerStyle and PagerSettings properties in the GridView control

<asp:GridView ID="GridView1" Runat="server" DataSourceID="SqlDataSource1"
   DataKeyNames="CustomerID" AutoGenerateColumns="True"
   AllowSorting="True" AllowPaging="True" PageSize="10">
   <PagerStyle HorizontalAlign="Center" />
   <PagerSettings Position=»TopAndBottom»
     FirstPageText=»Go to the first page»
     LastPageText=»Go to the last page»
     Mode=»NextPreviousFirstLast»>
   </PagerSettings>
 </asp:GridView>

Because the list of PagerSettings and PagerStyle properties is so long, all the options are not listed here, but you can find a full list of the options in the Visual Studio Help documents.

The GridView control also offers two events you can use to customize the standard paging behavior of the grid. The PageIndexChanging and PageIndexChanged events are raised before and after the GridView’s current page index changes. The page index changes when the user clicks the pager links in the grid. The PageIndexChanging event parameters allow you to examine the value of the new page index before it changes or even cancels the paging event altogether.

The GridView also includes the EnableSortingAndPagingCallbacks property that allows you to indicate whether the control should use client callbacks to perform sorting and paging. Enabling this property changes the GridView’s paging and sorting behaviors to use client-side AJAX callbacks to retrieve data, rather than a full-page postback.


NOTE If you are interested in learning more about other ways you can integrate AJAX into your ASP.NET applications, Chapter 23 introduces you to the ASP.NET AJAX Framework and explains how you can leverage its capabilities in your applications.

Customizing Columns in the GridView

Frequently the data you need to display in your grid is not simply text data, but data that you want to display using other types of controls or perhaps don’t want to display at all.

If you have your grid configured to automatically generate columns based on the bound data source, the grid creates fields for each public property exposed by the data source. Additionally for all of these properties, except those that return a Boolean type, the grid defaults to using its standard BoundField type, which treats all types as strings. For Boolean types the grid will use the CheckBoxField by default.

These default behaviors might not be optimal in your application. For example you are storing the website address for all of your customers and want the CustomerName column to be displayed as a hyperlink, allowing your users to link directly to a customer’s website. The GridView includes a number of specialized Field types that make displaying things like hyperlinks, check boxes, or buttons in grid columns easy.

You have two ways to configure columns in the GridView — through options in the GridView’s smart tag or by editing the column markup directly in Source view.

Clicking the Edit Columns link in the GridView’s smart tag opens the Fields window, shown in Figure 8-8. From here you can change any existing column’s visibility, header text, the usual style options, and many other properties of the column.

FIGURE 8-8

image

Selecting the Add New Column link from the GridView control’s smart tag displays the Add Field dialog box, shown in Figure 8-9. This dialog box includes options that allow you to add new columns to your grid. Depending on which column field type you select from the drop-down list, the dialog box presents you with the appropriate options for that column type.

FIGURE 8-9

image

The Add Field dialog box lets you select one of the field types described in Table 8-6.

TABLE 8-6

FIELD CONTROL DESCRIPTION
BoundField Displays the value of a field in a data source. This is the default column type of the GridView control.
CheckBoxField Displays a check box for each item in the GridView control. This column field type is commonly used to display fields with a Boolean value.
HyperLinkField Displays the value of a field in a data source as a hyperlink URL. This column field type allows you to bind a second field to the hyperlink’s text.
ButtonField Displays a command button or command link for each item in the GridView control. This allows you to create a column of custom button or link controls, such as an Add or Remove button.
CommandField Represents a field that displays command buttons or links to perform select, edit, insert, or delete operations in a data-bound control.
ImageField Automatically displays an image when the data in the field represents an image or displays an image from the URL provided.
TemplateField Displays user-defined content for each item in the GridView control according to a specified template. This column field type allows you to create a customized column field.

In the example described earlier where you want to allow end users to link to a customer’s website, you want to select the HyperLinkField from the drop-down list. The Add Field dialog box changes and lets you enter in the hyperlink information, including the URL, the data field, and a format string for the column.

You can also modify the grid’s columns in the Source view, manually adding or editing any of the field types listed in the previous table. Listing 8-22 shows how you can add the appropriate markup in Source view to create a HyperLinkField.

LISTING 8-22: Adding a HyperLinkField control to the GridView

<asp:HyperLinkField DataTextField="CompanyName"
  HeaderText="CompanyName" DataNavigateUrlFields="CustomerID,Country"
  SortExpression="CompanyName"
  DataNavigateUrlFormatString=
    http://www.example.com/Customer.aspx?id={0}&country={1} />

When you add a field in Source view you need to make sure you specify a property name from your data source on the field. Each field type exposes a different property (or set of properties) that allows you to define this connection between the field and data source. In the previous example you can see that the HyperLinkField’s DataNavigateUrlFields property allows you to provide a comma-delimited list of data source property names. This allows you to specify multiple data source values to bind to this column. You can then use these fields in your format string to pass two querystring parameters.

Using the TemplateField Column

A key column type available in the GridView control is the TemplateField column. This column type allows you to completely customize the contents of column cells by defining templates.

The TemplateField provides you with six templates that enable you to customize different areas or states of the column, such as the edit state. Table 8-7 describes the available templates.

TABLE 8-7

TEMPLATE NAME DESCRIPTION
ItemTemplate Template used for displaying a TemplateField cell in the data-bound control
AlternatingItemTemplate Template used for displaying an alternate TemplateField cell
EditItemTemplate Template used for displaying a TemplateField cell in edit state
InsertItemTemplate Template used for displaying a TemplateField cell in insert state
HeaderTemplate Template used for displaying the header section of the TemplateField
FooterTemplate Template used for displaying the footer section of the TemplateField

To use the TemplateField in a GridView, add the column type to a grid using the Add Field dialog box as described in the previous section. The <asp:TemplateField> tag serves as a container for the various templates the column can contain. To add content to the templates you can use the template editing features of the Visual Studio 2012 design surface or manually add content directly to the TemplateField element in Source view.

The ItemTemplate controls the default contents of each cell of the column. Listing 8-23 demonstrates how you can use the ItemTemplate to customize the contents of the column.

LISTING 8-23: Using ItemTemplate

<asp:TemplateField HeaderText="CurrentStatus">
  <ItemTemplate>
    <table>
      <tr>
        <td >
          <asp:Button ID="Button2"
             runat="server" Text="Enable" /></td>
        <td >
          <asp:Button ID="Button3"
             runat="server" Text="Disable" /></td>
      </tr>
    </table>
  </ItemTemplate>
</asp:TemplateField>

In the example the ItemTemplate contains a combination of an HTML table and ASP.NET Button controls.

Because the GridView control is data-bound, you can also access the data being bound to the control using data-binding expressions such as the Eval, XPath, or Bind expressions. Listing 8-24 shows how you can add a data-binding expression using the Eval method to set the text field of the Button control. More details about data-binding expressions can be found later in this chapter.

LISTING 8-24: Adding a data-binding expression

<asp:TemplateField HeaderText="CurrentStatus">
  <ItemTemplate>
    <table>
      <tr>
        <td>
          <asp:Button ID="Button2" runat="server"
            Text='<%# "Enable " + Eval("CustomerID") %>' />
        </td>
        <td>
          <asp:Button ID="Button3" runat="server"
            Text='<%# "Disable " + Eval("CustomerID") %>' />
        </td>
      </tr>
    </table>
  </ItemTemplate>
</asp:TemplateField>

Other common templates available in the TemplateField are InsertTemplate and EditTemplate. These templates are used by the grid when a row enters insert or edit mode. Inserting and editing data in the GridView control, including using the InsertItemTemplate and EditItemTemplate, are reviewed in the next section.

Editing GridView Row Data

Users not only want to view the data in their browsers, but they also want to be able to edit the data and save changes back to the data source. When combined with data source controls, the GridView control makes editing data bound to the grid easy. To demonstrate just how easy enabling editing is, you can modify the SqlDataSource and GridView controls used in the previous examples to allow users to edit the customer’s data.

Note that although in this chapter you focus on updating data in a GridView bound to a SqlDataSource control, you can also update data when connecting the GridView to other data source controls. The configuration needed to enable end users to place grid rows into edit mode and insert or delete data via the GridView is identical regardless of the bound data source control. The configuration needed to enable the data source control to persist these changes to the underlying data store is specific to each data source control. If you are using a data source control other than the SqlDataSource, you can refer to the section in this chapter that discusses the details of persisting inserts, updates, and deletes of data for that control.

Configuring the SqlDataSource for Updates

To get started, first modify the SqlDataSource control by adding an UpdateCommand either by using the Configure Data Source wizard or by manually adding the markup in Source view. This property tells the data source control which SQL command it should execute when an update needs to be performed. Listing 8-25 shows the markup needed to add the UpdateCommand property.

LISTING 8-25: Adding an UpdateCommand to a SqlDataSource control

<asp:SqlDataSource ID="SqlDataSource1" Runat="server"
  SelectCommand="SELECT * FROM [Customers]"
  ConnectionString="<%$ ConnectionStrings:ConnectionString %>"
  DataSourceMode="DataSet"
  UpdateCommand="UPDATE [Customers] SET [CompanyName] = @CompanyName,
    [ContactName] = @ContactName, [ContactTitle] = @ContactTitle,
    [Address] = @Address, [City] = @City, [Region] = @Region,
    [PostalCode] = @PostalCode, [Country] = @Country,
    [Phone] = @Phone,[Fax] = @Fax
    WHERE [CustomerID] = @ CustomerID">
</asp:SqlDataSource>

Notice that the UpdateCommand includes a number of placeholders such as @CompanyName, @Country, @Region, and @CustomerID. These placeholders represent the information that will come from the GridView when a row is updated. Each placeholder corresponds to a Parameter element defined in the SqlDataSource control’s UpdateParameters collection. The UpdateParameters collection, shown in Listing 8-26, works much like the SelectParameters element discussed earlier in the chapter.

LISTING 8-26: Adding UpdateParameters to the SqlDataSource Control

<UpdateParameters>
  <asp:Parameter Type="String" Name="CompanyName"></asp:Parameter>
  <asp:Parameter Type="String" Name="ContactName"></asp:Parameter>
  <asp:Parameter Type="String" Name="ContactTitle"></asp:Parameter>
  <asp:Parameter Type="String" Name="Address"></asp:Parameter>
  <asp:Parameter Type="String" Name="City"></asp:Parameter>
  <asp:Parameter Type="String" Name="Region"></asp:Parameter>
  <asp:Parameter Type="String" Name="PostalCode"></asp:Parameter>
  <asp:Parameter Type="String" Name="Country"></asp:Parameter>
  <asp:Parameter Type="String" Name="Phone"></asp:Parameter>
  <asp:Parameter Type="String" Name="Fax"></asp:Parameter>
  <asp:Parameter Type="String" Name="CustomerID"></asp:Parameter>
</UpdateParameters>

Each Parameter uses two properties to create a connection to the underlying data source, Name, which is the database column name, and Type, which is the database column’s data type. In this case, all the parameters are of type String.

Remember that you can also use any of the parameter types mentioned earlier in the chapter, such as the ControlParameter or QueryStringParameter in the UpdateParameters element.

Configuring GridView for Updates

Now that you have configured the SqlDataSource for updates, you need to create a way to place a row in the GridView into edit mode and a way to tell the GridView what the database table’s primary key is.

The GridView includes two built-in ways to place a row into edit mode — the AutoGenerateEditButton property and the CommandField.

When the AutoGenerateEditButton property is set to True, this tells the grid to add a ButtonField column with an edit button for each row. Clicking one of the buttons places the associated row into edit mode. Listing 8-27 shows how to add the AutoGenerateEditButton attribute to the GridView control.

LISTING 8-27: Adding the AutoGenerateEditButton property to a GridView

<asp:GridView ID="GridView1" Runat="server"
  DataSourceID="SqlDataSource1" DataKeyNames="CustomerID"
  AutoGenerateColumns="True" AllowSorting="True" AllowPaging="True"
  AutoGenerateEditButton="true" />

The GridView control also includes AutoGenerateSelectButton and AutoGenerateDeleteButton properties, which allow you to easily add row selection and row deletion capabilities to the grid.

The CommandField column is a special field type that allows you to enable end users to execute different commands on rows in the GridView. Listing 8-28 shows how to configure the CommandField to allow the end users to place a row into edit mode.

LISTING 8-28: Adding edit functionality using a CommandField

<asp:CommandField ShowHeader="True" HeaderText="Command"
  ShowEditButton="True" />

This allows you to display the command as a link, a button, or even an image.

Now if you browse to your web page, you see that a new edit column has been added. Clicking the Edit link allows the user to edit the contents of that particular data row.

The CommandField element also has attributes that allow you to control exactly what is shown in the column. You can dictate whether the column displays commands such as Cancel, Delete, Edit, Insert, or Select.

To complete configuring the grid to allow editing, you need to ensure the grid knows which database table columns are configured as its primary key. You can specify this using the GridView’s DataKeyNames property, which is shown in Listing 8-29.

LISTING 8-29: Adding the DataKeyNames to the GridView control

<asp:GridView ID="GridView1" Runat="server"
  DataSourceID="SqlDataSource1" DataKeyNames="CustomerID"
  AutoGenerateColumns="False" AllowSorting="True" AllowPaging="True">

If the primary key of the table is more than one column, you can specify more than one column name setting using a comma-delimited list.

You can control which columns the grid allows to be edited by adding the ReadOnly property to the columns that you do not want users to edit. Listing 8-30 shows how you can add the ReadOnly property to the ID column.

LISTING 8-30: Adding the ReadOnly property to a BoundField

<asp:BoundField DataField="CustomerID" HeaderText="CustomerID"
  SortExpression="CustomerID" ReadOnly="True" />

Now if you browse to the web page again and click the Edit button, you should see that the ID column is not editable.

Handling Errors When Updating Data

You can check for errors when updating data through the GridView, using the RowUpdated event. Listing 8-31 shows how to check for errors after an attempt to update data.

LISTING 8-31: Checking for Update errors using the RowUpdated event

VB

<script runat="server">
  Protected Sub GridView1_RowUpdated(ByVal sender As Object,
    ByVal e As System.Web.UI.WebControls.GridViewUpdatedEventArgs)
        If e.Exception IsNot Nothing Then
      Me.lblErrorMessage.Text = e.Exception.Message
    End If
  End Sub
</script>

C#

<script runat="server">
  protected void GridView1_RowUpdated(object sender,
                    GridViewUpdatedEventArgs e)
  {
    if (e.Exception != null)
    {
      this.lblErrorMessage.Text = e.Exception.Message;
    }
  }
</script>

The RowUpdated event arguments include an Exception property. The listing checks to see whether this property is null. If not, that indicates an error has occurred, and a message is shown to the end user.

Using the TemplateField’s EditItemTemplate

Earlier in the chapter, you were introduced to TemplateField and some of the templates it includes. One of those templates is EditItemTemplate, which the grid uses when a TemplateField column for a row enters edit mode. Using EditItemTemplate allows you to completely customize the data editing experience of the user. For instance, a better editing experience for the Region column would be to present the possible values as a drop-down list rather than as a simple textbox, which is the default editing experience for the BoundField.

To do this, you simply change the Region column from a BoundField to a TemplateField and add ItemTemplate and EditItemTemplate. In EditItemTemplate, you can add a DropDownList control and provide the proper data-binding information so that the control is bound to a unique list of Regions. Listing 8-32 shows how you can add ItemTemplate and EditItemTemplate to the GridView.

LISTING 8-32: Adding ItemTemplate and EditItemTemplate to the GridView

<asp:TemplateField HeaderText="Country">
  <ItemTemplate><%# Eval("Country") %></ItemTemplate>
  <EditItemTemplate>
    <asp:DropDownList ID="DropDownList1" runat="server"
      DataSourceID="SqlDataSource2"
      DataTextField="Country" DataValueField="Country">
    </asp:DropDownList>
    <asp:SqlDataSource ID="SqlDataSource2" runat="server"
      ConnectionString=
        "<%$ ConnectionStrings:ConnectionString %>"
      SelectCommand="SELECT DISTINCT [Country] FROM [Customers]">
    </asp:SqlDataSource>
  </EditItemTemplate>
</asp:TemplateField>

A simple Eval data-binding expression is used in the ItemTemplate to display the value of the column in the row’s default display mode. In EditItemTemplate, a DropDownList control bound to a SqlDataSource control is included.

To show the currently selected country in the DropDownList control, you use the RowDataBound event. Listing 8-33 shows how this is done.

LISTING 8-33: Using the RowDataBound event to select a DropDownList item

VB

<script runat="server">
  Protected Sub GridView1_RowDataBound(ByVal sender As Object,
    ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs)
        'Check for a row in edit mode.
    If ((e.Row.RowState = DataControlRowState.Edit) Or
      (e.Row.RowState = (DataControlRowState.Alternate Or
      DataControlRowState.Edit))) Then
          Dim drv As System.Data.DataRowView =
        CType(e.Row.DataItem, System.Data.DataRowView)
          Dim ddl As DropDownList =
        CType(e.Row.Cells(8).
          FindControl("DropDownList1"), DropDownList)
      Dim li As ListItem = ddl.Items.
        FindByValue(drv("Country").ToString())
      li.Selected = True
    End If
  End Sub
</script>

C#

<script runat="server">
  protected void GridView1_RowDataBound(object sender,
                     GridViewRowEventArgs e)
  {
    // Check for a row in edit mode.
    if ( (e.Row.RowState == DataControlRowState.Edit) ||
       (e.Row.RowState == (DataControlRowState.Alternate |
                DataControlRowState.Edit)) )
    {
      System.Data.DataRowView drv =
        (System.Data.DataRowView)e.Row.DataItem;
          DropDownList ddl =
        (DropDownList)e.Row.Cells[8].
          FindControl("DropDownList1");
      ListItem li =
         ddl.Items.FindByValue(drv["Country"].ToString());
      li.Selected = true;
    }
  }
</script>

To set the DropDownList value, first check that the currently bound GridViewRow is in edit mode by using the RowState property. The RowState property is a bitwise combination of DataControlRowState values. Table 8-8 shows you the possible states for a GridViewRow.

TABLE 8-8

ROWSTATE DESCRIPTION
Alternate Indicates that this row is an alternate row
Edit Indicates the row is currently in edit mode
Insert Indicates the row is a new row, and is currently in insert mode
Normal Indicates the row is currently in a normal state
Selected Indicates the row is currently the selected row in the GridView

To determine the current RowState correctly, you may need to make multiple comparisons against the RowState property. The RowState can be in multiple states at once — for example, alternate and edit — therefore, you need to use a bitwise comparison to properly determine whether the GridViewRow is in an edit state.

After the row is determined to be in an edit state, locate the DropDownList control in the proper cell by using the FindControl method. This method allows you to locate a server control by name. After you find the DropDownList control, locate the appropriate DropDownList ListItem and set its Selected property to True.

You also need to use a GridView event to add the value of the DropDownList control back into the GridView after the user updates the row. For this, you can use the RowUpdating event as shown in Listing 8-34.

LISTING 8-34: Using the RowUpdating event

VB

<script runat="server">
  Protected Sub GridView1_RowUpdating(ByVal sender As Object,
    ByVal e As System.Web.UI.WebControls.GridViewUpdateEventArgs)
        Dim gvr As GridViewRow =
      Me.GridView1.Rows(Me.GridView1.EditIndex)
    Dim ddl As DropDownList =
      CType(gvr.Cells(8).
        FindControl("DropDownList1"), DropDownList)
    e.NewValues("Country") = ddl.SelectedValue
  End Sub
</script>

C#

<script runat="server">
  protected void GridView1_RowUpdating(object sender,
                     GridViewUpdateEventArgs e)
  {
    GridViewRow gvr =
      this.GridView1.Rows[this.GridView1.EditIndex];
    DropDownList ddl =
      (DropDownList)gvr.Cells[8].FindControl("DropDownList1");
    e.NewValues["Country"] = ddl.SelectedValue;
  }
</script>

In this event, you determine the GridViewRow that is currently being edited using the EditIndex. This property contains the index of the GridViewRow that is currently in an edit state. After you find the row, locate the DropDownList control in the proper row cell using the FindControl method, as in the previous listing. After you find the DropDownList control, simply add the SelectedValue of that control to the GridView control’s NewValues collection.

Deleting GridView Data

Deleting data from the table produced by the GridView is even easier than editing data. Just a few additions to the SqlDataSource and GridView enable you to delete an entire row of data from the table.

Like the Edit buttons, you can add a Delete button to the grid by setting the AutoGenerateDeleteButton property to True or by using the CommandField. Using the AutoGenerateDeleteButton property is shown in Listing 8-35.

LISTING 8-35: Adding a delete link to the GridView

<asp:GridView ID="GridView2" Runat="server"
   DataSourceID="SqlDataSource1" DataKeyNames="CustomerID"
   AutoGenerateColumns="True" AllowSorting="True" AllowPaging="True"
   AutoGenerateEditButton="true" AutoGenerateDeleteButton="true"/>

The SqlDataSource control changes are also trivial and can be made using the Configure Data Source wizard or manually in markup. Listing 8-36 shows how you can add the DeleteCommand to the control.

LISTING 8-36: Adding delete functionality to the SqlDataSource control

<asp:SqlDataSource ID="SqlDataSource1" Runat="server"
   SelectCommand="SELECT * FROM [Customers]"
   ConnectionString="<%$ ConnectionStrings:ConnectionString %>"
   DataSourceMode="DataSet"
   DeleteCommand="DELETE From Customers
          WHERE (CustomerID = @CustomerID)"
   UpdateCommand="UPDATE [Customers]
     SET [CompanyName] = @CompanyName,
     [ContactName] = @ContactName, [ContactTitle] = @ContactTitle,
     [Address] = @Address, [City] = @City, [Region] = @Region,
     [PostalCode] = @PostalCode, [Country] = @Country,
     [Phone] = @Phone, [Fax] = @Fax
     WHERE [CustomerID] = @original_CustomerID">
  <%-- Update parameters removed for clarity --%>
</asp:SqlDataSource>

Just like the UpdateCommand property, the DeleteCommand property makes use of named parameters to determine which row should be deleted. Because of this, you define this parameter from within the SqlDataSource control. To do this, add a <DeleteParameters> section to the SqlDataSource control. This is shown in Listing 8-37.

LISTING 8-37: Adding a <DeleteParameters> section to the SqlDataSource control

<DeleteParameters>
  <asp:Parameter Name="CustomerID" Type="String">
  </asp:Parameter>
</DeleteParameters>

This is the only parameter needed for the <DeleteParameters> collection because the SQL command for this deletion requires only the CustomerID from the row to delete the entire row. Running the example displays a Delete link in the grid, which when clicked deletes the selected row.

Remember that just like when you update data, checking for database errors when you delete data is a good idea. Listing 8-38 shows how you can use the GridView’s RowDeleted event and the SqlDataSource’s Deleted event to check for errors that might have occurred during the deletion.

LISTING 8-38: Using the RowDeleted event to catch SQL errors

VB

<script runat="server">
  Protected Sub GridView1_RowDeleted(ByVal sender As Object,
    ByVal e As GridViewDeletedEventArgs)
        If (Not IsDBNull (e.Exception)) Then
      Me.lblErrorMessage.Text = e.Exception.Message
      e.ExceptionHandled = True
    End If
  End Sub
      Protected Sub SqlDataSource1_Deleted(ByVal sender As Object,
  ByVal e As System.Web.UI.WebControls.SqlDataSourceStatusEventArgs)
        If (e.Exception IsNot Nothing) Then
      Me.lblErrorMessage.Text = e.Exception.Message
      e.ExceptionHandled = True
    End If
  End Sub
</script>

C#

<script runat="server">
  protected void GridView1_RowDeleted(object sender,
                    GridViewDeletedEventArgs e)
  {
    if (e.Exception != null)
    {
      this.lblErrorMessage.Text = e.Exception.Message;
      e.ExceptionHandled = true;
    }
  }
      protected void SqlDataSource1_Deleted(object sender,
                    SqlDataSourceStatusEventArgs e)
  {
    if (e.Exception != null)
    {
      this.lblErrorMessage.Text = e.Exception.Message;
      e.ExceptionHandled = true;
    }
  }
</script>

Notice that both events provide Exception properties as part of their event arguments. If these properties are not empty, an exception has occurred that you can handle. If you do choose to handle the exception, you should set the ExceptionHandled property to True; otherwise, the exception will continue to bubble up to the end user.

DetailsView

The DetailsView control is a data-bound control that enables you to work with a single data record at a time. Although the GridView control is excellent for viewing a collection of data, there are many scenarios where you want to show a single record rather than an entire collection. The DetailsView control allows you to do this and provides many of the same data-manipulation and display capabilities as the GridView, including features such as paging, updating, inserting, and deleting data.

To start using the DetailsView, drag the control onto the design surface. Like the GridView, you can use the DetailsView’s smart tag to create and set the data source for the control. The examples in this section use the same SqlDataSource control that was used for the GridView examples in the previous section. Set the SqlDataSource as the DetailsView’s data source and run the page. Listing 8-39 shows the markup for a DetailsView control bound to a SqlDataSource control.

LISTING 8-39: A DetailsView control bound to a SqlDataSource

    <asp:DetailsView ID="DetailsView1" Runat="server"
       DataSourceID="SqlDataSource1" DataKeyNames="CustomerID"
       AutoGenerateRows="True"></asp:DetailsView>
        <asp:SqlDataSource ID="SqlDataSource1" Runat="server"
      SelectCommand="SELECT * FROM [Customers]"
      ConnectionString=
        "<%$ ConnectionStrings:ConnectionString %>"
      DataSourceMode="DataSet">
    </asp:SqlDataSource>

If you simply want to display a single record, you would probably want to change the SqlDataSource control’s SelectCommand so that it returns only one customer, rather than returning all customers as the query does. However, if you are returning more than a single object from the database, you can allow your end users to page through the data by setting the DetailsView’s AllowPaging property to True, as shown in Listing 8-40.

LISTING 8-40: Enabling paging on the DetailsView control

<asp:DetailsView ID="DetailsView1" Runat="server"
   DataSourceID="SqlDataSource1" DataKeyNames="CustomerID"
   AutoGenerateRows="True" AllowPaging="true"></asp:DetailsView>

You can select the Enable Paging check box in the DetailsView smart tag or add the property to the control in Source view. Also, like the GridView, the DetailsView control enables you to customize the control’s pager using the PagerSettings-Mode, as well as the Pager style.

Customizing the DetailsView Display

You can customize the appearance of the DetailsView control by choosing which fields the control displays. By default, the control displays each public property from its bound data source. However, using the same basic syntax used for the GridView control, you can specify that only certain properties be displayed. This is illustrated in Listing 8-41.

LISTING 8-41: Customizing the display of the DetailsView control

<asp:DetailsView ID="DetailsView1" Runat="server"
   DataSourceID="SqlDataSource1" DataKeyNames="CustomerID"
   AutoGenerateRows="False">
   <Fields>
     <asp:BoundField ReadOnly="True" HeaderText="CustomerID"
        DataField="CustomerID" SortExpression="CustomerID"
        Visible="False" />
     <asp:BoundField ReadOnly="True" HeaderText="CompanyName"
        DataField="CompanyName" SortExpression="CompanyName" />
     <asp:BoundField HeaderText="ContactName"
        DataField="ContactName" SortExpression="ContactName" />
     <asp:BoundField HeaderText="ContactTitle"
        DataField="ContactTitle"
        SortExpression="ContactTitle" />
   </Fields>
</asp:DetailsView>

In this example, only four fields from the Customers table are defined using BoundField objects in the DetailsView’s Fields collection.

Using the DetailsView and GridView Together

Next, this section looks at a common master/detail display scenario, which uses both the GridView and the DetailsView. In this example, you use the GridView to display a master view of the data and the DetailsView to show the details of the selected GridView row. The Customers table is the data source. Listing 8-42 shows the code needed for this.

LISTING 8-42: Using the GridView and DetailsView together

<html>
<head id="Head1" runat="server">
  <title>GridView & DetailsView Controls</title>
</head>
<body>
  <form id="form1" runat="server">
    <p>
      <asp:GridView ID="GridView1" runat="server"
         DataSourceId="SqlDataSource1"
         DataKeyNames="CustomerID"
         AutoGenerateSelectButton="True" AllowPaging="True"
         AutoGenerateColumns="True" PageSize="5">
        <SelectedRowStyle ForeColor="White" BackColor="#738A9C"
           Font-Bold="True" />
      </asp:GridView>
    </p>
    <p><b>Customer Details:</b></p>
    <asp:DetailsView ID="DetailsView1" runat="server"
       DataSourceId="SqlDataSource2"
       AutoGenerateRows="True" DataKeyNames="CustomerID">
    </asp:DetailsView>
        <asp:SqlDataSource ID="SqlDataSource1" runat="server"
       SelectCommand="SELECT * FROM [Customers]"
       ConnectionString=
         "<%$ ConnectionStrings:ConnectionString %>" />
        <asp:SqlDataSource ID="SqlDataSource2" runat="server"
       SelectCommand="SELECT * FROM [Customers]"
       FilterExpression="CustomerID='{0}'"
       ConnectionString=
         "<%$ ConnectionStrings:ConnectionString %>">
       <FilterParameters>
         <asp:ControlParameter Name="CustomerID"
           ControlId="GridView1"
           PropertyName="SelectedValue" />
       </FilterParameters>
    </asp:SqlDataSource>
  </form>
</body>
</html>

To see how this works, look at the changes that were made to the second SqlDataSource control, named SqlDataSource2. A FilterExpression used to filter the data retrieved by the SelectCommand has been added. In this case, the value of the FilterExpression is set to CustomerID='{0}' indicating that the control should filter the data it returns by the CustomerID value given to it.

The parameter specified in the FilterExpression, CustomerID, is defined in the SqlDataSource control’s <FilterParameters> collection. The example uses an <asp:ControlParameter> to specify the GridView control’s SelectedValue property to populate the parameter’s value.

SelectParameters versus FilterParameters

You might have noticed in the previous example that the FilterParameters seem to provide the same functionality as the SelectParameters, which were discussed in the “SqlDataSource Control” section of this chapter. Although both produce essentially the same result, they use very different methods. As you saw in the previous section, using the SelectParameters allows the developer to inject values into a WHERE clause specified in the SelectCommand. This limits the rows that are returned from the SQL Server and held in memory by the data source control. The advantage is that by limiting the amount of data returned from SQL, you can make your application faster and reduce the amount of memory it consumes. The disadvantage is that you are confined to working with the limited subset of data returned by the SQL query.

FilterParameters, on the other hand, do not use a WHERE, instead requiring all the data to be returned from the server and then applying a filter to the data source control’s in-memory data. The disadvantage of the filter method is that more data has to be returned from the data store. However, in some cases such as when you are performing many filters of one large chunk of data (for instance, to enable paging in DetailsView) this is an advantage as you do not have to call out to your data store each time you need the next record. All the data is stored in cache memory by the data source control.

Inserting, Updating, and Deleting Data Using DetailsView

Inserting data using the DetailsView is similar to inserting data using the GridView control. To insert data using the DetailsView, simply add the AutoGenerateInsertButton property to the DetailsView control as shown in Listing 8-43.

LISTING 8-43: Adding an AutoGenerateInsertButton property to the DetailsView

<asp:DetailsView ID="DetailsView1" runat="server"
   DataSourceId="SqlDataSource1" DataKeyNames="CustomerID"
   AutoGenerateRows="True" AutoGenerateInsertButton="True" />

Then add the InsertCommand and corresponding InsertParameters elements to the SqlDataSource control, as shown in Listing 8-44.

LISTING 8-44: Adding an InsertCommand to the SqlDataSource control

<asp:SqlDataSource ID="SqlDataSource1" runat="server"
  SelectCommand="SELECT * FROM [Customers]"
  InsertCommand="INSERT INTO [Customers] ([CustomerID],
    [CompanyName], [ContactName], [ContactTitle], [Address],
    [City], [Region], [PostalCode], [Country], [Phone], [Fax])
    VALUES (@CustomerID, @CompanyName, @ContactName, @ContactTitle,
    @Address, @City, @Region, @PostalCode,@Country, @Phone, @Fax)"
  DeleteCommand="DELETE FROM [Customers]
          WHERE [CustomerID] = @original_CustomerID"
  ConnectionString="<%$ ConnectionStrings:ConnectionString %>">
  <InsertParameters>
    <asp:Parameter Type="String" Name="CustomerID"></asp:Parameter>
    <asp:Parameter Type="String"
            Name="CompanyName"></asp:Parameter>
    <asp:Parameter Type="String"
            Name="ContactName"></asp:Parameter>
    <asp:Parameter Type="String"
            Name="ContactTitle"></asp:Parameter>
    <asp:Parameter Type="String" Name="Address"></asp:Parameter>
    <asp:Parameter Type="String" Name="City"></asp:Parameter>
    <asp:Parameter Type="String" Name="Region"></asp:Parameter>
    <asp:Parameter Type="String" Name="PostalCode"></asp:Parameter>
    <asp:Parameter Type="String" Name="Country"></asp:Parameter>
    <asp:Parameter Type="String" Name="Phone"></asp:Parameter>
    <asp:Parameter Type="String" Name="Fax"></asp:Parameter>
  </InsertParameters>
</asp:SqlDataSource>

Updating and deleting data using the DetailsView control are similar to updating and deleting data using the GridView. Simply specify the UpdateCommand or DeleteCommand attributes in the DetailsView control; then provide the proper UpdateParameters and DeleteParameters elements.

ListView

ASP.NET includes another list-style control that bridges the gap between the highly structured GridView control, and the anything goes, unstructured controls like DataList and Repeater.

In the past, many developers who wanted a grid-style data control chose the GridView because it was easy to use and offered powerful features such as data editing, paging, and sorting. Unfortunately, the more developers dug into the control, the more they found that controlling the way it rendered its HTML output was exceedingly difficult. This was problematic if you wanted to lighten the amount of markup generated by the control, or use CSS exclusively to control the control’s layout and style.

On the other side of the coin, many developers were drawn to DataList or Repeater because of the enhanced control they achieved over rendering. These controls contained little to no notion of layout and allowed developers total freedom in laying out their data. Unfortunately, these controls lacked some of the basic features of the GridView, such as paging and sorting, or in the case of the Repeater, any notion of data editing.

This is where the ListView can be useful. The control itself emits no runtime generated HTML markup; instead it relies on a series of 11 control templates that represent the different areas of the control and the possible states of those areas. Within these templates you can place markup autogenerated by the control at design time, or markup created by the developer, but in either case the developer retains complete control over not only the markup for individual data items in the control, but also of the markup for the layout of the entire control. Additionally, because the control readily understands and handles data editing and paging, you can let the control do much of the data-management work, allowing you to focus primarily on data display.

Getting Started with the ListView

To get started using the ListView, simply drop the control on the design surface and assign a data source to it just as you would any other data-bound list control. After you assign the data source, however, you will see that no design-time layout preview is available as you might expect. This is because, by default, the ListView has no layout defined and it is completely up to you to define the control’s layout. In fact, the design-time rendering of the control even tells you that you need to define at least an ItemTemplate and LayoutTemplate to use the control. The LayoutTemplate serves as the root template for the control, and the ItemTemplate serves as the template for each data item in the control.

You have two options for defining the templates needed by the ListView. You can either edit the templates directly by changing the Current View option in the ListView smart tag, or you can select a predefined layout from the control’s smart tag. Changing Current View allows you to see a runtime view of each of the available templates, and edit the contents of those templates directly just as you normally edit any other control template. Figure 8-10 shows the Current View drop-down in the ListView’s smart tag.

FIGURE 8-10

image

The second option, and probably the easier to start with, is to choose a predefined layout template from the Configure ListView dialog box. To open this dialog box, simply click the ConfigureListView option from the smart tag. You are presented with a dialog box that lets you select between several predefined layouts, select different style options, and even configure basic behavior options such as editing and paging.

The control includes five layout types — Grid, Tiled, Bulleted List, Flow, and Single Row — and four different style options. A preview of each type is presented in the dialog box, and as you change the currently selected layout and style, the preview is updated.

ListView Templates

After you have applied a layout template to the ListView, if you look at the Source window in Visual Studio, you can see that to provide the layout the control generated a significant chunk of markup. This markup is generated based on the layout that you chose in the Configure ListView dialog box.

If you closely examine the markup that has been generated for the Grid layout used in the previous section, you will see that, by default, the control creates markup for seven control templates: the ItemTemplate, AlternatingItemTemplate, SelectedItemTemplate, InsertItemTemplate, EditItemTemplate, EmptyDataTemplate, and LayoutTemplate. These are just some of the 11 templates that the control exposes, and that you can use to provide markup for the different states of the control. Choosing a different predefined layout option results in the control generating a different collection of templates. Of course, you can also always manually add or remove any of the templates yourself. All 11 templates are listed in Table 8-9.

TABLE 8-9

TEMPLATE NAME DESCRIPTION
ItemTemplate Provides a user interface (UI) for each data item in the control
AlternatingItemTemplate Provides a unique UI for alternating data items in the control
SelectedItemTemplate Provides a unique UI for the currently selected data item
InsertItemTemplate Provides a UI for inserting a new data item into the control
EditItemTemplate Provides a UI for editing an existing data item in the control
EmptyItemTemplate Provides a unique UI for rows created when there is no more data to display in the last group of the current page
EmptyDataTemplate The template shown when the bound data object contains no data items
LayoutTemplate The template that serves as the root container for the ListView control and is used to control the overall layout of the data items
GroupSeparatorTemplate Provides a separator UI between groups
GroupTemplate Provides a unique UI for grouped content
ItemSeparatorTemplate Provides a separator UI between each data item

The use of templates allows the ListView control to retain a very basic level of information about the markup sections and states which can comprise the ListView, while still being able to give you almost total control over the UI of the ListView.

ListView Data Item Rendering

Although the ListView is generally very flexible, allowing you almost complete control over the way it displays its bound data, it does have some basic structure that defines how the templates described in the previous section are related. As described previously, at a minimum, the control requires you to define two templates, the LayoutTemplate and ItemTemplate. The LayoutTemplate is the root control template and therefore is where you should define the overall layout for the collection of data items in the ListView.

For example, if you examine the template markup generated by the Grid layout, you can see the LayoutTemplate includes a <table> element definition, a single table row (<tr>) definition, and a <td> element defined for each column header.

The ItemTemplate, on the other hand, is where you define the layout for an individual data item. If you again look at the markup generated for the Grid layout, its ItemTemplate is a single table row (<tr>) element followed by a series of table cell (<td>) elements that contain the actual data.

When the ListView renders itself, it knows that the ItemTemplate should be rendered within the Layout Template, but what is needed is a mechanism to tell the control exactly where within the LayoutTemplate to place the ItemTemplate. The ListView control does this by looking within the LayoutTemplate for an item container. The item container is an HTML container element with the runat = "server" attribute set and an id attribute whose value is itemContainer. The element can be any valid HTML container element, although if you examine the default Grid LayoutTemplate you will see that it uses the <tbody> element.

<tbody id="itemContainer">
</tbody>

Adding to the overall flexibility of the control, even the specific itemContainer element id that ListView looks for can be configured. Although by default the control will attempt to locate an element whose id attribute is set to itemContainer, you can change the id value the control will look for by changing the control’s ItemContainerID property.

If the control fails to locate an appropriate HTML element designated as the item container, it will throw an exception.

The ListView uses the element identified as the itemContainer to position not only the ItemTemplate, but also any item-level template, such as the AlternativeItemTemplate, EditItemTemplate, EmptyItemTemplate, InsertItemTemplate, ItemSeparatorTemplate, and SelectedItemTemplate. During rendering, it simply places the appropriate item template into the item container, depending on the state of the data item (selected, editing, or alternate) for each data item it is bound to.

ListView Group Rendering

In addition to the item container, the ListView also supports another container type, the group container. The group container works in conjunction with the GroupTemplate to allow you to divide a large group of data items into smaller sets. The number of items in each group is set by the control’s GroupItemCount property. This is useful when you want to output some additional HTML after some number of item templates has been rendered. When using GroupTemplate, the same problem exists as was discussed in the prior section. In this case, however, rather than having two templates to relate, introducing the GroupTemplate means you have three templates to relate: the ItemTemplate to the GroupTemplate, and the GroupTemplate to the LayoutTemplate.

When the ListView renders itself, it looks to see whether a GroupTemplate has been defined. If the control finds a GroupTemplate, it checks to see whether a group container is provided in the LayoutTemplate. If you have defined the GroupTemplate, the control requires that you define a group container; otherwise it throws an exception. The group container works the same way as the item container described in the previous section, except that the container element’s id value should be groupContainer, rather than itemContainer. As with an item container, the specific id value that the control looks for can be changed by altering the GroupContainerID property of the control.

You can see an example of the group container being used by looking at the markup generated by the ListView’s Tiled layout. The LayoutTemplate of this layout shows a table serving as the group container, shown here:

<table id="groupContainer" runat="server" border="0" style="">
</table>

After a groupContainer is defined, you need to define an item container, but rather than doing this in the LayoutTemplate, you need to do it in the GroupTemplate. Again, looking at the Tiled layout, you can see that within its GroupTemplate, it defined a table row that serves as the item container.

<tr id="itemContainer" runat="server">
</tr>

Item count has been reached, so the ListView outputs the GroupTemplate, and then the ItemTemplate again, repeating this process for each data item it is bound to.

Using the EmptyItemTemplate

When using the GroupTemplate, it is also important to keep in mind that the number of data items bound to the ListView control may not be perfectly divisible by the GroupItemCount value. This is especially important to keep in mind if you have created a ListView layout that is dependent on HTML tables for its data item arrangement because there is a chance that the last row may end up defining fewer table cells than previous table rows, making the HTML output by the control invalid, and possibly causing rendering problems. To solve this, the ListView control includes the EmptyItemTemplate. This template is rendered if you are using the GroupTemplate and there are not enough data items remaining to reach the GroupItemCount value.

For example, if the data source bound to the ListView control contains four data items, but the GroupItemCount for the control is set to 3, there will be three ItemTemplates rendered in each group. This means for the second group rendered, there will only be a single data item remaining to render; therefore, the control will use the EmptyItemTemplate, if defined, to fill the remaining items.

ListView Data Binding and Commands

Because the ListView does not generate any layout markup at run time and does not include any of the auto field generation logic as you may be used to in the GridView, each template uses the standard ASP.NET inline data-binding syntax to position the values of each data item in the defined layout. The inline data-binding syntax is covered in detail later in this chapter.

You can see an example of inline binding by examining the ItemTemplate of the default Grid layout created by the control. In this template, each column of the bound data source is displayed using an ASP.NET label whose text property is set to a data-binding evaluation expression:

<asp:Label ID="ProductNameLabel" runat="server"
  Text='<%# Eval("ProductName") %>' />

Because the control uses this flexible model to display the bound data, you can leverage it to place the data wherever you want within the template, and even use the features of ASP.NET data binding to manipulate the bound data before it is displayed.

Every ListView template that displays bound data uses the same ASP.NET binding syntax, and simply provides a different template around it. For example, if you enable editing in the Grid layout you will see that the EditItemTemplate simply replaces the ASP.NET label used by the ItemTemplate with a textbox or check box, depending on the underlying data type.

<asp:TextBox ID="ProductNameTextBox" runat="server"
  Text='<%# Bind("ProductName") %>' />

Again, this flexibility allows you to choose exactly how you want to allow your end user to edit the data (if you want it to be editable). Instead of a standard ASP.NET textbox, you could easily replace this with a drop-down list, or even a third-party editing control.

To get the ListView to show the EditItemTemplate for a data item, the control uses the same commands concept found in the GridView control. The ItemTemplate provides three commands (see Table 8-10) you can use to change the state of a data item.

TABLE 8-10

COMMAND NAME DESCRIPTION
Edit Places the specific data item into edit mode and shows the EditTemplate for the data item
Delete Deletes the specific data item from the underlying data source
Select Sets the ListView control’s selected index to the index of the specific data item

These commands are used in conjunction with the ASP.NET Button control’s CommandName property. You can see these commands used in the ItemTemplate of the ListView’s default Grid layout by enabling editing and deleting using the ListView configuration dialog box. Doing this generates a new column with an Edit and Delete button, each of which specified the CommandName property set to Edit and Delete, respectively.

<asp:Button ID="DeleteButton" runat="server"
  CommandName="Delete" Text="Delete" />
<asp:Button ID="EditButton" runat="server"
  CommandName="Edit" Text="Edit" />

Other templates in the ListView offer other commands, as shown in Table 8-11.

TABLE 8-11

TEMPLATE COMMAND NAME DESCRIPTION
EditItemTemplate Update Updates the data in the ListView’s data source and returns the data item to the ItemTemplate display
EditItemTemplate Cancel Cancels the edit and returns the data item to the ItemTemplate
InsertItemTemplate Insert Inserts the data into the ListView’s data source
InsertItemTemplate Cancel Cancels the insert and resets the InsertTemplate controls binding values

ListView Paging and the Pager Control

ASP.NET includes another control called DataPager that the ListView uses to provide paging capabilities. The DataPager control is designed to display the navigation for paging to the end user and to coordinate data paging with any data-bound control that implements the IPagableItemContainer interface, which in ASP.NET is the ListView control. In fact, you will notice that if you enable paging on the ListView control by checking the Paging check box in the ListView configuration dialog box, the control simply inserts a new DataPager control into its LayoutTemplate. The default paging markup generated by the ListView for the Grid layout is shown here:

<asp:datapager ID="DataPager1" runat="server">
  <Fields>
    <asp:nextpreviouspagerfield ButtonType="Button" FirstPageText="First"
      LastPageText="Last" NextPageText="Next" PreviousPageText="Previous"
      ShowFirstPageButton="True" ShowLastPageButton="True" />
  </Fields>
</asp:datapager>

The markup for the control shows that within the DataPager, a Fields collection has been created, which contains a NextPreviousPagerField object. As its name implies, using the NextPreviousPager object results in the DataPager rendering Next and Previous buttons as its user interface. The DataPager control includes three types of Field objects: the NextPreviousPagerField; the NumericPagerField object, which generates a simple numeric page list; and the TemplatePagerField, which allows you to specify your own custom paging user interface. Each of these Field types includes a variety of properties that you can use to control exactly how the DataPager displays the user interface. Additionally, because the DataPager exposes a Fields collection rather than a simple Field property, you can display several Field objects within a single DataPager control.

The TemplatePagerField is a unique type of Field object that contains no user interface itself, but simply exposes a template that you can use to completely customize the pager’s user interface. Listing 8-45 demonstrates the use of the TemplatePagerField.

LISTING 8-45: Creating a custom DataPager user interface

<asp:DataPager ID="DataPager1" runat="server">
  <Fields>
    <asp:TemplatePagerField>
      <PagerTemplate>
        Page
        <asp:Label ID="Label1" runat="server"
          Text=
          "<%# (Container.StartRowIndex/Container.PageSize)+1%>" />
        of
        <asp:Label ID="Label2" runat="server"
          Text=
          "<%# Container.TotalRowCount/Container.PageSize%>" />
      </PagerTemplate>
    </asp:TemplatePagerField>
  </Fields>
</asp:DataPager>

Notice that the example uses ASP.NET data binding to provide the total page count, page size, and the row that the page should start on; these are values exposed by the DataPager control.

If you want to use custom navigation controls in the PagerTemplate, such as a Button control to change the currently displayed page, you would create a standard Click event handler for the button. Within that event handler, you can access the DataPager’s StartRowIndex, TotalRowCount, and PageSize properties to calculate the new StartRowIndex the ListView should use when it renders.

Unlike the paging provided by the GridView, DataPager, because it is a separate control, gives you total freedom over where to place it on your web page. The examples you have seen so far have all looked at the DataPager control when it is placed directly in a ListView, but the control can be placed anywhere on the web form. In Listing 8-46, the only significant change you should notice is the use of the PagedControlID property.

LISTING 8-46: Placing the DataPager control outside of the ListView

<asp:DataPager ID="DataPager1" runat="server"
   PagedControlID="ListView1">
  <Fields>
    <asp:NumericPagerField />
  </Fields>
</asp:DataPager>

The PageControlID property allows you to specify explicitly which control this pager should work with.

FormView

The FormView control functions like the DetailsView control in that it displays a single data item from a bound data source control and allows adding, editing, and deleting data. What makes it unique is that it displays the data in custom templates, which gives you much greater control over how the data is displayed and edited. The FormView control also contains an EditItemTemplate and InsertItemTemplate that allows you to determine how the control displays when entering edit or insert mode.Listing 8-47 shows the code that Visual Studio generates when designing the FormView control’s customized ItemTemplate.

LISTING 8-47: Using a FormView control to display and edit data

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
  <title>Using the FormView control</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:FormView ID="FormView1" Runat="server"
       DataSourceID="SqlDataSource1"
       DataKeyNames="CustomerID" AllowPaging="True">
      <EditItemTemplate>
        CustomerID:
        <asp:Label Text='<%# Eval("CustomerID") %>'
           Runat="server" ID="CustomerIDLabel1">
        </asp:Label><br />
        CompanyName:
        <asp:TextBox Text='<%# Bind("CompanyName") %>'
           Runat="server"
           ID="CompanyNameTextBox"></asp:TextBox><br />
        ContactName:
        <asp:TextBox Text='<%# Bind("ContactName") %>'
           Runat="server"
           ID="ContactNameTextBox"></asp:TextBox><br />
        ContactTitle:
        <asp:TextBox Text='<%# Bind("ContactTitle") %>'
          Runat="server"
          ID="ContactTitleTextBox"></asp:TextBox><br />
        Address:
        <asp:TextBox Text='<%# Bind("Address") %>'
           Runat="server"
           ID="AddressTextBox"></asp:TextBox><br />
        City:
        <asp:TextBox Text='<%# Bind("City") %>' Runat="server"
           ID="CityTextBox"></asp:TextBox><br />
        Region:
        <asp:TextBox Text='<%# Bind("Region") %>'
           Runat="server"
           ID="RegionTextBox"></asp:TextBox><br />
        PostalCode:
        <asp:TextBox Text='<%# Bind("PostalCode") %>'
           Runat="server"
           ID="PostalCodeTextBox"></asp:TextBox><br />
        Country:
        <asp:TextBox Text='<%# Bind("Country") %>'
           Runat="server"
           ID="CountryTextBox"></asp:TextBox><br />
        Phone:
        <asp:TextBox Text='<%# Bind("Phone") %>' Runat="server"
           ID="PhoneTextBox"></asp:TextBox><br />
        Fax:
        <asp:TextBox Text='<%# Bind("Fax") %>' Runat="server"
           ID="FaxTextBox"></asp:TextBox><br />
        <br />
        <asp:Button ID="Button2" Runat="server" Text="Button"
           CommandName="update" />
        <asp:Button ID="Button3" Runat="server" Text="Button"
           CommandName="cancel" />
      </EditItemTemplate>
      <ItemTemplate>
        <table width="100%">
          <tr>
            <td style="width: 439px">
            <b>
            <span style="font-size: 14pt">
              Customer Information</span>
            </b>
            </td>
            <td style="width: 439px" align="right">
              CustomerID:
              <asp:Label ID="CustomerIDLabel"
                 Runat="server"
                 Text='<%# Bind("CustomerID") %>'>
                 </asp:Label></td>
          </tr>
          <tr>
            <td colspan="2">
              CompanyName:
              <asp:Label ID="CompanyNameLabel"
                 Runat="server"
                 Text='<%# Bind("CompanyName") %>'>
                 </asp:Label><br />
              ContactName:
              <asp:Label ID="ContactNameLabel"
                 Runat="server"
                 Text='<%# Bind("ContactName") %>'>
                 </asp:Label><br />
              ContactTitle:
              <asp:Label ID="ContactTitleLabel"
                 Runat="server"
                 Text='<%# Bind("ContactTitle") %>'>
                 </asp:Label><br />
              <br />
              <table width="100%"><tr>
                <td colspan="3">
                  <asp:Label ID="AddressLabel"
                     Runat="server"
                     Text='<%# Bind("Address") %>'>
                       </asp:Label></td>
                </tr>
                <tr>
                <td style="width: 100px">
                  <asp:Label ID="CityLabel"
                     Runat="server"
                     Text='<%# Bind("City") %>'>
                       </asp:Label></td>
                <td style="width: 100px">
                  <asp:Label ID="RegionLabel"
                     Runat="server"
                     Text='<%# Bind("Region") %>'>
                       </asp:Label></td>
                <td style="width: 100px">
                  <asp:Label ID="PostalCodeLabel"
                     Runat="server"
                   Text='<%# Bind("PostalCode") %>'>
                      </asp:Label>
                  </td>
                </tr>
                <tr>
                <td style="width: 100px" valign="top">
                  <asp:Label ID="CountryLabel"
                     Runat="server"
                     Text='<%# Bind("Country") %>'>
                       </asp:Label></td>
                <td style="width: 100px"></td>
                <td style="width: 100px">Phone:
                  <asp:Label ID="PhoneLabel"
                     Runat="server"
                     Text='<%# Bind("Phone") %>'>
                          </asp:Label><br />
                    Fax:
                  <asp:Label ID="FaxLabel"
                     Runat="server"
                     Text='<%# Bind("Fax") %>'>
                       </asp:Label><br />
                  </td>
                </tr></table>
              <asp:Button ID="Button1" Runat="server"
                 Text="Button" CommandName="edit" />
            </td>
          </tr></table>
      </ItemTemplate>
    </asp:FormView>
    <asp:SqlDataSource ID="SqlDataSource1" Runat="server"
       SelectCommand="SELECT * FROM [Customers]"
       ConnectionString=
         "<%$ ConnectionStrings:ConnectionString %>">
        </asp:SqlDataSource>
      </div>
  </form>
</body>
</html>

OTHER DATA-BOUND CONTROLS

ASP.NET contains a variety of other simple controls that can be bound to data sources. This section looks at three of the most popular and useful of these other controls and how you can connect them to data in your web application.

TreeView

The TreeView displays hierarchically structured data. Because of this, it can be data-bound only to the XmlDataSource and the SiteMapDataSource controls that are designed to bind to hierarchically structured data sources like a SiteMap file. Listing 8-48 shows an example SiteMap file you can use for your SiteMapDataSource control.

LISTING 8-48: A SiteMap file for your samples

<siteMap>
  <siteMapNode url="page3.aspx" title="Home" description="" roles="">
    <siteMapNode url="page2.aspx"
           title="Content" description="" roles="" />
    <siteMapNode url="page4.aspx"
           title="Links" description="" roles="" />
    <siteMapNode url="page1.aspx"
           title="Comments" description="" roles="" />
  </siteMapNode>
</siteMap>

Listing 8-49 shows how you can bind a TreeView control to a SiteMapDataSource control to generate navigation for your website.

LISTING 8-49: Using the TreeView with a SiteMapDataSource control

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
  <title>Using the TreeView control</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:TreeView ID="TreeView1" Runat="server"
       DataSourceID="SiteMapDataSource1" />
    <asp:SiteMapDataSource ID="SiteMapDataSource1"
       Runat="server" />
  </div>
  </form>
</body>
</html>

Menu

Like the TreeView control, the Menu control is capable of displaying hierarchical data in a vertical pop-out style menu. Also like the TreeView control, it can be data-bound only to the XmlDataSource and the SiteMapDataSource controls. Listing 8-50 shows how you can use the same SiteMap data used earlier in the TreeView control example, and modify it to display using the Menu control.

LISTING 8-50: Using the Menu control with a SiteMapDataSource control

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
  <title>Using the Menu control</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:Menu ID="Menu1" Runat="server"
       DataSourceID="SiteMapDataSource1" />
    <asp:SiteMapDataSource ID="SiteMapDataSource1"
       Runat="server" />
  </div>
  </form>
</body>
</html>

Chart

This is a great control for getting you up and running with some good-looking charts. The Chart server control supports many chart types including:

Those are a lot of different chart styles! You can find the Chart server control in the toolbox of Visual Studio 2012 underneath the Data tab. It is part of the System.Web.DataVisualization namespace.

When you drag it from the toolbox and place it on the design surface of your page, you are presented with a visual representation of the chart type that are you going to use. See Figure 8-11 for an example.

FIGURE 8-11

image

Open up the smart tag for the control, and you find that you can assign a data provider for the chart as well as select the chart type you are interested in using. Changing the chart type gives you a sample of what that chart looks like (even if you are not yet working with any underlying data) in the Design view of the IDE. There is a lot to this control, probably more than all the others, and this single control could almost warrant a book on its own. To get you up and running with this chart server control, follow this simple example.

Create a new web application and add the Northwind database to your App_Data folder within the application. After that is accomplished, drag and drop the Chart server control onto the design surface of your page. From the smart tag of the control, select <New Data Source> from the drop-down menu when choosing your data source. Work your way through this wizard making sure that you are choosing a SQL data source as your option. As you work through the wizard, you are going to want to choose the option that allows you to choose a custom SQL statement and use the following SQL for this operation:

SELECT TOP(5) [ProductName], [UnitsInStock] FROM [Products] ORDER BY ProductName DESC

With that in place and the chart server control bound to this data source control, you now find that you have more options in the smart tag of the chart server control. This is presented in Figure 8-12.

FIGURE 8-12

image

Now you can select the series data members and choose what is on the x-axis and what is on the y-axis. I have assigned the Name of the product to be on the x-axis and the quantity ordered to be on the y-axis. After widening the chart’s width a bit, you end up with code similar to the following as illustrated here in Listing 8-51.

LISTING 8-51: Charting with the new Chart server control

<%@ Register Assembly="System.Web.DataVisualization, Version=4.0.0.0,
   Culture=neutral, PublicKeyToken=31bf3856ad364e35"
    Namespace="System.Web.UI.DataVisualization.Charting" TagPrefix="asp" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>MultiView Server Control</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:Chart ID="Chart1" runat="server"
                DataSourceID="SqlDataSource1"
                Width="500px">
                <Series>
                    <asp:Series ChartType="Bar" Name="Series1"
                        XValueMember="ProductName"
                        YValueMembers="UnitsInStock" YValuesPerPoint="2">
                    </asp:Series>
                </Series>
                <ChartAreas>
                    <asp:ChartArea Name="ChartArea1">
                    </asp:ChartArea>
                </ChartAreas>
            </asp:Chart>
            <asp:SqlDataSource ID="SqlDataSource1" runat="server"
                ConnectionString="<%$ ConnectionStrings:ConnectionString %>"
                SelectCommand="SELECT TOP(5) [ProductName], [UnitsInStock] FROM [Products] 
                ORDER BY ProductName DESC"></asp:SqlDataSource>
        </div>
    </form>
</body>
</html>

From this, you can see that there isn’t much code needed to wire everything up. Most notably, you can see that putting this Chart server control on your page actually added a @Register directive to the top of the page. This is unlike most of the ASP.NET server controls.

Within the <Series> element of this control, you can have as many series as you want, and this is something that is quite common when charting multiple items side by side (such as a time series of prices for two or more stocks).

Running this code, you get results similar to what is presented here in Figure 8-13.

FIGURE 8-13

image

INLINE DATA-BINDING SYNTAX

Another feature of data binding in ASP.NET is inline data-binding syntax. Inline syntax in ASP.NET 1.0/1.1 was primarily relegated to templated controls such as the DataList or the Repeater controls, and even then it was sometimes difficult and confusing to make it work as you wanted it to. In ASP.NET 1.0/1.1, if you needed to use inline data binding, you might have created something like the procedure shown in Listing 8-52.

LISTING 8-52: Using DataBinders in ASP.NET 1.0

<asp:Repeater ID="Repeater1" runat="server"
   DataSourceID="SqlDataSource1">
  <HeaderTemplate>
    <table>
  </HeaderTemplate>
  <ItemTemplate>
    <tr>
     <td>
       <%# Container.DataItem("ProductID")%><BR/>
       <%# Container.DataItem("ProductName")%><BR/>
       <%# DataBinder.Eval(
           Container.DataItem, "UnitPrice", "{0:c}")%><br/>
      </td>
    </tr>
  </ItemTemplate>
  <FooterTemplate>
    </table>
  </FooterTemplate>
</asp:Repeater>

As you can see in this example, you are using a Repeater control to display a series of employees. Because the Repeater control is a templated control, you use data binding to output the employee-specific data in the proper location of the template. Using the Eval method also allows you to provide formatting information such as Date or Currency formatting at render time.

In later versions of ASP.NET, the concept of inline data binding remains basically the same, but you are given a simpler syntax and several powerful binding tools to use.

Data-Binding Syntax

ASP.NET contains three ways to perform data binding. One way is that you can continue to use the existing method of binding, using the Container.DataItem syntax:

<%# Container.DataItem("Name") %>

This is good because it means you don’t have to change your existing web pages if you are migrating from prior versions of ASP.NET. But if you are creating new web pages, you should probably use the simplest form of binding, which is to use the Eval method directly:

<%# Eval("Name") %>

You can also continue to format data using the formatter overload of the Eval method:

<%# Eval("HireDate", "{0:mm dd yyyy}" ) %>

In addition to these changes, ASP.NET includes a form of data binding called two-way data binding. Two-way data binding allows you to support both read and write operations for bound data. This is done using the Bind method, which, other than using a different method name, works just like the Eval method:

<%# Bind("Name") %>

The Bind method should be used in controls such as the GridView, DetailsView, or FormView, where autoupdates to the data source are implemented.

When working with the data-binding statements, remember that anything between the <%# %> delimiters is treated as an expression. This is important because it gives you additional functionality when data binding. For example, you could append additional data:

<%# "Foo " + Eval("Name") %>

Or you can even pass the evaluated value to a method:

<%# DoSomeProcess( Eval("Name") )%>

XML Data Binding

Because XML is so prevalent in applications, ASP.NET also includes several ways to bind specifically to XML data sources. These data-binding expressions give you powerful ways of working with the hierarchical format of XML. Additionally, except for the different method names, these binding methods work exactly the same as the Eval and Bind methods discussed earlier. These binders should be used when you are using the XmlDataSource control. The first binding format that uses the XPathBinder class is shown in the following code:

<% XPathBinder.Eval(Container.DataItem, "employees/employee/Name") %>

Notice that rather than specifying a column name as in the Eval method, the XPathBinder binds the result of an XPath query. Like the standard Eval expression, the XPath data-binding expression also has a shorthand format:

<% XPath("employees/employee/Name") %>

Also, like the Eval method, the XPath data-binding expression supports applying formatting to the data:

<% XPath("employees/employee/HireDate", "{0:mm dd yyyy}") %>

The XPathBinder returns a single node using the XPath query provided. Should you want to return multiple nodes from the XmlDataSource control, you can use the class’s Select method. This method returns a list of nodes that match the supplied XPath query:

<% XPathBinder.Select(Container.DataItem,"employees/employee") %>

Or use the shorthand syntax:

<% XpathSelect("employees/employee") %>

USING EXPRESSIONS AND EXPRESSION BUILDERS

Expressions are statements that are parsed by ASP.NET at run time to return a data value. ASP.NET automatically uses expressions to do things like retrieve the database connection string when it parses the SqlDataSource control, so you may have already seen these statements in your pages. An example of the ConnectionString expression is shown in Listing 8-53.

LISTING 8-53: A ConnectionString expression

<asp:SqlDataSource ID="SqlDataSource1" Runat="server"
  SelectCommand="SELECT * FROM [Customers]"
  ConnectionString="<%$ ConnectionStrings:ConnectionString %>" />

When ASP.NET is attempting to parse an ASP.NET web page, it looks for expressions contained in the <%$ %> delimiters. This indicates to ASP.NET that this is an expression to be parsed. As shown in the previous listing, it attempts to locate the NorthwindConnectionString value from the web.config file. ASP.NET knows to do this because of the ConnectionStrings expression prefix, which tells ASP.NET to use the ConnectionStringsExpressionBuilder class to parse the expression.

ASP.NET includes several expression builders, including one for retrieving values from the AppSettings section of the web.config file, one for retrieving ConnectionStrings as shown in Listing 8-53, and one for retrieving localized resource file values. Listings 8-54 and 8-55 demonstrate using the AppSettingsExpressionBuilder and the ResourceExpressionBuilder.

LISTING 8-54: Using AppSettingsExpressionBuilder

<asp:Label runat="server" ID="Label1"
   Text="<%$ AppSettings: LabelText %>" />

LISTING 8-55: Using ResourceExpressionBuilder

<asp:Label runat="server" ID="Label1"
   Text="<%$ Resources: MyAppResources,Label1Text %>" />

In addition to using the expression builder classes, you can also create your own expressions by deriving a class from the System.Web.Compilation.ExpressionBuilder base class. This base class provides you with several methods you must override if you want ASP.NET to parse your expression properly. Listing 8-56 shows a simple custom expression builder.

LISTING 8-56: Using a simple custom expression builder

VB

<ExpressionPrefix("MyCustomExpression")>
<ExpressionEditor("MyCustomExpressionEditor")>
Public Class MyFirstCustomExpression
  Inherits ExpressionBuilder
      Public Overrides Function GetCodeExpression(
    ByVal entry As BoundPropertyEntry,
    ByVal parsedData As Object,
    ByVal context As ExpressionBuilderContext) _
    As System.CodeDom.CodeExpression
        Return New CodeCastExpression("Int64",
      New CodePrimitiveExpression(1000))
  End Function
End Class

C#

[ExpressionPrefix("MyFirstCustomExpression")]
[ExpressionEditor("MyFirstCustomExpressionEditor")]
public class MyFirstCustomExpression : ExpressionBuilder
{
  public override System.CodeDom.CodeExpression
    GetCodeExpression(BoundPropertyEntry entry, object parsedData,
      ExpressionBuilderContext context)
  {
    return new CodeCastExpression("Int64",
      new CodePrimitiveExpression(1000));
  }
}

In examining this listing, notice several items. First, you have derived the MyCustomExpression class from ExpressionBuilder as discussed earlier. Second, you have overridden the GetCodeExpression method. This method supplies you with several parameters that can be helpful in executing this method, and it returns a CodeExpression object to ASP.NET that it can execute at run time to retrieve the data value.


NOTE The CodeExpression class is a base class in .NET’s CodeDom infrastructure. Classes that are derived from the CodeExpression class provide abstracted ways of generating .NET code, whether VB or C#. This CodeDom infrastructure helps you create and run code dynamically at run time.

The BoundPropertyEntry parameter entry tells you exactly which property the expression is bound to. For example, in Listings 8-54 and 8-55, the Label’s Text property is bound to the AppSettings and Resources expressions. The object parameter parsedData contains any data that was parsed and returned by the ParseExpression method, which you see later on in the chapter. Finally, the ExpressionBuilderContext parameter context allows you to reference the virtual path or templated control associated with the expression.

In the body of the GetCodeExpression method, you are creating a new CodeCastExpression object, which is a class derived from the CodeExpression base class. The CodeCastExpression tells .NET to generate the appropriate code to execute a cast from one data type to another. In this case, you are casting the value 1000 to an Int64 data type. When .NET executes the CodeCastExpression, it is (in a sense) writing the C# code ((long)(1000)) or (if your application was written in VB) CType(1000,Long). Note that a wide variety of classes derive from the CodeExpression class that you can use to generate your final code expression.

The final lines to note are the two attributes that have been added to the class. The ExpressionPrefix and ExpressionEditor attributes help .NET determine that this class should be used as an expression, and they also help .NET locate the proper expression builder class when it comes time to parse the expression.

After you have created your expression builder class, you let .NET know about it. You do this by adding an ExpressionBuilders node to the compilation node in your web.config file. Notice that the value of the ExpressionPrefix is added to the ExpressionBuilder to help ASP.NET locate the appropriate expression builder class at run time.

<compilation debug="true" strict="false" explicit="true">
  <expressionBuilders>
    <add expressionPrefix="MyCustomExpression" type="MyCustomExpression"/>
  </expressionBuilders>
</compilation>

The GetCodeExpression method is not the only member available for overriding in the ExpressionBuilder class. Several other useful members include the ParseExpression, SupportsEvaluate, and EvaluateExpression methods.

The ParseExpression method lets you pass parsed expression data into the GetCodeExpression method. For example, in Listing 8-56, the CodeCastExpression value 1000 was hard-coded. If, however, you want to allow a developer to pass that value in as part of the expression, you simply use the ParseExpression method as shown in Listing 8-57.

LISTING 8-57: Using ParseExpression

VB

<ExpressionPrefix("MyCustomExpression")>
<ExpressionEditor("MyCustomExpressionEditor")>
Public Class MySecondCustomExpression
  Inherits ExpressionBuilder
      Public Overrides Function GetCodeExpression(
    ByVal entry As BoundPropertyEntry,
    ByVal parsedData As Object,
    ByVal context As ExpressionBuilderContext) _
    As System.CodeDom.CodeExpression
        Return New CodeCastExpression("Int64",
      New CodePrimitiveExpression(parsedData))
  End Function
      Public Overrides Function ParseExpression(
    ByVal expression As String,
    ByVal propertyType As Type,
    ByVal context As ExpressionBuilderContext) As Object
        Return expression
  End Function
End Class

C#

[ExpressionPrefix("MySecondCustomExpression")]
[ExpressionEditor("MySecondCustomExpressionEditor")]
public class MySecondCustomExpression : ExpressionBuilder
{
  public override System.CodeDom.CodeExpression
    GetCodeExpression(BoundPropertyEntry entry, object parsedData,
      ExpressionBuilderContext context)
  {
    return new CodeCastExpression("Int64",
      new CodePrimitiveExpression(parsedData));
  }
  public override object ParseExpression
    (string expression, Type propertyType,
     ExpressionBuilderContext context)
  {
    return expression;
  }
}

The last two ExpressionBuilder overrides to examine are the SupportsEvaluate and EvaluateExpression members. You need to override these methods if you are running your website in a no-compile scenario (you have specified compilationMode = "never" in your @Page directive). The SupportEvaluate property returns a Boolean indicating to ASP.NET whether this expression can be evaluated while a page is executing in no-compile mode. If True is returned and the page is executing in no-compile mode, the EvaluateExpression method is used to return the data value rather than the GetCodeExpression method. The EvaluateExpression returns an object representing the data value. See Listing 8-58.

LISTING 8-58: Overriding SupportsEvaluate and EvaluateExpression

VB

<ExpressionPrefix("MyCustomExpression")>
<ExpressionEditor("MyCustomExpressionEditor")>
Public Class MyThirdCustomExpression
  Inherits ExpressionBuilder
      Public Overrides Function GetCodeExpression(
    ByVal entry As BoundPropertyEntry,
    ByVal parsedData As Object,
    ByVal context As ExpressionBuilderContext) _
    As System.CodeDom.CodeExpression
        Return New CodeCastExpression("Int64",
      New CodePrimitiveExpression(parsedData))
  End Function
      Public Overrides Function ParseExpression(
    ByVal expression As String,
    ByVal propertyType As Type,
    ByVal context As ExpressionBuilderContext) As Object
        Return expression
  End Function
  Public Overrides ReadOnly Property SupportsEvaluate As Boolean
    Get
      Return True
    End Get
  End Property
      Public Overrides Function EvaluateExpression(
    ByVal target As Object,
    ByVal Entry As BoundPropertyEntry,
    ByVal parsedData As Object,
    ByVal context As ExpressionBuilderContext) As Object
        Return parsedData
  End Function
    End Class

C#

[ExpressionPrefix("MyThirdCustomExpression")]
[ExpressionEditor("MyThirdCustomExpressionEditor")]
public class MyThirdCustomExpression : ExpressionBuilder
{
  public override System.CodeDom.CodeExpression
    GetCodeExpression(BoundPropertyEntry entry, object parsedData,
      ExpressionBuilderContext context)
  {
    return new CodeCastExpression("Int64",
      new CodePrimitiveExpression(parsedData));
  }
      public override object ParseExpression
    (string expression, Type propertyType,
     ExpressionBuilderContext context)
  {
    return expression;
  }
      public override bool SupportsEvaluate
  {
    get
    {
      return true;
    }
  }
      public override object EvaluateExpression(object target,
    BoundPropertyEntry entry, object parsedData,
    ExpressionBuilderContext context)
  {
    return parsedData;
  }
}

As shown in Listing 8-58, you can simply return True from the SupportsEvaluate property if you want to override the EvaluateExpression method. Then all you do is return an object from the EvaluateExpression method.

SUMMARY

In this chapter, you examined data binding in ASP.NET. Data source controls such as LinqDataSource, SqlDataSource, and XmlDataSource make querying and displaying data from any number of data sources an almost trivial task. Using the data source controls’ own wizards, you learned how easy it is to generate powerful data access functionality with almost no code.

You examined how even a beginning developer can easily combine the data source controls with the GridView, ListView, and DetailsView controls to create powerful data-manipulation applications with a minimal amount of coding.

You saw how ASP.NET includes a multitude of controls that can be data-bound, examining the features of the controls that are included in the ASP.NET toolbox, such as the GridView, TreeView, ListView, FormView, and Menu controls.

Finally, you looked at how the inline data-binding syntax has been improved and strengthened with the addition of the XML-specific data-binding expressions.