Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]

.pdf
Скачиваний:
107
Добавлен:
16.08.2013
Размер:
29.8 Mб
Скачать

328 CHAPTER 9 DATA BINDING

This is the only code you need to write for the page. Figure 9-12 shows the page in action.

Figure 9-12. Binding to a single employee record

Updating Records

The ObjectDataSource provides the same type of support for updatable data binding as the SqlDataSource. The first step is to specify the UpdateMethod, which needs to be a public instance method in the same class:

<asp:ObjectDataSource ID="sourceEmployees" runat="server" TypeName="DatabaseComponent.EmployeeDB" SelectMethod="GetEmployees" UpdateMethod="UpdateEmployee" />

The challenge is in making sure the UpdateMethod has the right signature. As with the SqlDataSource, updates, inserts, and deletes automatically receive a collection of parameters from the linked data control. These parameters have the same names as the corresponding class properties.

To understand how this works, it helps to consider a basic example. Assume you create a grid that shows a list of EmployeeDetails objects. You also add a column with edit links. When the user commits an edit, the GridView fills the ObjectDataSource.UpdateParameters collection with one parameter for each property of the EmployeeDetails class, including EmployeeID, FirstName, LastName, and TitleOfCourtesy. Then, the ObjectDataSource searches for a method named UpdateEmployee() in the EmployeeDB class. This method must have the same parameters, with the same names.

That means this method is a match:

public void UpdateEmployee(int employeeID, string firstName, string lastName, string titleOfCourtesy)

{ ... }

CHAPTER 9 DATA BINDING

329

This method is not a match, because the names don’t match exactly:

public void UpdateEmployee(int id, string first, string last, string titleOfCourtesy)

{ ... }

This is not a match, because there’s an additional parameter:

public void UpdateEmployee(int employeeID, string firstName, string lastName, string titleOfCourtesy, bool useOptimisticConcurrency)

{ ... }

The method matching algorithm is not case-sensitive, and it doesn’t consider the order or data type of the parameters. It simply tries to find a method with the right number of parameters and the same names. As long as that method is present, the update can be committed automatically, without any custom code.

Note Remember, if you set the DataKeyNames property of the rich data control, the names of key fields will be changed (based on the ObjectDataSource.OldValuesParameterFormatString property). See the section “Updating and Key Fields” earlier in this chapter for more information.

Updating with a Data Object

One problem with the UpdateEmployee() method shown in the previous example is that the method signature is a little cumbersome—you need one parameter for each property in the data object. Seeing as you already have a definition for the EmployeeDetails class, it makes sense to create an UpdateEmployee() method that uses it and gets all its information from an EmployeeDetails object. Here’s an example:

public void UpdateEmployee(EmployeeID emp) { ... }

The ObjectDataSource supports this approach. However, to use it, you must set the DataObjectTypeName to the full name of the class you want to use. Here’s how it works:

<asp:ObjectDataSource ID="sourceEmployees" runat="server" TypeName="DatabaseComponent.EmployeeDB" DataObjectTypeName="DatabaseComponent.EmployeeDetails"

... />

Once this is in place, the ObjectDataSource will match only the UpdateMethod, DeleteMethod, or InsertMethod if it has a single parameter that accepts the type specified in DataObjectTypeName. Additionally, your data object must follow some rules:

It must provide a default (zero-argument) constructor.

For every parameter, there must be a property with the same name. (Public variables are ignored.)

All properties must be public and writable.

You’re free to add code to your data object class. For example, you can add methods, constructors, validation and event-handling logic in your property procedures, and so on.

330 CHAPTER 9 DATA BINDING

Dealing with Nonstandard Method Signatures

Sometimes you may run into a problem in which the property names of your data class don’t exactly match the parameter names of your update method. If all you need is a simple renaming job, you need to perform the task that was described in the “Updating with Stored Procedures” section earlier, although the syntax is slightly different.

First, you define the additional parameters you need, with the correct names. For example, maybe you need to rename the EmployeeDetails.EmployeeID property to a parameter named id in the update method. Here’s the new parameter you need:

