Practical ASP.NET

Leverage Databinding With Less Code

Databinding in ASP.NET 2.0 is a different beast from its original implementation in ASP.NET -- there are more databinding controls and a radically different approach that gets more done with less code.

Technology Toolbox: VB.NET, ASP.NET

ASP.NET is often cited as the runaway success in the initial implementation of .NET. So it might surprise some developers just how significantly Microsoft has reworked ASP.NET in version 2.0, which ships with Visual Studio 2005.

You'll recognize some things if you're familiar with ASP.NET 1.1—for instance, the DataGrid and DataList are still in the Toolbox. But even that familiarity is a trap—the DataGrid and DataList are present only to support backward compatibility. Databinding in ASP.NET 2.0 radically changes the databinding model from ASP.NET 1.1 and, in the process, reduces the amount of code to retrieve and display data to zero.

Databinding is different at the roots of the process. For example, you can't databind a textbox as you could in Visual Studio .NET. (For backward compatibility, you can still switch to Source view and write code using the horrible DataBinder.Eval syntax that Visual Studio .NET generates.) The DataAdapter and Connection objects that used to populate the tray at the bottom of the page are gone, as is the tray. Instead of those two data controls, there is a single DataSource object.

But the ultimate change is in the coding model: Complete databinding solutions are now "no-code" applications, albeit these no-code solutions have the same restrictions as the Update method of the DataSet object. This is a significantly different paradigm for developers and might take some getting used to. I'll walk you through how to deal with some of the more radical differences of using this new approach, concentrating on databinding single records with the DataSource using the DetailsView or FormView controls. The initial sample will illustrate how to use these controls in a single-tier solution, but I'll also explain how to use databinding in n-tier applications.

Some of the changes in ASP.NET 2.0 are evolutionary and reflect that the ASP.NET design team is paying attention to common practice. The team, for instance, recognized that most developers keep their connection strings in the appSettings section of the web.config file. The web.config file now includes an element dedicated to holding connection strings. Note that if you're concerned about exposing connection strings in your web.config file, you can add tags that let you store a string as encrypted text that is decrypted for you automatically when you retrieve the string. A connectionStrings element typically looks like this:

<connectionStrings>
   <add name="Northwind"  connectionString="?"
      providerName="System.Data.SqlClient" />
   </connectionStrings>
</appSettings>

As in Visual Studio .NET, Server Explorer is still available to give you access to data sources from within Visual Studio, with the caveat that the Server Explorer migrates to the right side of the editing window in the default configuration. Selecting Add Connection from Server Explorer's popup menu brings up the Add Connection dialog (see Figure 1).

Connect With ADO.NET 2.0
The formatting of the connectionString element illustrates another fundamental change in ASP.NET 2.0: Visual Studio now uses ADO.NET to connect to your databases, rather than ADO. This helps ensure that the way that you interact with your data in Visual Studio mimics the way that your application interacts with the data at run time.

One way to create a data-bound page in VS 2005 is to drag a table from Server Explorer onto a Web page. Creating your page in this way reveals another change in ASP.NET databinding: You get a single DataSource control rather than a connection and a data adapter added to your page. This control provides a single point of access for your data—you also get a DataView control added to your page, regardless of whether you want or need one.

The existence of a single unified control for databinding makes using the data controls from the Toolbox a reasonable option for developers. You can connect to your data simply by dragging and configuring a single control tailored for the kind of database that you're accessing. Configuring your DataSource is made easier by a SmartTag that pops up as soon as you drop your control on your form (see Figure 2). The Wizard that this SmartTag launches lets you select a connection string from the ones defined in your web.config file and build the Select portion of your SQL statement. (You can also use this SmartTag to select a stored procedure, as the situation warrants.)

