Wire Up Data-Driven Web Apps with an ASP.NET 2.0 DataSource Control

DataSource controls provide declarative data binding, or put more simply, data binding without requiring you to write code. They represent back end data stores and replace the code that queries data sources and binds the results to controls with <asp:> tags. As such, they have no rendering in the user interface. DataSource controls also provide rich capabilities such as sorting, paging, filtering, updating, deleting, and inserting across data.

ASP.NET 2.0 has five DataSource controls:

  • AccessDataSource: Connects data binding controls to an Access database
  • ObjectDataSource: Connects data binding controls to data objects/components
  • SiteMapDataSource: Connects site navigation controls to site map data
  • SqlDataSource: Connects data binding controls to a SQL Server database
  • XmlDataSource: Connects data binding controls to XML data

This article focuses on the ObjectDataSource and the value it provides.

ObjectDataSource Background

N-tier applications commonly isolate the logic for talking to data sources in a data access layer. The theory behind this design is to isolate the user front end or UI from changes that may occur to the back end data store. This eliminates the dependency between the UI and the data source. As long as the data access layer does not change the interface it presents to the UI, the back end data source can change any number of times with minimal to no effect on the front end.

Interestingly, not all of the DataSource controls are designed to support an n-tier type of application. For example, the AccessDataSource and SqlDataSource controls do not allow for a separation of UI and data access. They actually do the opposite; the AccessDataSource and SqlDataSource controls create an explicit connection between the UI and the back end data store.

The ObjectDataSource (ODS), however, was created with n-tier applications in mind. It supports declarative data binding to data objects/components, which allows for use of middle-tier data access components. It supports two-way data binding through SelectMethod, InsertMethod, UpdateMethod, and DeleteMethod. It also offers optional caching of query results and parameterized operations.

The ODS has a number of properties that allow you to control its behavior. This article covers the following ODS properties:

  • TypeName: Type of the data component
  • DataObjectTypeName: Type of parameter used for calls to insert, update, and delete operations in place of individual parameters
  • SelectMethod: Method called on the data component to execute read queries
  • InsertMethod: Method called on the data component to execute inserts
  • UpdateMethod: Method called on the data component to execute updates
  • DeleteMethod: Method called on the data component to execute deletes
  • SelectParameters: Parameters for SelectMethod
  • InsertParameters: Parameters for InsertMethod
  • UpdateParameters: Parameters for UpdateMethod
  • DeleteParameters: Parameters for DeleteMethod

Although it appears simple in its function, the ODS is complex in nature. A number of subtle nuances can influence its behavior in many different ways.

Selecting Data

To demonstrate how to select data through an ObjectDataSource and display it in a GridView control, the example in this section sets the SelectMethod property on the ODS and configures a GridView.

The following sample code represents a data object component for data from the SQL Server example database. A connection string in the web.config file contains the connection string information required. For this simple example, the data component will do nothing more than query some data from the Northwind database and return it in a collection:

using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;

public class Customers
{

   public Customers()
   {
   }

   public ICollection GetCustomers()
   {
      ArrayList al = new ArrayList();
      ConnectionStringSettings cts =
         ConfigurationManager.ConnectionStrings["Northwind"];
      using (SqlConnection connection =
         new SqlConnection(cts.ConnectionString))
      {
         using (SqlCommand cmd =
            new SqlCommand("SELECT * FROM Customers", connection))
         {
            connection.Open();
            using (SqlDataReader reader =
               cmd.ExecuteReader(CommandBehavior.CloseConnection))
            {
               while (reader.Read())
               {
                  al.Add(new Customer(reader));
               }
            }
            connection.Close();
         }
      }

      return al;
   }
}

public class Customer
{
   private string customerID = "";
   public string CustomerID
   {
      get { return this.customerID; }
      set { this.customerID = value; }
   }