<asp:ObjectDataSource ID="sourceEmployees" runat="server" TypeName="DatabaseComponent.EmployeeDB" SelectMethod="GetEmployees" UpdateMethod="UpdateEmployee" OnUpdating="sourceEmployees_Updating" >

<UpdateParameters>

<asp:Parameter Name="id" Type="Int32" /> </UpdateParameters>

</asp:ObjectDataSource>

Second, you react to the ObjectDataSource.Updating event, setting the value for these parameters and removing the ones you don’t want:

protected void sourceEmployees_Updating(object sender, ObjectDataSourceMethodEventArgs e)

{

e.InputParameters["id"] = e.InputParameters["EmployeeID"]; e.InputParameters.Remove("EmployeeID");

}

Tip The same approach used here for updating applies when you’re performing inserts and deletes. The only difference is that you handle the Inserting and Deleting events instead.

You can use a similar approach to add extra parameters. For example, if your method requires a parameter with information that’s not contained in the linked data control, just define it as one of the UpdateParameters and then set the value when the ObjectDataSource.Updating event fires.

If you’re more ambitious, you can even decide to programmatically point the ObjectDataSource to a different update method in the same class:

sourceEmployees.UpdateMethod = "UpdateEmployeeStrict";

You’ll use this approach to solve a common problem in the section “The Limits of the Data Source Controls” later in this chapter.

In fact, to get really adventurous you could set the ConflictDetection property to ConflictOptions.CompareAllValues so that the old and new values are submitted in the UpdateParameters collection. You can then examine these parameters, determine what fields have changed, and call a different method (with different parameters accordingly). Unfortunately, this isn’t a zero-code scenario, and you might end up writing some awkward code for updating and removing parameters. Still, it gives you a valuable extra layer of flexibility.

Handling Identity Values in an Insert

So far, all the examples you’ve seen have used parameters to supply values to an update operation. However, you can also create a parameter to return a result. With the SqlDataSource, you can use this option to get access to an output parameter. With the ObjectDataSource, you can use this technique to capture the return value.

CHAPTER 9 DATA BINDING

331

To see this in action, it’s worth considering the InsertEmployee() method, which adds an employee record and returns the newly generated unique ID value as an integer:

public int InsertEmployee(EmployeeDetails emp) { ... }

You don’t need to use the identity value. As you’ve seen already, linked data controls are bound after any updates are committed, which ensures that the updated information always appears in the linked controls. However, you might want to use the identity for another purpose, such as displaying a confirmation message. To capture this identity value, you need to define a parameter:

<asp:ObjectDataSource ID="sourceEmployees" runat="server" TypeName="DatabaseComponent.EmployeeDB" DataObjectTypeName="DatabaseComponent.EmployeeDetails" SelectMethod="GetEmployees"

InsertMethod="InsertEmployee" OnInserted="sourceEmployees_Inserted"> <InsertParameters>

<asp:Parameter Direction="ReturnValue" Name="EmployeeID" Type="Int32" /> </InsertParameters>

</asp:ObjectDataSource>

Now you can retrieve the parameter by responding to the Inserted event, which fires after the insert operation is finished:

protected void sourceEmployees_Inserted(object sender, ObjectDataSourceStatusEventArgs e)

{

if (e.Exception == null)

{

lblConfirmation.Text = "Inserted record " + e.ReturnValue.ToString();

}

}

The Limits of the Data Source Controls

As a whole, the data source controls are a remarkable innovation for ASP.NET developers. However, you’ll still run into situations where you need to step beyond their bounds—or even abandon them completely. In the following sections, you’ll see how to use the SqlDataSource and ObjectDataSource to deal with a common design requirement—additional options in a master-details list.

The Problem

Earlier, you saw an example that allowed users to browse a list of cities in different regions using two linked controls—a DropDownList and a GridView. Best of all, this example could be created using a SqlDataSource or an ObjectDataSource; either way, it doesn’t require any custom code. Figure 9-9 shows this example.

As convenient as this example is, it presents a problem that’s difficult to fix. Because it’s impossible to create a drop-down list that doesn’t have a selected item (unless it’s empty), the first city in the list is automatically selected. Many web applications use a different behavior and add an extra item at the top of the list with text such as “(Choose a City)”. Selecting this first item has no effect. Additionally, you might want to add an item that allows you to see every city in a single list.

Figure 9-13 shows the result you want.

332 CHAPTER 9 DATA BINDING

Figure 9-13. Data binding with extra options

So, how can you implement this model with data binding? One of the few weaknesses in the data binding model is that you never explicitly handle or create the data object that’s bound to the control. As a result, you don’t have the chance to add an extra item. In fact, this example has two challenges—determining how to add the extra options to the list and reacting when they are selected to override the automatic query logic.

Adding the Extra Items

This problem has a few possible workarounds, but none is perfect. You could rewrite the query so that it returns a result set with an extra hard-coded item. Here’s an example:

SELECT '(Choose a City)' AS City UNION SELECT DISTINCT City FROM Employees

The problem with this approach is that it forces you to add presentation details to the data layer. If your query is in a dedicated stored procedure (which is always a good idea), it will be difficult to reuse this query for other purposes, and it will be awkward to maintain the page.

A better choice is to insert this fixed piece of string into the DropDownList programmatically. However, you can’t take this step before the data binding takes place, because the data binding process will wipe it from the list. You could override the Page.OnPreRenderComplete() method to perform this task. However, that raises new complications. For one thing, the GridView will have already been filled with data based on the initial DropDownList selection. (Even if you solve this problem, there are other issues related to how changes are detected in the DropDownList selection.)

Ultimately, you’ll need to resort to programmatic data binding. In normal operation, data source controls are invoked automatically when a linked control needs data or is ready to commit

CHAPTER 9 DATA BINDING

333

an update. However, a lesser known fact is that you can also take charge of data source controls programmatically, by calling methods such as Select(), Update(), Insert(), and Delete(). Of course, it’s up to you to bind the data you retrieve from Select() and supply the changed data for when committing an update.

To put this into practice, start by removing the DropDownList.DataSourceID property. Instead of using this property, you’ll bind the control when the page first loads. This gives you the chance to insert the items immediately, before any other data binding actions take place:

protected void Page_Load(object sender, EventArgs e)

{

if (!Page.IsPostBack)

{

//Trigger the sourceEmployeeCities query and bind the results. lstCities.DataSource =

sourceEmployeeCities.Select(DataSourceSelectArguments.Empty);

lstCities.DataBind();

//Add the two new items and select the first. lstCities.Items.Insert(0, "(Choose a City)"); lstCities.Items.Insert(1, "(All Cities)"); lstCities.SelectedIndex = 0;

}

}

In this example, the data binding for the list control is performed only once, when the page is requested for the first time. After that, the values in view state are used instead. This code is identical for the SqlDataSource and the ObjectDataSource. That’s not true for the remainder of the example.

Handling the Extra Options with the SqlDataSource

The next challenge is to intercept clicks on either of the first two items. You can accomplish this by handling the data source Selecting event, which occurs just before the query is executed. You can then check the parameters that are about to be supplied and cancel the operation if needed.

Here’s the complete code:

protected void sourceEmployees_Selecting(object sender, SqlDataSourceSelectingEventArgs e)

{

if ((string)e.Command.Parameters["@City"].Value == "(Choose a City)")

{

// Do nothing. e.Cancel = true;

}

else if ((string)e.Command.Parameters["@City"].Value == "(All Cities)")

{

// Manually change the command. e.Command.CommandText = "SELECT * FROM Employees";

}

}

This brute-force approach—changing the command using a hard-coded query—is ugly. Another approach is to cancel the operation, call another method that returns the appropriate data, and bind that. However, that forces you to do a fair bit of work manually, and mixing manual and automatic data binding can quickly get confusing. Unfortunately, no perfect solution exists.

334 CHAPTER 9 DATA BINDING

Handling the Extra Options with the ObjectDataSource

The object data source handles the problem better, because it gives you the option to reroute the command to another method. If you find that a full list of employees is required, you can remove the City parameter altogether and use a no-parameter method for retrieving all the employees.

Here’s how it works:

protected void sourceEmployees_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)

{

if (e.InputParameters["employeeID"] == null) e.Cancel = true;

if ((string)e.InputParameters["City"].Value == "(Choose a City)")

{

// Do nothing. e.Cancel = true;

}

else if ((string)e.InputParameters["City"].Value == "(All Cities)")

{

// Manually change the method. sourceEmployees.SelectMethod = "GetAllEmployees"; e.InputParameters.Remove("City");

}

}

This solution isn’t possible with the SqlDataSource, because the command logic is embedded into the data source control. Still, this approach can easily be abused and lead to code that is difficult to maintain. For example, you won’t receive any warning if you rename, remove, or modify the parameters for the GetAllEmployees() method. In this case, you’ll receive an error only when you run the page and click the (All Cities) option.

Summary

In this chapter, you looked at data binding expressions and the ASP.NET data source controls in detail. Along the way, you started using the GridView, ASP.NET’s premier rich data control. In the next chapter, you’ll explore the three most powerful data-bound controls in detail: the GridView, DetailsView, and FormView. You’ll also learn how they work with a few data source features that this chapter didn’t cover, such as sorting and filtering.

C H A P T E R 1 0

■ ■ ■

Rich Data Controls

In the previous chapter, you saw how to use the data source controls to perform queries, both with and without the assistance of a custom data access class. Along the way, you used some of ASP.NET’s rich data controls, such as the GridView. However, you haven’t delved into all the features these controls provide.

In this chapter, you’ll take a closer look at the GridView, DetailsView, and FormView and learn how to fine-tune formatting and take control of features such as selection, sorting, filtering, and templates. You’ll also learn about advanced scenarios such as showing images, calculating summaries, and creating a master-details list in a single control.

ASP.NET 1.X TEMPLATED CONTROLS

ASP.NET 2.0 still provides the templated controls that were introduced with ASP.NET 1.0. These include the DataGrid, DataList, and Repeater. Most ASP.NET programmers won’t use any of these controls any longer except for backward compatibility (in fact, they aren’t discussed in this book).

DataGrid: The DataGrid is completely replaced by the GridView, which provides the same set of features (and more) and simplifies the coding mode. By default, the DataGrid doesn’t appear in the Visual Studio 2005 Toolbox.

DataList: The DataList is mostly replaced by the GridView, which provides a similar set of templates and much simpler coding model. However, you could still use the DataList if you want to create a multicolumn table, where each cell is a separate record. The GridView doesn’t support this unusual design, because it forces every record to occupy a separate row.

Repeater: The Repeater still plays the same role as a bare-bones template-based control. Although it doesn’t provide many features or frills, you might use it to create customized data displays. The Repeater doesn’t add any built-in elements, so you aren’t locked into a table-based format. However, getting the result you want takes a lot of work because the Repeater doesn’t include higher-level features such as selection and editing.

The chapter focuses exclusively on the new ASP.NET 2.0 data controls: the GridView, DetailsView, and FormView. If you are interested in using the older 1.x controls, refer to the previous edition of this book.

335

336 C H A P T E R 1 0 R I C H D ATA C O N T R O L S

The GridView

If you’ve programmed with ASP.NET 1.x, you’ve probably used the similar DataGrid control. Faced with the challenge of enhancing the DataGrid while preserving backward compatibility, the ASP.NET team decided to create an entirely new control to implement their improvements. This control is the GridView.

The GridView is an extremely flexible grid control for showing data. It includes a wide range of hard-wired features, including selection, paging, and editing, and it is extensible through templates. The great advantage of the GridView over the DataGrid is its support for code-free scenarios. Using the GridView, you can accomplish many common tasks, such as paging and selection, without writing any code. With the DataGrid, you were forced to handle events to implement the same features.

