Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]
.pdf
308 CHAPTER 9 ■ DATA BINDING
Figure 9-4. The bare-bones GridView
Of course, you can do a lot more to configure the appearance of the GridView and can use advanced features such as sorting, paging, and editing. You’ll learn about these features throughout this chapter and in the next chapter. You can also give your GridView a quick face-lift by choosing Auto Format from the GridView’s smart tag.
Binding to a DataView
You will encounter a few limitations when you bind directly to a DataReader. Because the DataReader is a forward-only cursor, you can’t bind your data to multiple controls. You also won’t have the ability to apply custom sorting and filtering criteria on the fly. Finally, unless you take care to code your page using generic interfaces such as IDataReader, you lock your code into the data provider you’re currently using, making it more difficult to modify or adapt your code in the future. To solve these problems, you can use the disconnected ADO.NET data objects.
If you fill a disconnected DataSet, you can bind that to one or more controls, and you can tailor the sorting and filtering criteria. The DataSet is also completely generic—no matter which data provider you use to fill your DataSet, the DataSet itself (and the data binding code) looks the same.
Technically, you never bind directly to a DataSet or DataTable object. Instead, you bind to a DataView object. A DataView represents a view of the data in a specific DataTable. That means the following:
grid.DataSource = dataTable; grid.DataBind();
is equivalent to this:
grid.DataSource = dataTable.DefaultView; grid.DataBind();
CHAPTER 9 ■ DATA BINDING |
309 |
It’s important to note that every DataTable includes a default DataView object that’s provided through the DataTable.DefaultView property. This sleight of hand allows you bind directly to the DataTable. If you do, ASP.NET actually uses the default DataView automatically. The default DataView doesn’t apply any sort order and doesn’t filter out any rows. If you want to tweak these settings, you can either configure the default DataView or create your own and explicitly bind it.
You can then use all the sorting and filtering techniques explained in Chapter 8.
Data Source Controls
In Chapter 7 and Chapter 8, you saw how you can directly connect to a database, execute a query, loop through the records in the result set, and display them on a page. In this chapter, you’ve already seen that you have a simpler option; with data binding, you can write your data access logic and then show the results in the page with no looping or control manipulation required. Now, it’s time to introduce another convenience—data source controls. With data source controls, you can avoid writing any data access code.
■Note As you’ll soon see, there’s often a gap between what you can do and what you should do. In most professional, large-scale applications, you’ll still need to write and fine-tune your data access code for optimum performance, data aggregation, error handling, logging, and so on. Even if you do, you can still use the data source controls—just don’t expect to escape without writing any code!
The data source controls include any control that implements the IDataSource interface. The .NET Framework includes the following data source controls:
SqlDataSource: This data source allows you to connect to any data source that has an ADO.NET data provider. This includes SQL Server, Oracle, and the OLE DB or ODBC data sources, as discussed in Chapter 7. When using this data source, you don’t need to write the data access code.
ObjectDataSource: This data source allows you to connect to a custom data access class, such as the one you saw in Chapter 8. This is the preferred approach for large-scale professional web applications.
XmlDataSource: This data source allows you to connect to an XML file. You’ll learn more in Chapter 12.
SiteMapDataSource: This data source allows you to connect to the Web.Sitemap file that describes the navigational structure of your website. You’ll learn more in Chapter 16.
You can find all the data source controls in the Data tab of the Toolbox in Visual Studio. Data source controls are new in ASP.NET 2.0, and it’s expected that more will become available, both from Microsoft and from third-party vendors.
When you drop a data source control onto your web page, it shows up as a gray box in Visual Studio. However, this box won’t appear when you run your web application and request the page (see Figure 9-5).
310 CHAPTER 9 ■ DATA BINDING
Figure 9-5. A data source control at design time and runtime
The Page Life Cycle with Data Binding
Data source controls can perform two key tasks:
•They can retrieve data from a data source and supply it to linked controls.
•They can update the data source when edits take place in linked controls.
In ASP.NET 1.x, creating data-bound pages was complicated because you needed to understand the page life cycle, or you risked binding the page at the wrong time. Of course, in ASP.NET 2.0 you still need to understand the basics of the page life cycle because you’ll run into situations where you need to work with or extend the data binding model. For example, you might want to add data or set a selected item in a control after it has been bound to the data source. Depending on the scenario, you might be able to respond to data source control events, but they aren’t always fired at the point you need to perform your logic.
Essentially, data binding tasks take place in this order:
1.The page object is created (based on the .aspx file).
2.The page life cycle begins, and the Page.Init and Page.Load events fire.
3.All other control events fire.
4.The data source controls perform any updates. If a row is being updated, the Updating and Updated events fire. If a row is being inserted, the Inserting and Inserted events fire. If a row is being deleted, the Deleting and Deleted events fire.
5.The Page.PreRender event fires.
6.The data source controls perform any queries and insert the retrieved data in the linked controls. The Selecting and Selected events fire at this point.
7.The page is rendered and disposed.
In the rest of this chapter, you’ll look in detail at the SqlDataSource and the ObjectDataSource and see how you can use both to enable a variety of data binding scenarios with the rich GridView control.
CHAPTER 9 ■ DATA BINDING |
311 |
■Tip Even if you plan to use the ObjectDataSource for binding your pages, you should begin by reading “The SqlDataSource” section, which will explain many of the basics about data source controls, including parameters, key fields, and two-way data binding.
The SqlDataSource
Data source controls turn up in the .aspx markup portion of your web page like ordinary controls. Here’s an example:
<asp:SqlDataSource ID="SqlDataSource1" runat="server" ... />
The SqlDataSource represents a database connection that uses an ADO.NET provider. However, this has a catch. The SqlDataSource needs a generic way to create the Connection, Command, and DataReader objects it requires. The only way this is possible is if your data provider includes a data provider factory, as discussed in Chapter 7. The factory has the responsibility of creating the provider-specific objects that the SqlDataSource needs in order to access the data source.
As you know, .NET ships with these four provider factories:
•System.Data.SqlClient
•System.Data.OracleClient
•System.Data.OleDb
•System.Data.Odbc
These are registered in the machine.config file, and as a result you can use any of them with the SqlDataSource. You choose a data source by setting the provider name. Here’s a SqlDataSource that connects to a SQL Server database:
<asp:SqlDataSource ProviderName="System.Data.SqlClient" ... />
The next step is to supply the required connection string—without it, you cannot make any connections. Although you can hard-code the connection string directly in the SqlDataSource tag, you should always place it in the <connectionStrings> section of the web.config file to guarantee greater flexibility and ensure you won’t inadvertently change the connection string, which minimizes the effectiveness of connection pooling.
For example, if you create this connection string:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <connectionStrings>
<add name="Northwind"
connectionString="Data Source=localhost;Initial Catalog=Northwind; Integrated Security=SSPI"/>
</connectionStrings>
...
</configuration>
you would specify it in the SqlDataSource using a $ expression like this:
<asp:SqlDataSource ConnectionString="<%$ ConnectionStrings:Northwind %>" ... />
Once you’ve specified the provider name and connection string, the next step is to add the query logic that the SqlDataSource will use when it connects to the database.
312 CHAPTER 9 ■ DATA BINDING
Selecting Records
You can use each SqlDataSource control you create to retrieve a single query. Optionally, you can also add corresponding commands for deleting, inserting, and updating rows. For example, one SqlDataSource is enough to query and update the Customers table in the Northwind database.
However, if you need to independently retrieve or update Customers and Orders information, you’ll need two SqlDataSource controls.
The SqlDataSource command logic is supplied through four properties: SelectCommand, InsertCommand, UpdateCommand, and DeleteCommand, each of which takes a string. The string you supply can be inline SQL (in which case the corresponding SelectCommandType, InsertCommandType, UpdateCommandType, or DeleteCommandType property should be Text, the default) or the name of a stored procedure (in which case the command type is StoredProcedure). You need to define commands only for the types of actions you want to perform. In other words,
if you’re using a data source for read-only access to a set of records, you need to define only the SelectCommand property.
■Note If you configure a command in the Properties window, you’ll see a property named SelectQuery instead of SelectCommand. The SelectQuery is actually a virtual property that’s displayed as a design-time convenience. When you edit the SelectQuery (by clicking the ellipsis next to the property name), you can use a special designer to write the command text (the SelectCommand) and add command parameters (the SelectParameters).
Here’s a complete SqlDataSource that defines a SELECT command for retrieving records from the Employees table:
<asp:SqlDataSource ID="sourceEmployees" runat="server" ProviderName="System.Data.SqlClient"
ConnectionString="<%$ ConnectionStrings:Northwind %>" SelectCommand= "SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees"/>
■Tip You can write the data source logic by hand or by using a design-time wizard that lets you create a connection and then create the command logic in a graphical query builder. To launch this tool, select the data source control, and choose Configure Data Source from the smart tag.
Once you’ve created the data source, you can reap the benefits—namely, the ability to bind your controls at design time, rather than writing logic in the event handler for the Page.Load event. Here’s how it works:
1.Select the data source control, and click Refresh Schema in the smart tag. This step triggers the data source control to connect to the database and retrieve the column information for your query. The advantage is this column information will now be available in handy dropdown boxes in the appropriate part of the Properties window. (If you don’t take this step, or the database isn’t available at design time, you’ll need to type in field names by hand.)
2.Add a ListBox to your form. Set the ListBox.DataSourceID property to the data source control. You can choose it from a drop-down list that shows all the data sources on the form (see Figure 9-6).
3.Set the ListBox.DataTextField to the column you want to display (in this case, choose EmployeeID). The list of fields should also be provided in a drop-down list (see Figure 9-6).
CHAPTER 9 ■ DATA BINDING |
313 |
Figure 9-6. Binding a list control to a data source field
4.You can use the same steps to bind a rich data control. Add a GridView to your page, and set the GridView.DataSourceID property to the same data source. You don’t need to set any field information, because the GridView can display multiple fields. You’ll see the column headings from your query appear on the design surface of your page immediately.
5.Run your page. Don’t worry about executing the command or calling DataBind() on the page—ASP.NET performs both of those tasks automatically. You’ll see a data-bound page like the one in Figure 9-7.
Clearly, the great advantage of the data source controls is that they allow you to configure data binding at design time, without writing tedious code. Even better, the results of your selections appear (to a limited degree) in the Visual Studio designer so you can get a better idea of what your form will look like.
Figure 9-7. A simple data-bound page with no code
314 CHAPTER 9 ■ DATA BINDING
Data Binding “Under the Hood”
As you learned earlier in this chapter, you can bind to a DataReader or a DataView. So, it’s worth asking, which approach does the SqlDataSource control use? It’s actually in your control, based on whether you set the DataSourceMode to SqlDataSourceMode.DataSet (the default) or to SqlDataSourceMode.DataReader. The DataSet mode is almost always better, because it supports advanced sorting, filtering, and caching settings that depend on the DataSet. All these features are disabled in the DataReader mode. However, you can use the DataReader mode with extremely large grids, as it’s more memory-efficient. That’s because the DataReader holds only one record in memory at a time—just long enough to copy the record’s information to the linked control. Both modes support binding to multiple controls. To understand why this is possible, you need to take a closer look at how the selection is performed.
If you profile your database, you’ll discover that by binding two controls to the same data source, you cause the query to be executed twice. On the other hand, if you bind the page manually, you have the ability to bind the same object to two different controls, which means you need to execute the query only once. Clearly, the SqlDataSource imposes a bit of unnecessary extra overhead here, but if you’re aware of it you can design accordingly. First, you should consider caching, which the SqlDataSource supports natively (see Chapter 11 for a full discussion). Second, realize that most of the time you won’t be binding more than one control to a data source. That’s because the rich data controls—the GridVew, DetailsView, and FormsView—have the ability to present multiple pieces of data in a flexible layout. If you use these controls, you’ll need to bind only one control, which allows you to steer clear of this limitation.
It’s also important to note that data binding is performed at the end of your web-page processing, just before the page is rendered. That means the Page.Load event will fire, followed by any control events, followed by the Page.PreRender event, and only then will the data binding take place. The data binding is performed on every postback (unless you redirect to another page). If you need to write code that springs into action after the data binding is complete, you can override the Page.OnPreRenderComplete() method. This method is called immediately after the PreRender stage but just before the view state is serialized and the actual HTML is rendered.
Parameterized Commands
In the previous example, the complete query was hard-coded. Often, you won’t have this flexibility. Instead, you’ll want to retrieve a subset of data, such as all the products in a given category or all the employees in a specific city.
The following example creates a master-details form using parameters. To create this example, you need two data sources. The first data source provides a list of cities (where various employees live). Here’s the definition for this SqlDataSource:
<asp:SqlDataSource ID="sourceEmployeeCities" runat="server" ProviderName="System.Data.SqlClient" ConnectionString="<%$ ConnectionStrings:Northwind %>" SelectCommand="SELECT DISTINCT City FROM Employees"> </asp:SqlDataSource>
This data source fills a drop-down list with city values:
<asp:DropDownList ID="lstCities" runat="server" DataSourceID="sourceEmployeeCities" DataTextField="City" AutoPostBack="True">
</asp:DropDownList>
The list control has automatic postback enabled, which ensures that the page is posted back every time the list selection is changed, giving your page the chance to update its data-bound controls accordingly. The other option is to create a dedicated button (such as Select) next to the list control for initiating the postback.
CHAPTER 9 ■ DATA BINDING |
315 |
When you select a city, the second data source retrieves all the employees in that city. Here’s the definition for the second data source:
<asp:SqlDataSource ID="sourceEmployees" runat="server" ProviderName="System.Data.SqlClient" ConnectionString="<%$ ConnectionStrings:Northwind %>" SelectCommand="SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees WHERE City=@City"> <SelectParameters>
<asp:ControlParameter ControlID="lstCities" Name="City" PropertyName="SelectedValue" />
</SelectParameters>
</asp:SqlDataSource>
The trick here is the query is written using a parameter. Parameters are always indicated with an @ symbol, as in @City. You can define as many symbols as you want, but you must map each provider to another value. In this example, the value for the @City parameter is taken from the lstCities.SelectedValue property. However, you could just as easily modify the ControlParameter tag to bind to another property or control.
Now when you run the page, you can view the employees in a specific city (see Figure 9-8).
Figure 9-8. Selecting records based on control selection
It’s important to understand the benefits and limitations of this example. First, when you create a parameterized command in a SqlDataSource tag, the parameters are properly encoded and SQL injection attacks aren’t a problem (as discussed in Chapter 7). Second, all the data-bound controls you create rebind themselves after every postback. This means that when you select a city, the page will be posted back and both queries will be executed. This is probably extra database work you don’t require, assuming the list of cities does not change frequently. Once again, this is a good place to consider caching (see Chapter 11 for details).
Stored Procedures
You can adapt this example to work with a stored procedure just as easily. For example, if you have the following stored procedure in your database:
316CHAPTER 9 ■ DATA BINDING
CREATE PROCEDURE GetEmployeesByCity @City varchar(15)
AS
SELECT EmployeeID, FirstName, LastName, Title, City FROM Employees WHERE City=@City
GO
you can change the sourceEmployees data source, as shown here:
<asp:SqlDataSource ID="sourceEmployees" runat="server" ProviderName="System.Data.SqlClient" ConnectionString="<%$ ConnectionStrings:Northwind %>"
SelectCommand="GetEmployeesByCity" SelectCommandType="StoredProcedure"> <SelectParameters>
<asp:ControlParameter ControlID="lstCities" Name="City" PropertyName="SelectedValue" />
</SelectParameters>
</asp:SqlDataSource>
Not only does this give you all the benefit of stored procedures, but it also streamlines the
.aspx portion of your page by removing the actual SQL query, which can be quite lengthy in a realworld page.
More Parameterized Commands
Parameter values aren’t necessarily drawn from other controls. You can map a parameter to any of the parameter types defined in Table 9-2.
Table 9-2. Parameter Types
Source |
Control Tag |
Description |
Control property |
<asp:ControlParameter> |
A property from another control on the |
|
|
page. |
Query string value |
<asp:QueryStringParameter> |
A value from the current query string. |
Session state value |
<asp:SessionParameter> |
A value stored in the current user’s |
|
|
session. |
Cookie value |
<asp:CookieParameter> |
A value from any cookie attached to the |
|
|
current request. |
Profile value |
<asp:ProfileParameter> |
A value from the current user’s profile |
|
|
(see Chapter 24). |
A form variable |
<asp:FormParameter> |
A value posted to the page from an input |
|
|
control. Usually, you’ll use a control |
|
|
property instead, but you might need to |
|
|
grab a value straight from the Forms |
|
|
collection if you’ve disabled view state |
|
|
for the corresponding control. |
|
|
|
You don’t need to remember the different tag names, as Visual Studio provides a handy editor that lets you create your command and define your parameters (see Figure 9-9). To see this dialog box, click the ellipsis (…) next to the SelectQuery property in the Properties window. When you type a command that uses one or more parameters, click the Refresh Parameters button, and the list of parameters will appear. You can then choose the mapping for each parameter by making a choice in the Parameter Source box.
CHAPTER 9 ■ DATA BINDING |
317 |
Figure 9-9. Configuring parameter binding at design time
For example, you could split the earlier example into two pages. In the first page, define a list control that shows all the available cities:
<asp:SqlDataSource ID="sourceEmployeeCities" runat="server" ProviderName="System.Data.SqlClient" ConnectionString="<%$ ConnectionStrings:Northwind %>" SelectCommand="SELECT DISTINCT City FROM Employees"> </asp:SqlDataSource>
<asp:ListBox ID="lstCities" runat="server" DataSourceID="sourceEmployeeCities" DataTextField="City"></asp:ListBox><br />
Now, you’ll need a little extra code to copy the selected city to the query string and redirect the page. Here’s a button that does just that:
protected void cmdGo_Click(object sender, EventArgs e)
{
Response.Redirect("QueryParameter2.aspx?city=" + lstCities.SelectedValue);
}
Finally, the second page can bind the GridView according to the city value that’s supplied in the query string:
<asp:SqlDataSource ID="sourceEmployees" runat="server" ProviderName="System.Data.SqlClient" ConnectionString="<%$ ConnectionStrings:Northwind %>"
SelectCommand="GetEmployeesByCity" SelectCommandType="StoredProcedure"> <SelectParameters>
<asp:QueryStringParameter Name="City" QueryStringField="city" /> </SelectParameters>
</asp:SqlDataSource>