   private string companyName = "";
   public string CompanyName
   {
      get { return this.companyName; }
      set { this.companyName = value; 
   }

   public Customer(IDataReader reader)
   {
      this.CustomerID = reader["CustomerID"].ToString();
      this.CompanyName = reader["CompanyName"].ToString();
   }
}

The following code provides an example declaration of an ObjectDataSource control and a grid view. The ObjectDataSource is connected to the data component and the GridView is bound to the data source. This will result in a grid being displayed with two columns of data from the Northwind database:

<div>
   <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
   SelectMethod="GetCustomers" TypeName="Customers">
   </asp:ObjectDataSource>/\*-
</div>

<asp:GridView ID="GridView1" runat="server"
              DataSourceID="ObjectDataSource1"></asp:GridView>

When you execute the code, you should see results similar to Figure 1.

Figure 1. Example ObjectDataSource Control and a Grid View

Inserting, Updating, and Deleting Data

The insert, update, and delete functionalities all behave similarly. This example uses the update functionality to demonstrate how they work, but it could just as easily be insert or delete. To be able to update data, you set the UpdateMethod property of the ObjectDataSource to the name of the data component method that you want to call. Here, you start to get into some of the many nuances of the ObjectDataSource control and its behavior. Subtle configuration changes can make a big difference in the way the ObjectDataSource behaves.

UpdateMethod Auto Wire-Up

The following code contains the ObjectDataSource declaration along with UpdateCustomer sample code to add to the Customers example in the previous section. It enables the autogenerate of the edit button on the GridView so that it will automatically build out the columns. On the ObjectDataSource, it sets the DataObjectTypeName to Customer. This will cause the GridView to automatically populate an instance of the Customer class and pass it as a parameter to the UpdateCustomer method.

Here is the Web form sample code:

<div>
   <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
   SelectMethod="GetCustomers" TypeName="Customers"
   DataObjectTypeName="Customer" UpdateMethod="UpdateCustomer">
   </asp:ObjectDataSource>
</div>
<asp:GridView ID="GridView1" runat="server"
              AutoGenerateEditButton="True"
              DataSourceID="ObjectDataSource1"></asp:GridView>

Here is the sample code to include in the Customers class:

//...
public static void UpdateCustomer(Customer customer)
{
   System.Diagnostics.Debug.WriteLine(customer.CustomerID);
   System.Diagnostics.Debug.WriteLine(customer.CompanyName);
}

The ability to automatically populate and pass a Customer instance is a nice feature, but you can run into some functionality that may not work so well. In this case, it will pass in all the columns from the GridView into the Customer instance because the values are named the same and are all contained within the GridView. As soon as you remove fields like customerID from the actual display, the values are no longer passed in to the UpdateCustomer method through a Customer instance. Not having the customerID or the original customerID makes it difficult to know what to update. Now, you want more control.

UpdateMethod with Specified Parameters

At times, you may find that you want to control exactly which values get passed in to the update. For example, you may want to add an additional column from a drop-down list or other text box. You'll get more consistent behavior from the ObjectDataSource by following a specific pattern when using it. You should specify exactly which parameters you want to be passed in to the method call. The following code snippet demonstrates an update method with two parameters to be included in the UpdateCustomer method of the Customers class:

public static void UpdateCustomer(string customerID,
                                  string companyName)
{
   System.Diagnostics.Debug.WriteLine(customerID);
   System.Diagnostics.Debug.WriteLine(companyName);
}

Here is the resulting ObjectDataSource definition:

<asp:ObjectDataSource ID="ObjectDataSource1"
                      runat="server" SelectMethod="GetCustomers"
                      TypeName="Customers"
                      UpdateMethod="UpdateCustomer">
   <UpdateParameters>
      <asp:FormParameter Name="customerID" Type="String" />
      <asp:FormParameter Name="companyName" Type="String"/>
   </UpdateParameters>
</asp:ObjectDataSource>

Notice that I removed the DataObjectTypeName property from the definition. Your attempt to add parameters will be ignored when that property is set, which is bound to cause you headaches during debugging.

This technique can run into some of the same problems as before, where it assumes that all of the values that you need and want to update are present in the GridView. There is a more realistic pattern that requires a little more code. It involves using the GridView_RowUpdating event. Here is the GridView definition:

<div>
   <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
                         SelectMethod="GetCustomers"
                         TypeName="Customers"
                         UpdateMethod="UpdateCustomer">
   </asp:ObjectDataSource>
</div>
<asp:GridView ID="GridView1" runat="server"
              AutoGenerateEditButton="True"
              DataSourceID="ObjectDataSource1"
              OnRowUpdating="GridView1_RowUpdating">
</asp:GridView>

Here is a revised version of the UpdateCustomers method. You'll notice that I added a third parameter representing the original customer ID:

public static void UpdateCustomer(string originalCustomerID,
                                  string customerID,
                                  string companyName)
{
   System.Diagnostics.Debug.WriteLine(originalCustomerID);
   System.Diagnostics.Debug.WriteLine(customerID);
   System.Diagnostics.Debug.WriteLine(companyName);
}

The following code belongs in the event handler of the Web form. The event allows you to take advantage of the NewValues and OldValues collections to retrieve values and use them in the update process. Set up the call to the update process and execute it:

protected void GridView1_RowUpdating(object sender,
                                     GridViewUpdateEventArgs e)
{
   ObjectDataSource1.UpdateParameters.Clear();
   ObjectDataSource1.UpdateParameters.Add("customerID",
      e.OldValues[0].ToString());
   ObjectDataSource1.UpdateParameters.Add("companyName",
      e.NewValues[1].ToString());
   ObjectDataSource1.Update();
   GridView1.EditIndex = -1;
   e.Cancel = true;
}

Wire Up Data-Driven Web Apps with an ASP.NET 2.0 DataSource Control

ObjectDataSource Performance

The ObjectDataSource will create a new instance of the data component for each call that is performed, and it will call a public default constructor that must be present on the data component. This instantiation can have significant performance implications—especially if the data component does time-consuming work at the time it is instantiated. Some built-in mechanisms help accommodate this. For starters, you can use static rather than instance methods. As you probably know, static methods do not require you to create a class instance, which reduces the performance overhead.

If you do have cause to use an instance component, an ObjectCreated event allows you to custom initialize the component after it has been instantiated, and an ObjectCreating event allows you to respond before the object is created so you can customize how it is created. Similarly, an ObjectDisposing event fires just before disposing the data component. If the data component implements the IDisposable interface, the Dispose method will be called after the event.

You wire up the events by adding them to the ObjectDataSource declaration and registering the methods to call. A good use for the ObjectCreating and ObjectDisposing events would be to allow for an instance object to be stored in cache and to control the retrieval from cache to prevent it from being disposed.

Static Sample

Using a static method does not take any additional setup on the part of the ObjectDataSource. Rather than set the TypeName to an instance method, you simply point it to a static method. Here is a code snippet from the original example with the static keyword added to the method declaration:

using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;

public class Customers
{
   public Customers()
   {
   }

