In-Depth

Build More Robust Databinding Apps

Take control of databinding in .NET 2.0 and utilize Visual Studio's new automated testing and refactoring features to make your app more testable and resilient to change.

Technology Toolbox: C#, SQL Server, ASP.NET

Databinding lies at the heart of many presentation layers in .NET-based applications. It's easy to see why. It's a powerful feature, and many demonstrations of .NET showcase databinding techniques to build data-driven applications. The ease at which Visual Studio .NET allows for the seemingly effortless use of databinding has resulted in a rash of applications that are built using many of the techniques demonstrated in product demos. Unfortunately, many of the techniques and strategies presented at such demos give developers a false sense of confidence about the seeming flexibility of declarative databinding. More often than not, these techniques can lead you down a path that will cause problems as your applications change.

Visual Studio 2005 and ASP.NET 2.0 introduce even more options for taking advantage of databinding in applications, including new data source controls that allow for an even simpler, completely declarative, and in some cases, code-free approach to databinding. These are powerful features, but you must be careful how you use them.

I'll show you how to take advantage of these features to create databinding applications that are resilient to change, that offer compile-time databinding checks, and that you can test and verify without running your application. Along the way, I'll demonstrate and explain how to avoid some of the pitfalls associated with utilizing common approaches to databinding, including issues you run up against when using some of the new data source controls (download the sample code here).

The first decision you must make when choosing to implement databinding in your application is what you'll use as a data source. You have many choices for this in .NET, including data sets, XML, and custom objects.

The more robust example in this article uses a custom object data source, but first I want to contrast this approach with one that uses data sets to illustrate some of the pitfalls you can encounter with that approach. Begin by building a simple Web page that displays a list of information in the Customers table from the Northwind database (see Figure 1).

This example takes advantage of column definitions for the Northwind Customers table, including some of the new controls made available in ASP.NET 2.0—namely, the new data source controls (see Figure 2). Next, configure the SqlDataSource control that drives the page (see Listing 1).

This approach looks great on the surface. In less than two minutes, you can have a fully data-driven Web page, with no code required, by relying completely on ASP.NET markup tags. You can use Visual Studio to manipulate the SQLDataSource control, and it can generate the ASPX markup for the control configuration. This results in a completely wizard-driven approach to databinding.

How Codeless Comes Up Short
Unfortunately, this approach has some issues. First, using the SqlDataSource control requires that the view (page) have intimate knowledge of the database. The view determines where to pull the data from, as well as how to display that data. If the connection string were stored anywhere other than the configuration file, the view would also be responsible for providing a connection string to connect to the database. This is simply not a good way to separate the responsibility for various aspects of your app.

Another, more significant issue: You must run the application to verify that the databinding works. For example, assume you change the name of the "Customers" table to "Customer." You might think you can make this simple change, rebuild the database, and be ready to roll. But running your application at this point will generate a server error indicating that Customers is an invalid object name.

This is a common scenario for many developers. One small change can cause ripple effects that go unnoticed until you attempt to run the application. This approach can slow down the development process and reduce confidence in the application's functionality. This architecture is fundamentally a two-tier approach, and it limits the flexibility of your app.

ASP.NET 2.0 introduces a new way to bind to application data: the ObjectDataSource control. This control mitigates the two-tier design of the SqlDataSource approach. The ObjectDataSource control can bind to custom methods defined in a business/data access layer component of the application, while still taking advantage of declarative configuration. For example, use this code to configure the Web page with the new ObjectDataSource control:

<asp:ObjectDataSource ID="customersObjectDataSource" 

   runat="server" 
   SelectMethod="GetAllCustomers"
   TypeName="DataBinding.ServiceLayer.SimpleCustomerTask">
   </asp:ObjectDataSource>

This approach looks good initially, but it also has underlying issues. The view no longer needs to know anything about connection strings or the table that the data comes from. Unfortunately, that gain is offset by the fact that this approach uses string literals to define the type that exposes the binding method and the name of the binding method itself. If any one of these change and you don't update the necessary string literals, then you still must run the application before you can discover errors in your app.

.NET lets you bind to arbitrary collections of objects, as opposed to data tables and XML only. This means that you can set the DataSource property of any of the databinding controls to an IList of your own domain-specific object.

Begin by creating a Customer class that includes all of the attributes you care to know about a customer; the example assumes that this class is an object representation of a Customer row in the database (see Listing 2).

Map Your Data
So far, you have a custom class. Next, you need to get data from the database to "map" into this class. A data mapper does the trick nicely. Add a CustomerMapper class into the DataAccess Layer. Its responsibility is to retrieve a set of customer rows from the database and convert them into the appropriate customer objects. This simplified implementation of the Mapper Design Pattern is sufficient to get the point across (see Listing 3).

This implementation is a first step to achieving automated application failure recognition. Ultimately, you want to be able to identify as many errors as possible without running the application manually. Let's focus on how you could capture errors caused by changes made to the Customers table. Do this by writing a test for the CustomerMapper class (see Listing 4). Pay attention to the TransactionScope object, which is new to .NET Framework 2.0. The test method uses it to ensure that you don't need to roll back any changes made to the database manually after test execution. By not calling Complete on the TransactionScope, any effects the test has on the database are reversed when the test ends.

The approach I'm describing now is quite a bit more work up front than the codeless databinding I covered at the outset. Let's look at what this effort buys you. The CustomerMapper is now responsible for retrieving data from the Customers table, as well as creating the necessary domain objects from it. This means the "ShouldBeAbleToGetAllCustomers" test will now fail for both scenarios, if either the table name or the name of one of the columns has changed. You can take advantage of new features in Visual Studio 2005 to run these automated tests from inside the IDE or from the command line. This eliminates the initial issue of needing to run the application to catch these errors—a tremendous benefit.

Next, wire the desired data back up to the UI:

public partial class DeclarativeWithNoDataSourceControl : Page
{
   protected void Page_Load(object sender, EventArgs e)
   {
      if (! IsPostBack)
      {
         BindCustomersGrid();
      }
   }

   private void BindCustomersGrid()
   {
      this.customersGridView.DataSource = new 
         CustomerTask().GetAllCustomers();
      this.customersGridView.DataBind();
   }
}

This code invokes a method on your service layer that returns a generic IList of type Customer; bind your data grid to the invoked method. The method that returns the data to you is fully tested, as is the underlying mapper that takes rows from the database and converts them into Customer objects.

One major hurdle remains. The grid view still uses string literals to specify the names of properties that it should bind to. This can cause runtime error scenarios if you attempt to use an invalid property name. For example, mistype the name of one of the properties you want to bind to:

<asp:BoundField DataField="CompanyNam" HeaderText="CompanyName"/>

This code attempts to bind this BoundField to the CompanyNam property of the Customer class, but the Customer class doesn't have a CompanyNam property. Running all the automated tests won't catch this scenario, and you get an error when you run the application.

Take Control of Binding
What you need to do is eliminate the use of string literals to perform databinding (see Listing 5). You must hook up code to handle the RowDataBound event explicitly, which is fired once for each row being bound to the grid. Then, you use the DataItem property and cast it to a Customer type because you know you're binding to Customer objects. Once you have the Customer object, populate all of the controls in the row by using the Generic method, which returns a control of a specified type from the current GridRow using its control ID. Next, populate this control with the value you want from the Customer object.

This solution is not far from being the codeless approach some demos promise. However, eliminating the string literals for properties gives you the ability to refactor the customer class at will, and the code-behind reflects the changes automatically. Let's demonstrate this by renaming the CompanyName property on Customer to Company. VS 2005 now supports automatic code refactorings. Rename is one of them. Go into the code for the Customer class and place the cursor anywhere inside the CompanyName property name. Right-click on the CompanyName property. This causes the Rename dialog to pop up. Use the NewName textbox to change the CompanyName property to Company.

After you hit Continue, you'll see progress bars flash to indicate that Visual Studio is renaming any references to CompanyName to become Company. Rebuild. The build succeeds. Run your unit test. They all pass! Run the app, and it will work no problem. This is great; you now have a solution that can take advantage of Visual Studio's refactoring capabilities, support changes to the underlying database schema, and support automated execution of tests that verify that the code the UI depends on is working correctly. Let's take this one step further so that you can have your cake and eat it too. One step remains before you can take this databinding solution to its optimal conclusion. Moving to code-behind means you no longer need to utilize string literals to resolve property bindings. This gain requires a fair amount of code-behind in your Web page to implement. It also introduces a new dependency between the code-behind and the names of controls in the GridView ItemTemplates. If you now want to change the control that displays a particular piece of information, you have to change it in two places: the code-behind and the ASPX page.

Fortunately, you can bind to your data source declaratively, as well as enforce compile-time verification of bindings. Begin by changing the GridView definitions in the ASPX page to take advantage of a different databinding syntax. This snippet illustrates how to use the new binding syntax to bind the CompanyName field in the ASPX page:

<asp:TemplateField HeaderText = "CompanyName"> 
    <ItemTemplate><span><%# 
   ((Customer)Container.DataItem).
   CompanyName %></span></ItemTemplate>
   </asp:TemplateField>

This type of databinding syntax lets you use inline code fragments that the page outputs in place. In this fragment, you cast the Container.DataItem (the current item that you're binding to) to the correct type (Customer) and then invoke the property that you want to access to populate the span.

You can continue to modify all of the ItemTemplates so that all of the fields bind using the new syntax. Once you have completed the necessary changes to the ASPX file, you can remove all of the custom databinding code that you previously added to handle binding a customer to each row in the GridView. You don't require this code anymore, as the inline code in the ASPX file takes care of pulling values from the Customer object and outputting them to places inside of the template field. This gives you, as a page designer, more freedom in how you want to lay out your Item templates. The code-behind is no longer tightly coupled to controls within the ItemTemplate:

protected void Page_Load(object sender, EventArgs e)
   {
      if (! IsPostBack)
      {
         BindCustomersGrid();
      }
   }

   private void BindCustomersGrid()
   {
      this.customersGridView.DataSource = new 
         CustomerTask().GetAllCustomers();
      this.customersGridView.DataBind();
   }

Don't forget what led you down this path in the first place. Go into the Customer class and rename the Company property back to CompanyName. Next, switch back to the ASPX file and note that the rename has not changed the inline databinding line. How has this helped, you're wondering? You're still stuck waiting to run the app, right? Wrong. Build the application.

You get a compile error stating that the Customer class does not contain a definition for CompanyName. This rename is much better than waiting to run the app to find this error.

This means you now have a databinding solution that is resilient to change from all angles. The tests for the CustomerMapper catch any change that affects the Customer table, and you have a front end that can support declarative-style binding coupled with compile-time binding checks. Using the approach described in this article might feel significantly different to many approaches that you're familiar with, but the benefits far outweigh any initial discomfort. One of the most important questions you should ask yourself as you're building a piece of application functionality: How can I automate the testing of it to ensure that I'm getting the results that I want?

The "automagic" code-free approaches to databinding are more expensive than they initially seem. They come at the expense of the ability to change underlying objects/schemas with any degree of certainty aside from running the app, and they don't give you a way to automate the testing of the databinding functionality because all of the functionality is wrapped up in declarative constructs. The more change-tolerant approach to databinding described in this article should enable you to implement databinding applications that scale better and require much less work in the maintenance phase.

comments powered by Disqus
Upcoming Events

.NET Insight

Sign up for our newsletter.

I agree to this site's Privacy Policy.