Note The DataGrid is still available in ASP.NET 2.0, and it now supports binding to a data source control. Although you can’t find it in the Toolbox, you can add it by right-clicking the Toolbox and selecting Choose Items. As a rule of thumb, the DataGrid should be used only for backward compatibility and existing ASP.NET websites (where it still works quite well). When creating a new website, use the GridView instead.

Defining Columns

The GridView examples you’ve seen so far have set the GridView.AutoGenerateColumns property to true. When this property is set, the GridView uses reflection to examine that data object and finds all the fields (of a record) or properties (of a custom object). It then creates a column for each one, in the order that it finds it.

This automatic column generation is good for creating quick test pages, but it doesn’t give you the flexibility you’ll usually want. For example, what if you want to hide columns, change their order, or configure some aspect of their display, such as the formatting or heading text? In all of these cases, you’ll need to set AutoGenerateColumns to false and define the columns yourself in the <Columns> section of the GridView control tag.

Tip It’s possible to have AutoGenerateColumns set to true and define columns in the <Columns> section. In this case, the columns you explicitly defined are added before the autogenerated columns. This technique was used in the previous chapter to create a GridView with automatically generated bound columns and a manually defined column with edit controls. However, for the most flexibility you’ll usually want to explicitly define every column.

Each column can be any of several different types, as described in Table 10-1. The order of your column tags determines the right-to-left order of columns in the GridView.

Table 10-1. Column Types

Column

Description

BoundField

This column displays text from a field in the data source.

ButtonField

This column displays a button for each item in the list.

CheckBoxField

This column displays a check box for each item in the list. It’s used

 

automatically for true/false fields (in SQL Server, these are fields that

 

use the bit data type).

CommandField

This column provides selection or editing buttons.

C H A P T E R 1 0 R I C H D ATA C O N T R O L S

337

Column

Description

HyperlinkField

This column displays its contents (a field from the data source or static text)

 

as a hyperlink.

ImageField

This column displays image data from a binary field (providing it can be

 

successfully interpreted as a supported image format).

TemplateField

This column allows you to specify multiple fields, custom controls, and

 

arbitrary HTML using a custom template. It gives you the highest degree

 

of control but requires the most work.

 

 

The most basic column type is the BoundField, which binds to one field in the data object. For example, here’s the definition for a single data-bound column that displays the EmployeeID field:

<asp:BoundField DataField="EmployeeID" HeaderText="ID" />

This achieves one improvement over the autogenerated column—the header text has been changed from EmployeeID to just ID.

When you first create a GridView, the AutoGenerateColumns property is set to false. When you bind it to a data source control, nothing changes. However, if you click the Refresh Schema link of the data source control, the AutoGenerateColumns property is flipped to true, and Visual Studio adds a <BoundField> tag for each field it finds in the data source. This approach has several advantages:

You can easily fine-tune your column order, column headings, and other details by tweaking the properties of your column object.

You can hide columns you don’t want to show by removing the column tag. (However, don’t overuse this technique, because it’s better to cut down on the amount of data you’re retrieving if you don’t intend to display it.)

Tip You can also hide columns programmatically. To hide a column, use the Columns collection for the GridView. For example, setting GridView1.Columns[2].Visible to false hides the third column. Hidden columns are left out of the rendered HTML altogether.

Explicitly defined columns are faster than autogenerated columns. That’s because autogenerated columns force the GridView to reflect on the data source at runtime.

You can add extra columns to the mix for selecting, editing, and more.

Tip If you modify the data source so that it returns a different set of columns, you can regenerate the GridView columns. Just select the GridView, and click the Refresh Schema link in the smart tag. This step will wipe out any custom columns you’ve added (such as editing controls).

Here’s a complete GridView declaration with explicit columns:

<asp:GridView ID="gridEmployees" runat="server" DataSourceID="sourceEmployees" AutoGenerateColumns="False">

<Columns>

<asp:BoundField DataField="EmployeeID" HeaderText="ID" /> <asp:BoundField DataField="FirstName" HeaderText="First Name" /> <asp:BoundField DataField="LastName" HeaderText="Last Name" />