   public static ICollection GetCustomers()
   {
      //Cut out sample ...
   }
}

ObjectCreated and ObjectDisposing Sample

The following code, an expanded sample from the earlier control declaration, demonstrates wiring up and handling the ObjectCreating and ObjectDisposing events:

<div>
   <asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
   SelectMethod="GetCustomers"
   TypeName="Customers"></asp:ObjectDataSource>
</div>

<asp:GridView ID="GridView1" runat="server"
              DataSourceID="ObjectDataSource1"
              OnObjectCreating="ObjectDataSource1_ObjectCreating"
              OnObjectDisposing="ObjectDataSource1_ObjectDisposing">
</asp:GridView>

Here is sample code from the Web page code behind for handling the events:

//Method fired just after the instance is created
protected void ObjectDataSource1_ObjectCreating(object sender,
   ObjectDataSourceEventArgs e)
{
   //Get or put something from cache through e.ObjectInstance = xxx
}

//Method fired just before disposing of the data component
protected void ObjectDataSource1_ObjectDisposing(object sender,
   ObjectDataSourceDisposingEventArgs e)
{
   //Get or put something from cache and cancel event e.Cancel = true
}

Here is sample code for the data component, as it must implement the IDisposable interface. This code is a snippet of the changes to make to the example above:

public class Customers : System.IDisposable
{
   public Customers()
   {
   }

   void System.IDisposable.Dispose()
   {
      //...
   }
   //Cut out remaining sample ...
}

You can see these events fire simply by putting a statement such as System.Diagnostics.Debug.WriteLine("xxx") in the method and using a break point to see it in the Visual Studio IDE.

What Have You Learned?

You've seen how to select data and display it in a GridView by binding to an ObjectDataSource. Because the insert and delete operations behave similarly to the update operation, they should be interchangeable with the examples. You also looked at the performance implications of using ObjectDataSource and some ways that you can ensure your applications perform well.

Future Columns

The topic of the next column is yet to be determined. If you have something in particular that you would like to see explained here, you could reach me at mstrawmyer@crowechizek.com



About the Author

Mark Strawmyer

Mark Strawmyer is a Senior Architect of .NET applications for large and mid-size organizations. He specializes in architecture, design and development of Microsoft-based solutions. Mark was honored to be named a Microsoft MVP for application development with C# for the fifth year in a row. You can reach Mark at mark.strawmyer@crowehorwath.com.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Event Date: April 15, 2014 The ability to effectively set sales goals, assign quotas and territories, bring new people on board and quickly make adjustments to the sales force is often crucial to success--and to the field experience! But for sales operations leaders, managing the administrative processes, systems, data and various departments to get it all right can often be difficult, inefficient and manually intensive. Register for this webinar and learn how you can: Align sales goals, quotas and …

  • Cisco and Intel have harnessed flash memory technology and truly innovative system software to blast through the boundaries of today's I/O-bound server/storage architectures. See how they are bringing real-time responsiveness to data-intensive applications—for unmatched business advantage. Sponsored by Cisco and Intel® Partnering in Innovation

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds