Share via


Server-side ASP .NET Data Binding
Dino Esposito
Download the code for this article: Cutting0103.exe (39KB)
Browse the code for this article at Code Center: ASP .NET Databinding

T

he concept of data binding was first introduced with Microsoft® Internet Explorer 4.0. Used in conjunction with Dynamic HTML (DHTML), it was a rather effective way to have a set of records asynchronously downloaded to the client. Through Internet Explorer 4.0 data binding, Web developers could issue remote calls to data providers and manipulate the returned data in its native formatâ€"the recordsetâ€"rather than plain text. Furthermore, a browser-specific declarative syntax allowed fields of the data source to be bound to HTML tags. In other words, Internet Explorer 4.0 data binding was the Web counterpart of Visual Basic® data-bound controls.
      Data binding clearly has a number of advantages. For example, it allows you to get records asynchronously. In other words, getting records is not tied to page loading. As a result, the whole page is more responsive and loads more quickly. Data binding results in fewer trips to the server, since you normally won't need to interact with the server once the requested data has been downloaded.
      Being able to manipulate data as recordsets rather than plain text allows you to build quite complex client-side functionality such as filtered views, sorting, user-specific templates, and better search. There are two key factors enabling this new range of functionality: extensive use of DHTML and the fact that you hold the data in a smart memory format. For example, there's no need for extra round-trips to have data sorted by a certain field. There's no need to involve the server just to filter records by a key.
      Once the page is up and running, any other (read-only) actionâ€"even the most complexâ€"takes place locally, with indisputable benefits for both the user and the server. From this point of view, data binding is a load-balancing technology that divides the work equally between a feature-rich client and the server. In this context, the DHTML-specific ability to refresh the page is a critical element of the binding.

So What's the Problem?

      By design, this flavor of data binding must rely on special browser capabilities. The Internet Explorer 4.0 data binding architecture utilizes an ActiveX® control as the bridge between the Web page and the server-side component accomplishing the actual data access. In this configuration, COM support from the browser is an absolute necessity. However, this piece of the architecture could have been easily implemented through the combined use of a Java-language applet and a Web server-specific stub. Although not specifically targeted to data access, Remote Scripting works the same way. (See Cutting Edge in the January 2000 issue of Microsoft Internet Developer for more details.)
      No matter which way you choose to bring data down to the client, you still have to manipulate the data as an object and update the user interface accordingly. ECMAScript objects could be used as a less powerful, but effective and widely supported alternative to COM objects.
      What you won't find yet on a large number of browsers is decent support for page refreshâ€"a key feature of DHTML. So at the end of the day, Internet Explorer 4.0-style data binding was anything but a cross-browser solution. No matter what other good and bad features a Web technology may have, dependence on a specific browser can still prevent wide acceptance.
      Another aspect of Internet Explorer 4.0 data binding that proved to be more intriguing than practical was the aforementioned declarative syntax. Being able to automatically bind HTML tags with record fields, as illustrated here, is great.

          <SPAN DATASRC=#rds DATAFLD=FirstName>
          
</SPAN> &nbsp;
<SPAN DATASRC=#rds DATAFLD=LastName>
</SPAN>

 

However, how often do you need such a direct binding? What if you have to preprocess that content before displaying it? What if you have to populate a table, picking up only the records that match a certain condition? What if you don't want to show all the recordset columns? You can do it, but your pages would end up containing lots of script code, looking like client-side ASP pages, and losing compatibility with all browsers other than Internet Explorer 4.0 and higher. For many developers this cost isn't worth it.

Old-style, Server-side Data Binding

      The next natural step is porting the concept of data binding to the server. You lose the benefits of extreme DHTML, but gain the ability to create pages that can be viewed through a much larger number of browsers. But is data binding really beneficial when used on the server? The advantage is in the way you write your server-side code. Flexibility, reusability, and easier maintenance are the assets, while performance remains the primary concern. Before the advent of ASP .NET, COM was the only way to set up server-side data binding. Both scripted Windows Script Components (WSC) and compiled ASP COM objects can generate HTML code on the server that easily accesses data sources. (See Cutting Edge in the December 1999 issue of Microsoft Internet Developer for details and sample code.) However, COM introduces a separate processing layer with its own performance costs. Although they're perfectly capable of working together, ASP and COM remain two distinct layers of code.
      The same component-based model that COM encouraged in recent years is now implemented in a completely different way in ASP .NET, providing a mix of ease of use and greatly improved performance.

