Raise your hand if you’ve written a web application that couldn’t be cataloged as a data-driven application. I’m betting that you didn’t raise your hand, because the definition of a data-driven application is quite blurred and flexible enough to incorporate nearly everything. Most web applications get data from the middle tier and display it in a nice-looking HTML layout. This common pattern brought framework authors to introduce a semi-automatic mechanism to facilitate the display of data into well-defined UI components. This mechanism is generally known as data binding.
Data binding is the process that retrieves data from a given data source and associates it to attributes of UI elements. The data source can be virtually any software component that exposes data, including in-memory elements (e.g., arrays, collections), properties of live objects, and stream-based data flows (e.g., result sets from database queries).
I’ll cover key aspects of data binding in ASP.NET Web Forms applications. I’ll start with simple forms of data binding, then discuss the various types of data source controls and data bound controls.
Simple Data Binding Expressions
At its core, ASP.NET data binding is the association between the property of a server control and a server-side calculated value. This association is established at compile time and buried in the folds of the code that ASP.NET auto-generates when it compiles a .aspx page.
Take a look at the code in Listing 1 (below). This code is supposed to use a Label control to display the city that a user selects from a drop-down list. The code compiles just fine, but it doesn’t work. You don’t receive any errors, but nothing happens.
When you use data binding expressions with ASP.NET controls, behaviors are attached to the control’s life cycle through the DataBinding event handler. For example, the Label control in Listing 1 will update its Text property only when it receives a DataBinding event. If there’s no such event, there’s no update. What triggers this event? It’s the page—if you properly instruct it to do so.
As a developer, you have the power to trigger data binding events for individual controls (including child controls) or for all controls within a page. All you do is invoke the DataBind method. Both controls and ASP.NET pages feature this method. To data-bind all the controls in a page, you need code like that in Listing 2 (below).
Calling the DataBind method has the effect of triggering the DataBinding event for all the controls in scope. Subsequently, any controls with a <%# … %> data binding expression will update themselves. Web Figure 1 shows the results from calling the DataBind method on the page created with the code in Listing 1 (below).
If you don’t want to call DataBind on each page in which you intend to use data binding, you can craft a new page class that overrides the OnLoad method. The code in Listing 3 (below) shows how. This code creates the DataBindingPage class, which loads the DataBind method instead of the OnLoad method when the Load event is triggered. Then, all you need to do is derive each page from the DataBindingPage class by using the following code:
BindingExpressions : DataBindingPage
// Page-specific code goes here.
Done this way, data binding in ASP.NET Web Forms is similar to data binding in WinForms. You don’t need to explicitly trigger data binding events; you just need to make sure any bindable element is connected to the correct data source. Calling DataBind (even multiple times) doesn’t really have an impact on performance, because all it does internally is play with Boolean flags. During rendering, these flags are checked to determine whether the programmer has explicitly ordered data binding.
There’s a subtle difference between code blocks (<%= … %>) and data binding expressions (<%# … %>). Code blocks can’t be used with ASP.NET controls; they can only be used with plain HTML markup. Data binding expressions can only be used with ASP.NET control markup and require a call to the DataBind method.
Data Source Controls
Data source controls are designed to interact with data bound controls and hide the complexity of the manual data binding process. Specifically, data source controls provide data to data bound controls. They also support data bound controls in the execution of common operations, such as insertions, deletions, sorting, and updates.
There are quite a few data source controls available. The most commonly used controls are:
- ObjectDataSource. This control allows binding to custom .NET business objects that manage data. You need to follow a specific design pattern and include specific elements. For example, you might need to include a parameterless constructor and methods that behave in a certain way for easy mapping to Create, Read, Update, and Delete (CRUD) actions.
- SqlDataSource. This control allows binding to SQL Server data and other OLE DB and ODBC data sources. You specify the name of the provider and the connection string through properties. You specify CRUD actions through SQL parameterized code.
- EntityDataSource. This control allows binding to the results of an Entity Framework query.
- LinqDataSource. This control allows binding to the results of a Language-Integrated Query (LINQ) to SQL query.
In ASP.NET, you can use a variety of classes as data sources. You aren’t limited to classes that deal with database content, such as DataReader or DataTable. In ASP.NET, any object that exposes the IEnumerable interface is a valid bindable data source. The IEnumerable interface defines the minimal API (i.e., the interface with the smallest number of members) to enumerate the contents of a data source, as in
The IEnumerable interface represents the lowest-level binding interface. In the real world, you’ll be using more specific classes that can enumerate their content through the IEnumerable interface or richer interfaces derived from IEnumerable, such as ICollection and IList. In particular, you can bind a Web control to the following classes:
- Collections, including dictionaries, arrays, and custom data put into your own collections
- ADO.NET container classes, such as DataSet, DataTable, and DataView
- ADO.NET data readers
- Any IQueryable objects that result from the execution of a LINQ query on any LINQ-compatible data source
Data binding through collections requires specific support from ASP.NET controls. In other words, you can’t bind the content of a query to a text box or check box. Instead, you need to bind the content to a control that can display a collection of data, such as a drop-down list, list box, or data grid. In other words, you need to bind to a list data bound control.
Data Bound Controls
There are two main families of data bound controls—one rooted in the base class of BaseDataList and the other rooted in the base class of BaseDataBoundControl. Figure 1 shows the various data bound controls. This diagram doesn’t extend in a uniform manner because backward compatibility was kept in mind when controls were added to the various versions of ASP.NET.
Figure 1: Data bound controls and their properties
The main difference between the various types of data bound controls lies in which template they use to represent a single bound data item. For example, the CheckBoxList control renders a check box for each bound item, whereas the BulletedList control renders a bullet point for each item. However, all the data bound controls share some common features, such as a common set of properties.
Every data bound control implements two properties: DataSource and DataSourceID. Each control also has an additional set of data binding properties. The precise list depends on the position of the control in the hierarchy of data bound controls. Table 1 defines the commonly used properties.
Table 1: Commonly Used Properties of Data Bound Controls
DataSourceID and DataSource are mutually exclusive properties. If both are set, you get an invalid operation exception at runtime. DataSourceID is of type string and is typically set declaratively in the .aspx layout. (It was introduced just to facilitate declarative programming.) The DataSource property is generally set programmatically. However, nothing prevents you from adopting a kind of declarative approach for it using the code
DataSource="<%# GetData() %>" />
In this case, GetData is a public method of the page’s code-behind class.
Note that when DataSourceID is bound to a data source control, you don’t need to call DataBind. You still need to call DataBind in the page if you trigger data binding through DataSource, regardless of whether you declaratively or programmatically set the property.
How to Use the Controls
The code in Listing 4 (below) demonstrates how to use data source controls and data bound controls. Web Figure 2 shows the resulting page. As you can see in Listing 4, the page has two data bound controls: DropDownList and GridView. Both controls share a common behavior. They get a data source and iterate through the bound content to output markup. Let’s take a look at the DropDownList control. What I’m saying for this list control applies to other list controls (i.e., CheckBoxList, RadioButtonList, BulletedList, and ListBox) as well.
Web Figure 2: Results from the code in Listing 4
The DropDownList control gets the data to display through either the DataSource or DataSourceID property. In Listing 4, it gets data through the DataSource property of the ObjectDataSource control. The ObjectDataSource instance refers to an object of type Simple.DAL.CustomerRepository. This object is part of the application’s data access layer, which you write. To make it work as a data feed for the list control, you need to specify the name of a method that returns a bindable data source. In this case, the method is GetCountries, as callout A in Listing 4 shows. GetCountries doesn’t require any parameters, and it returns a list of objects. You pass this method’s name to the data source control’s SelectMethod property when you want that method to select data for a data binding operation. Whatever the method returns is automatically bound to the drop-down list. In this case, the method returns a list of strings, which is immediately bindable.
If the method will be returning a collection of objects, you use the returned objects’ properties to set the display text and value for each item the user will see in the drop-down list. In particular, you set the DropDownList control’s DataTextField and DataValueField properties to the bound objects’ property names to provide the text and value of each item in the drop-down list.
Now let’s take a quick look at how to use the SqlDataSource control
"SELECT ... FROM MyTable">
In this case, you set the DropDownList control’s DataTextField and DataValueField properties to the names of the columns picked up by the query.
If you aren’t using a data source control, you can perform binding in the postback event, as the code in Listing 5 shows. In this code, the Name and Id are properties in the queried data.
Templated Data Bound Controls
Two popular data bound controls are ListView and GridView. They let you display data laid out according to customizable templates. The ListView control is the most flexible when it comes to generating a multicolumn layout. The GridView control provides a table-based rendering and is typically used for displaying raw or editable data. With the ListView control, you can easily simulate the GridView control. However, if all you need is a table, it’s quicker to use the GridView control.
Let’s take a closer look at the ListView control. It uses the same data binding mechanics as the other data bound controls, so I’ll focus on how to fill its template.
The ListView control in Listing 6 (below) gets its data from an ObjectDataSource control. There are no properties that limit you to displaying a fixed number of fields. You can use and combine any fields returned by the query. The ListView control’s ItemTemplate template is where you arrange the structure to repeat for each bound item. In this case, you’ll get the company name and country for each item, as callout A in Listing 6 shows.
When using bound data in a template, you use a slightly different binding expression. In this case, the expression is
Text='<%# Eval("CompanyName") %>' />
The Eval method is exposed by the Page class and evaluates the provided expression in the current context. The current context is the object being bound at the current step of the iteration. In other words, you’ll get the value of the CompanyName property on the data item being rendered. You get an exception if no such property exists on the item. Optionally, you can use the ItemSeparatorTemplate template. If specified, this template is rendered in between two successive ItemTemplate templates.
The GridView control is characterized by a variable number of columns. One row is rendered for each bound data item. The GridView control supports the types of columns listed in Table 2. The GridView control in Listing 4 uses the default BoundField column type to create a table containing each customer’s ID, company, and city in plain text. The GridView control in Listing 7 (below) shows how to use the ImageField and TemplateField column types. The first cell in the row shows an image. The other cell contains the HTML markup, which mixes together five data item properties in a single cell.
Table 2: Columns Supported by the GridView Control
GridView Control Paging
The ability to scroll through a large data set is an important but challenging feature for modern distributed applications. An effective paging mechanism lets customers interact with a database without holding resources. The bad news is that creating this paging mechanism is entirely up to you. The good news is that you can easily enable paging on a GridView control. All you do is set the AllowPaging property to true with code such as
The table then automatically displays a pager bar and prepares to detect a user’s pager button clicks.
When a user clicks to see a new page, the page posts back and GridView traps the event and tracks the new page index. After that, GridView fires a refresh of the binding. Your data source control (or your code if you’re using the DataSource property) is responsible for binding the correct page data set. There are various strategies to do this, including:
- Running the query for the specific page every time
- Caching the results for the page for a few seconds to save bandwidth and processing overhead
- Sending the entire data set to the web server once, caching it, and binding it to the control, along with the page index to select; in this case, the GridView control will automatically pick up only the segment of items required
In general, I recommend using the last option, except when the data set is particularly large or the database being queried is subject to very frequent changes. What constitutes a “particularly large” data set? A data set containing up to 10,000 records is fine most of the time, unless the size of a single record is several kilobytes. With reasonably small records, you can even raise the maximum number of cached records to 100,000. What constitutes “very frequent changes”? When you gather requirements, most stakeholders will say that they absolutely need up-to-date data. In reality, virtually all applications (with very few exceptions) can blissfully bear 1 or 2 seconds of caching. In high-traffic sites, you can gain a lot of scalability with this imperceptible delay.
The Next Step
Data-driven applications need to be bound to data. Data binding is simply a specific API that makes writing such applications simpler and quicker for developers. Data binding code can be quite repetitive to write and thus prone to errors. The ASP.NET Web Forms model features many tools to simplify the development of data bound applications. Data bound server controls and special syntax features (i.e., data binding expressions) are at its foundation. Now that you know how data binding works in Web Forms applications and which tools you can leverage to customize your experience, the next step is to try using those controls when you’re designing .aspx pages.
Listing 1: Code That Displays the City Selected from a Drop-Down List
<asp:Label runat="server" ID="lblCity"
Text='<%# "<b>You selected: </b>" + Cities.SelectedValue %>' />
Listing 2: Code That Adds a Data Binding Expression to a Page
protected void Page_Load(object sender, EventArgs e)
// Replace "this" with a control ID if you want to data-bind only
// that specific control and its children.
Listing 3: Code That Creates a Page Class
public class DataBindingPage : Page
protected override void OnLoad(System.EventArgs e)
Listing 4: Code That Creates a Page in Which Data Source Controls Feed Data Bound Controls
<asp:DropDownList ID="Countries" runat="server"
<asp:ObjectDataSource ID="ObjectDataSource2" runat="server"
// BEGIN CALLOUT A
// END CALLOUT A
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
ControlID="Countries" PropertyName="SelectedValue" />
<asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1"
<asp:BoundField DataField="CustomerId" HeaderText="" />
<asp:BoundField DataField="CompanyName" HeaderText="Company" />
<asp:BoundField DataField="City" HeaderText="City" />
Listing 5: Code That Performs Binding in the Postback Event
var data = GetDataViaSomeQuery();
DropDownLis1.DataSource = data;
DropDownList1.DataTextField = "Name";
DropDownList1.DataValueField = "Id";
Listing 6: Code That Uses a ListView Control
// BEGIN CALLOUT A
<asp:Label runat="server" ID="lblCompany"
Text='<%# Eval("CompanyName") %>' />
<asp:Label runat="server" ID="lblCountry"
Text='<%# Eval("Country") %>' />
// END CALLOUT A
Listing 7: Code That Uses a GridView Control
<b><%# Eval("titleofcourtesy") + " " +
Eval("lastname") + ", " +
Eval("firstname") %></b> <br />