A simple scenario for a data-bound page might consist of a listbox that lets a user select a record and a display for the data in that record (see Figure 3). You can use the new ASP.NET 2.0 tools to create this page by dragging a DataSource onto the page, picking a connection string from the dropdown list in the Wizard, and setting the fields you want to retrieve. For the listbox in this scenario, the fields that you retrieve are the table's primary key (which you then use to retrieve the record to display) and some descriptive fields to display in the list.

The DetailsView control works with a second DataSource control to handle displaying the data. The second takes advantage of another feature in the control's Wizard: the ability to tie the Where clause of your SQL statement to the value of the ListBox control (you can also tie the Where clause to a cookie, a querystring value, or a value in the user's profile, among other choices).

When connected to a DataSource, the DetailsView control is populated automatically with all the necessary controls to display the fields retrieved by the DataSource. You can limit the data it displays by either going back and reconfiguring the DetailsView's DataSource or editing the fields displayed by the DetailsView. You can choose either option from the DetailsView's SmartTag. You can also turn on paging if you need to retrieve more than a single row.

Put It All Together
You can let your users select a record to display in four simple steps. First, drag a DataSource onto the page and configure it to retrieve a table's primary key and a descriptive field. Second, drag a listbox onto the page and select Choose Data Source from its SmartTag, then configure the control to display the descriptive field in the listbox and hold the primary key in its data field. Next, drag a second DataSource onto the page and set it to retrieve all the fields to be displayed. Finally, drag a DetailsView control onto the page and set its DataSourceId to the name of the second DataSource control. Setting the AutoPostBack property on your listbox to True causes the page to be refreshed each time the user makes a new selection.

These steps enable you to implement this functionality without writing a single line of code. Yes, you can accomplish the same thing by writing the code yourself. But, unless you're paid by the hour, why would you want to? The technique for creating a simple display hints at the power ASP.NET 2.0 delivers, but real-world corporate examples typically require quite a bit more functionality. Fortunately, the tool enables you to handle complicated situations, such as editing the data your users enter during edit, delete, and insert operations.

Enabling updates is a simple matter of checking the Add, Edit, and Insert options in the SmartTag. The major restriction when implementing updates (or "two-way databinding") is whether the DataSource handles updates. Two-way databinding only works if the DataSource is retrieving data from an updateable data source. Typically, this means that your DataSource must be tied to a single table. Specifically, this means you can't have any Joins in the underlying SQL statement. However, nothing stops you from adding your own code to handle updates.

What is missing from the DetailsView is any ability to modify the data display (other than to apply formatting). You need to use the FormView control to go beyond the simple display capabilities of the DetailsView. The FormView control lets you choose which controls you want to use to display your data, and it also lets you decide how you want to arrange them. After you place a control in a FormView, you can databind it much as you did in VS.NET. Visual Studio even generates the databinding expression for you when you specify which field to bind the control to.

So far, the examples assume you want to bind your data directly to a database, creating a two-tier application. However, ASP.NET 2.0 databinding also supports binding to any object. The way you use the DetailsView or the FormView doesn't change when you bind to an object. Rather, the difference is in which DataSource object you use.

When binding to an object, you drag the ObjectDataSource control to your page instead of one of the database-driven DataSource controls. However, using the ObjectDataSource does require that you follow one of two patterns in creating objects that you can bind to. Before you implement those patterns, you need to understand how the process of object databinding works.

You don't bind the ObjectDataSource object to the results of a SQL statement; rather, you bind it to a method on an object that returns a DataSet, a DataReader, or any strongly typed collection.

For example, this code produces a strongly typed collection (a List) that you can use with an ObjectDataSource:

Using conn As New SqlClient.SqlConnection( _
   System.Configuration.ConfigurationManager. _
   ConnectionStrings("Northwind").ToString)
   conn.Open()
   Dim comm As New SqlClient.SqlCommand( _
      "SELECT * FROM Products ", conn)

   Using rdr As SqlClient.SqlDataReader = _
      comm.ExecuteReader( _
      CommandBehavior.CloseConnection)
      Dim ProductList As New List(Of ProductObject)
      While rdr.Read()
         Dim prd As New ProductObject(rdr)
         ProductList.Add(prd)
      End While
      Return ProductList
   End Using