ASP .NET Simple Data Binding

      ASP .NET supplies data binding services through a family of data-bound server-side controls. You can group these controls in two distinct subgroups: text controls and list controls. Text controls include textboxes, buttons, images, and so on. They expose a Text and/or a Value property that you can automatically set with content coming from a data source. Figure 1 contains a brief description of all the available list controls. While you can use data binding to set the caption of buttons or the content of a textbox, the most effective use of this technique is through controls that can display tables of records. However, in ASP .NET textboxes, labels, buttons, and data-bound noncontainer controls in general boast a very nice feature: their text can be set as the result of an expression.
      Consider the typical way you would fill a data-bound listbox in ASP .NET.

          oCMD = new SQLDataSetCommand(strCmd, strConn);
          
DataSet oDS = new DataSet();
oCMD.FillDataSet(oDS, "EmployeesList");

//Emp is an ASP .NET listbox
Emp.DataSource = oDS.Tables["EmployeesList"].DefaultView;
Emp.SelectedIndex = 0;
Emp.DataTextField = "lastname";
Page.DataBind();

 

The first lines create an ADO .NET dataset by executing a command against a SQL Server™ connection. A dataset is a collection of named data tables. A data table roughly corresponds to an ActiveX Data Objects (ADO) recordset. This code contains a data table called EmployeesList.
      In the second part of the code, you just associate the Emp object with an <asp:ListBox> element:

          <asp:ListBox id=Emp runat="server" />
          

 

The DataSource property does the magic. The DataTextField property contains the name of the data table column to be used to feed the control.
      There's no way for you to automatically combine two or more fields in a listbox or a dropdown list control. DataTextField must match the name of one of the columns. Textboxes and labels, on the other hand, can show content produced by an expression computed using data fields. You use a special syntax for this. The following code shows how the content of a label can automatically reflect the currently selected item in a dropdown list control.

          <asp:dropdownlist runat=server id=DDList 
          
autopostback=True />
<asp:label runat=server id=Statusbar
text='<%# "Selected: DDList.SelectedItem.Text %>'
/>

 

      The Text property is not simply the property identifying the selected item. As you can see, it can be an expression where you combine literals, data-bound information, and values. In order to let the runtime know the value of Text (and also the value of an element attribute in general), the code must use the following special convention:

          <% # ...code here... %>
          

 

      If the code includes quotes, then use single quotes to wrap it all. Not quoting the whole text is not a problem when it comes to running the code, but prevents Visual Studio .NET (at least in Beta 1) from properly switching between the HTML and design views.
      Data-bound controls can expose their content through the Text and Value properties. Both properties can be associated with external data. You'll use the DataTextField property to bind the Text property and DataValueField for Value. Figure 2 shows the code behind the controls in Figure 3.

Figure 3 Dynamically Updated Label
Figure 3 Dynamically Updated Label

      As mentioned earlier, you cannot fill up a dropdown list or a listbox with the result of an evaluated expression. For example, the Northwind database stores first and last names for each employee in distinct fields. What if you want to insert the full name of all the employees in a dropdown list control? With plain data binding this just can't happen, unless you ask the T-SQL engine to return an extra computed column formatted at your leisure. For example, instead of a simpler

          SELECT employeeid, firstname, lastname 
          
FROM Employees

 

use

          SELECT employeeid, 
          
firstName + ' ' + lastName AS 'EmployeeName'
FROM Employees

 

where the EmployeeName column contains just the first and last name separated by a blank. The sample code in Figure 2 also proves how handy the distinction between DataTextField and DataValueField can be. While showing the first and last name of an employee certainly makes the application much more user-friendly, that is not necessarily enough to identify a person uniquely. In Northwind, an employee is characterized by his employee ID. So if you need to select a name for some sort of further processing, associate DataTextField with the display column, and DataValueField with the primary key.

          EmpList.DataSource = ...; 
          
EmpList.DataTextField = "EmployeeName";
EmpList.DataValueField = "employeeid";

 

      If you do this, it's really easy to get the unique value for any selected item:

          <asp:dropdownlist id=EmpList runat=server 
          
autopostback=True />
<asp:Label id=EmpID runat=server
text='<%# "Employee ID: " +
EmpList.SelectedItem.Value %>'
/>

 

The Data Source

      ASP .NET data binding can associate controls with a variety of sources. You can bind control properties with data table columns. In addition, you can get data from particular, filtered views of data tables. XML and managed arrays are also excellent sources for ASP .NET data binding. There are only two types of sources: arrays and data tables. XML, in fact, can be considered a special case of data tables. In terms of syntax, a data source can be anything of type ICollection. To manage collections, remember to always import the System.Data namespace in your code.
      You cannot bind an XML string as is; you need to load it first as a data table into a DataSet. You can read XML data and schema separately or from the same file. Use the DataSet's ReadXml method to read XML schema and data into the DataSet. You should use ReadXmlSchema and ReadXmlData to load them from distinct sources or at a different time.
      So far I've explained some details of simple data binding without explicitly mentioning the page method that enables ASP .NET data binding. This method is called DataBind, and is defined in the Control class exposed by the System.Web.UI namespace. DataBind causes data binding to occur on the invoked control and all of its child controls. It must be called in order to enable the automatic association between controls and data fields. You can call DataBind for each of the involved controls or for all of those belonging to the page. In the former case, you prefix the method name with the name of the specific control. In the latter, just use the following code.

          Page.DataBind();
          

 

      Speaking of ASP .NET pages, there's another interesting aspect that needs further explanation. In Figure 2, you may have noticed a weird test at the very beginning of the Page_Load event.

          <script runat=server>
          
public void Page_Load(Object Sender, EventArgs e)
{
if (!Page.IsPostBack)
{
// bind controls to fields
}
Page.DataBind();
}
</script>

 

The Page_Load procedure handles the Load event, which reaches the control whenever it should perform any work that needs to occur for each page request. ASP .NET introduces a feature called postback events. This is a system-provided feature allowing applications to handle events on the server and then post back the modified page to the browser.
      For example, in Figure 3 the label near the dropdown list is regularly updated to reflect the value of the selected item. This also works under old versions of Netscape's browsers, including Communicator 4.6. While the effect is much the same as DHTML, it requires a round-trip to the server. The page is completely reprocessed, but the ASP .NET runtime ensures it is returned to the client with only the necessary changes. The current status of existing controls is retained unless you reset the data-bound properties. The IsPostBack property informs you whether the page is going to be processed for the first time or as a result of a postback event. In the latter case, you just need to refresh the bindings by calling DataBind. Remember to call the DataBind method whenever you want the data-bound content to be refreshed. This normally happens during page loading and in response to some page-level events.
      Postback events are one of the most intriguing features of ASP .NET. What makes them even more attractive is that they've been built with code that you can borrow right now to improve your ASP pages. I plan to cover ASP .NET postback events in an upcoming column.

Other List-bound Controls

      Performance is certainly not the key advantage of server-side data binding. Any interaction, in fact, requires a round-trip to the server to refresh the page. On the other hand, this ensures total browser compatibility. By the time the final version of Visual Studio .NET ships, though, you can expect to see the browser-targeting feature shown in Figure 4 working properly.

Figure 4 Browser Targeting
Figure 4 Browser Targeting

By then, in fact, you should be able to select the target schema for your HTML code. The selected schema is then inserted in the HTML page as a <meta> tag:

          <meta name=vs_targetSchema content="HTML 4.0">
          

 

This information might help the ASP .NET runtime generate DHTML code for postback events if, in fact, you're targeting an uplevel browser.
      Server-side data binding is helpful because it saves you from writing long, tedious pieces of code. Can you think of anything more boring than filling a list of <option> tags by hand? It is a task you'll want to pass on to someone else, whether it's done in plain HTML code or a simple script loop. I admit that in some of my earlier data access components I often slipstreamed a few extra methods to return recordsets already formatted as an option list or a checkbox list. Imagine my surprise when I realized that the dropdown list control works just this way, and all that I had to do was declare it in my server-side pages.
      ASP .NET also features the RadioButtonList and CheckBoxList controls to transform a set of records, or an array list, in checkboxes or radio buttons.

          <asp:RadioButtonList id=RBEmployees 
          
repeatcolumns=2 runat="server" />
<asp:CheckBoxList id=CBEmployees
repeatcolumns=2 runat="server" />

 

The DataGrid Control

      One extremely common use of ASP has been to show a table of records that uses HTML table tags to emulate rich UI controls like DataGrids over the Web. In plain old HTML, a DataGrid can be created using a <table> element. Extra functions such as sorting, coloring, paging, even item selection, are implemented through script and/or DHTML. Once again, the key benefit of ASP .NET is ease of use, and this is evident in the new DataGrid control. Want to create a list of records with alternate colors, like the one in Figure 5?

Figure 5 Records with Alternating Colors
Figure 5 Records with Alternating Colors

It's a snap with the source code shown in Figure 6 and it even works in other browsers. The core part is in the following few lines:

          <asp:DataGrid id=DataGrid1 runat="server" 
          
ForeColor="Black" height="287px" width="411px"
font-names="Verdana" font-size="Smaller">
•••
</asp:DataGrid>

 

      The asp:DataGrid control dynamically expands to a table and includes a number of features that let you customize the appearance and enrich the overall functionality of the control. The content of the DataGrid is set through its DataSource property and points to any collection of homogeneous data, as explained previously.
      The DataGrid, as well as other list-bound controls, holds a collection of items. This collection can be programmatically accessed through the Items member. Any item is represented by a DataGridItem object. There may be several different types of items in a DataGrid control: header, footer, separator, normal, and alternate items, plus a couple of very special items that enable advanced operations such as selection and in-place editing. Figure 7 explains them all. Those item types are grouped in the ListItemType enumeration. When the DataGrid control is populated or re-bound, it just accesses the data source and refreshes its internal collection of items.
      The UI of a DataGrid can be improved through a number of graphical properties. Figure 8 summarizes those properties and all the collections you can take advantage of when working with grids. Some of these properties can be set with the tag attributes shown in Figure 9.
      I've set both DataGrid-specific properties using tag attributes (specifically, CellPadding) and item-specific properties such as AlternatingItemStyle-backcolor. Visual Studio .NET also allows a slightly different syntax, at least for item styles (see Figure 10). The <property> child tag wraps an <asp:TableItemStyle> tag that you can use to declaratively set attributes for the specified property. This verbose code is automatically generated by Visual Studio .NET (Beta 1) when you drop a DataGrid control on a Web form.

Paging within a DataGrid

      ASP .NET DataGrids rely heavily on the HTML <table> tag. However, this element doesn't have the built-in capability to scroll its content in a fixed area. All of the rows you add end up being displayed in the main document. For this reason, thousands of ASP developers have had to build their own custom, reusable mechanisms to simulate paging. Basically, they decide the maximum number of rows a table can contain and add buttons or hyperlink controls at the bottom of the table to allow users to navigate. ADO 2.1 helped a lot by making recordsets easily pageable so that you didn't have to worry about getting the right subset of records for a given page.
      The ASP .NET DataGrid component makes all this just a faint memory. If you want to scroll between pages of dynamically determined sizes, just tell the DataGrid to do so! Figure 11 shows such a page in action. For the corresponding source code, see Figure 12. To enable paging, you have several tasks to sort out.

Figure 11 Selecting Number of Page Buttons
Figure 11 Selecting Number of Page Buttons

      First, enable paging at the DataGrid level by setting the AllowPaging property to True. Assign the desired value to PageSize to let the control know how many rows you want to see for each page. Second, make sure the data source is properly refreshed whenever you scroll back and forth. The DataGrid raises an event each time you click on the pager to jump to a certain page. The event is OnPageIndexChanged and, of course, you need to handle it as well.
      However, the most important thing to remember is that you have to design your code in a manner that suits the paging. As I mentioned earlier, paging always requires you to rebind to the data source each time a new page is about to be shown. Caching the DataSet, in this case, is not a functional technique. The best thing to do is package the code that creates the data source object in an easily callable function. For example:

          private ICollection 
          
CreateDataSource(String strConn, String strCmd)
{
SQLDataSetCommand oCMD;
oCMD = new SQLDataSetCommand(strCmd, strConn);
DataSet oDS = new DataSet();
oCMD.FillDataSet(oDS, "EmployeesList");

// Return a data table
return oDS.Tables["EmployeesList"].DefaultView;
}

 

This function can be called from a variety of places. In particular, you can call it from the Page_Load event while the page is being prepared, or in response to any event that affects the structure and the content of the page. For example, in the code being discussed, I didn't use the Page_Load handler; I used the onclick handler of the pushbutton. In addition, CreateDataSource must be called from the delegate that manages the page change event within the grid.

          public void 
          
Grid_Change(Object Sender, DataGridPageChangedEventArgs e)
{
// set the connection string

ICollection ds = CreateDataSource
(strConn, queryText.Text);

DataGrid1.DataSource = ds;
DataGrid1.DataBind();
}

 

      The function is linked to the <asp:DataGrid> element via the OnPageIndexChanged delegate.

          <asp:datagrid id=DataGrid1 runat="server" 
          
ForeColor="Black"
font-size="Smaller"
font-names="Verdana"
width="411px"
AllowPaging="True"
OnPageIndexChanged="Grid_Change">

 

      There are many other, not strictly graphical, characteristics you can change for a paged DataGrid. The most typical one is certainly the number of records for each page. The property to use is PageSize. Another property, CurrentPageIndex, lets you know about the currently displayed page. Don't forget that if you take the page size from a textbox, you must first convert it to an integer type:

          DataGrid1.PageSize =      
          
IndicatePageSize.Text.ToInt32();

 

      Other aspects you want to make configurable at runtime have to do with the DataGrid's pager. The pager element is a button bar that it is used for rendering the user interface elements that actually let you move across pages. At this time, the pager is just a <TR> element placed somewhere around the DataGrid. It contains a number of anchor tags pointing to the server-side code to refresh the data source. The following HTML code evaluates to the pager for the page in Figure 11.

          <tr>
          
<td colspan="3">
<span>1</span>&nbsp;
<a href="javascript:__doPostBack(...)">2</a>&nbsp;
<a href="javascript:__doPostBack(...)">3</a>
</td>
</tr>

 

The element that identifies the current pageâ€"the only <span> tag in the previous codeâ€"is not clickable.
      By clicking on the other hyperlink buttons (2 and 3) you originate a postback event. The control passes to the ASP .NET code on the server. The first piece of code to run is the Page_Load handler. Then, it's the delegates turn for the grid page change event. The code for the __doPostBack function is added to any HTML page using postback events and consists merely of a form submission. The arguments contain information moving from the client to the server, formatted according to a proprietary protocol.

Customizing the Pager

      The pager can be customized to some extent. You can decide on the font, background and foreground color, position (top, bottom, or both), alignment, and whether the scrollable pages must be indicated on the bar through numbers or through simpler symbols such as < and > to indicate back and forward.
      The page shown in Figure 11 allows you to switch between the two modes by checking and unchecking the checkbox. The following is the code running behind the page:

          if (IndicatePageNumbers.Checked) 
          