End Using

The code uses a DataReader to retrieve rows from the Products table in the Northwind database, create a Product object from each row, add those objects to a List object, and then return the List. Note that the code assumes that one of the constructors for the Product object will populate the Product object with data from the DataReader's fields when passed a DataReader. The code also takes advantage of the ConnectionString element defined at the start of this article by retrieving the connection string through the ConfigurationManager object.

The code retrieves all the Products in the Northwind database. However, you can easily modify the routine to accept a ProductId as a parameter and create a List of exactly one ProductObject, corresponding to that ProductId. The benefit of defining a parameter that specifies the object you want to create is that the ObjectDataSource control lets you bind that parameter to the same objects that you used in the Where clause for a database-driven DataSource (a listbox or the Session object, for example). This means your user can select an item from a listbox and have that item's data displayed without you ever writing a line of code.

Using the DetailsView and FormView with an ObjectDataSource is identical to using them with a database-driven DataSource. However, you can't configure your Select clause to control which fields are retrieved, as you can when using a database-driven data source containing a SQL statement. The ObjectDataSource object always retrieves all the properties for an object; by default, the DetailsView displays every property returned from the DataSource. As a result, you're more likely to want to take advantage of the Edit Fields option on the DetailsView's SmartTag to remove some of the items from the display when using the ObjectDataSource object.

You need to specify a method to call when performing each of the update, delete, or insert operations to support updates with an ObjectDataSource object. The DataSource supports two scenarios for updating objects. One scenario assumes that the method you want to call accepts a parameter for each property on the object. The second scenario assumes that the object itself will be passed to some method that performs the update. These two different scenarios require you to implement your objects using one of two patterns: the Factory pattern and one that, for want of a better name, I'll call the "naïve programmer pattern."

The naïve programmer scenario requires that you make the object you are databinding expose a method or property that returns a DataSet or DataReader that holds your object's data. This is the same method you bind to the ObjectDataSource object to retrieve data.

The object you are databinding must also include update methods with parameters for each of the object's properties. The result of this pattern is an object with two versions of its update method: One version accepts no parameters and updates the database with the current values from the object's properties, and the second version accepts parameters that override the object's current property settings.

For example, assume you're using this object from your code. You could set the properties on the object and call the object's Update method without any parameters:

Dim prd As New Product(5)
prd.ProductName = "New Product Name"
prd.Update

However, supporting databinding requires that the Update method also accept parameters that correspond to each property on the object. Assuming that the Product object has only two properties, you can use the object like this:

Dim prd As New Product
prd.Update("5", "New Product Name")

The other pattern for supporting databinding is to create two objects. You use one object (the "factory" or "builder" object) to create the object that you want to databind. The previous code sample that builds a list of products and returns them as a list uses this pattern. You don't put this code in the builder object; rather, you give the builder a method that accepts an object and performs any updates on the object's behalf.

The code to create this object is straightforward:

Dim prdb As New ProductBuilder
Dim prd As New Product

prd = prdb.NewProduct("5")

You update the object like this:

prd.ProductName = "New Product Name"
prdb.Update(prd)

It's up to you to decide which approach you prefer to use.

The samples in this article utilize no-code scenarios, and admittedly simple scenarios. Typically, you would want to edit data before it goes back to the database, or you might want to modify data after you retrieve it from the database and before you display it. Both the FormView and the DetailsView have rich event models that let you step into the processing cycle and manipulate your data. That event model will let you insert your own update code for those situations when you need to retrieve data from more than one table. The FormView and DetailsView also have extensive formatting options that let you control the appearance of the controls using either Cascading Style Sheets or ASP.NET's themes and skins. Regardless of the kinds of applications you need to create, you need to write only the code that your particular application needs and leave the standard processing to the ASP.NET databinding controls.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

Subscribe on YouTube