DataGrid1.PagerStyle.Mode = PagerMode.NumericPages;
else
DataGrid1.PagerStyle.Mode = PagerMode.NextPrev;

 

      If you choose the NextPrev mode, then two angle brackets appear, allowing you to move only one page back or one page forward. When working in this mode, the DataGrid accepts custom text for both the Previous and the Next button:

          DataGrid1.PagerStyle.NextPageText = "Next"; 
          
DataGrid1.PagerStyle.PrevPageText = "Previous";

 

      Notice that you can also apply some simple HTML formatting here. You can draw the text in bold or even replace it with an image:

          DataGrid1.PagerStyle.PrevPageText = "<img src=prev.gif>";
          
DataGrid1.PagerStyle.NextPageText = "<b>Next�</b>";

 

Using images is one of the key hooks you have for influencing the appearance of the paging buttons. You can also use the CssClass property to assign special styles to the table row containing the pager.
      The text you assign to PrevPageText and NextPageText is inserted within the body of the anchor tag:

          <a href=...>text goes here</a>
          

 

      If you want to take customization to the limit, try assigning the following:

          <span class=YourCssClass></span>
          

 

The <span> tag is necessary because you need an inline tag. Using a <div>, in fact, would place the text on a new row. YourCssClass is supposed to be the name of a group of Cascading Style Sheet (CSS) settings. In this way, you can apply your own UI settings directly to the pager buttons. This approach doesn't work if you have numeric buttons. In addition, normal CSS styles don't allow advanced functionality such as highlighting the buttons when the mouse passes over them.
      A more effective solution is using Internet Explorer 5.0 behaviors to provide the core functionality you need and hooking the OnItemCreated DataGrid event to get your hands on the various buttons created within the pager, regardless of the DataGrid working mode. Using behaviors in this case doesn't violate the portability of the solution. Behaviors, in fact, can be attached to an element through a CSS style. Downlevel browsers that do not support this feature will just ignore it. The code in Figure 13 shows how to associate a behavior with the hyperlink buttons of a pager. This code degrades gracefully on Netscape's browsers, where the feature is just ignored. In this month's source archive you'll find working code that demonstrates this.

Figure 14 Additional Pages
Figure 14 Additional Pages

      When the pager works in numeric mode, you can decide the maximum number of buttons allowed. If the number of pages exceeds the maximum number of buttons, then the control displays a final "�" button which, when clicked, causes the pager bar to refresh and show a second page of buttons (see Figure 14). The property governing this feature is PageButtonCount.
      If you don't want to provide your users with the ability to change DataGrid settings on the fly, then you can just exploit the Property Builder dialog (see Figure 15) to set paging and other settings at design time through a comfortable environment.

Figure 15 Property Builder Dialog
Figure 15 Property Builder Dialog

Custom Paging

      The DataGrid normally calculates the ordinal position of the first record to show in the opening page based on the values of CurrentPageIndex and the PageSize. Given the page's size and the current page, it determines the record to start from, like this:

          startIndex = (grid.CurrentPageIndex * grid.PageSize);
          

 

      You can implement your own mechanism for paging by setting AllowCustomPaging to True. When this property is set to True, the DataGrid always displays the first rows according to the value of PageSize. Of course, there's no need to activate custom paging if you don't really need it. If you do, then it's up to you to provide a data source whose first PageSize records match the ones you want to be displayed. Only when custom paging is on can you manually set the total number of records in the DataSet. You do this through the VirtualItemCount property.

Coming Soon

      Using ASP .NET DataGrids is really easy, but in this month's column I just scratched the surface. Starting next month, I'll take the customization level to the limit and show you how to bind columns, giving them individual settings. In addition, I'll cover even more advanced stuff like item selection and in-place editing. Stay tuned!


Send questions and comments for Dino to cutting@microsoft.com.

Dino Esposito is a trainer and consultant based in Rome, Italy. Author of several books for Wrox Press, he now spends most of his time teaching classes on ASP .NET and ADO .NET for Wintellect (https://www.wintellect.com). Get in touch with Dino at dinoe@wintellect.com.

From the March 2001 issue of MSDN Magazine