Tutorial 16: An Overview of Inserting, Updating, and Deleting Data

 

Scott Mitchell

March 2007

Summary: In this tutorial, Scott Mitchell explains how to map the Insert(), Update(), and Delete() methods of the ObjectDataSource to methods of classes in the BLL, as well as how to configure the GridView, DetailsView, and FormView controls to provide data-modification capabilities. (45 printed pages)

Download the code for this sample.

Contents of Tutorial 16 (Visual C#)

Introduction
Step 1: Creating the Insert, Update, and Delete Tutorials Web Pages
Step 2: Adding and Configuring the ObjectDataSource Control
Examining the ObjectDataSource's Markup
Step 3: Adding a Data Web Control and Configuring It for Data Modification
Deleting Data from the GridView
Editing Data with the GridView
Inserting, Editing, and Deleting Data with the DetailsView
Using the FormView for a More Flexible Data-Modification User Interface
Conclusion

Introduction

Over the past several tutorials, we've examined how to display data in an ASP.NET page using the GridView, DetailsView, and FormView controls. These controls just work with data that is supplied to them. Commonly, these controls access data through the use of a data-source control, such as the ObjectDataSource. We've seen how the ObjectDataSource acts as a proxy between the ASP.NET page and the underlying data. When a GridView must display data, it invokes the Select() method of its ObjectDataSource, which in turn invokes a method from our Business-Logic Layer (BLL), which calls a method in the appropriate Data-Access Layer's (DAL) TableAdapter, which in turn sends a SELECT query to the Northwind database.

Recall that when we created the TableAdapters in the DAL in our first tutorial, Microsoft Visual Studio automatically added methods for inserting, updating, and deleting data from the underlying database table. Moreover, in Creating a Business Logic Layer, we designed methods in the BLL that called down into these data-modification DAL methods.

In addition to its Select() method, the ObjectDataSource also has Insert(), Update(), and Delete() methods. Like the Select() method, these three methods can be mapped to methods in an underlying object. When configured to insert, update, or delete data, the GridView, DetailsView, and FormView controls provide a user interface for modifying the underlying data. This user interface calls the Insert(), Update(), and Delete() methods of the ObjectDataSource, which then invoke the associated methods of the underlying object (see Figure 1).

Click here for larger image

Figure 1. The Insert(), Update(), and Delete() methods of the ObjectDataSource serve as a proxy into the BLL. (Click on the picture for a larger image.)

In this tutorial, we'll see how to map the Insert(), Update(), and Delete() methods of the ObjectDataSource to methods of classes in the BLL, as well as how to configure the GridView, DetailsView, and FormView controls to provide data-modification capabilities.

Step 1: Creating the Insert, Update, and Delete Tutorials Web Pages

Before we start exploring how to insert, update, and delete data, let's first take a moment to create the ASP.NET pages in our Web site project that we'll need for this tutorial and the next several ones. Start by adding a new folder named EditInsertDelete. Next, add the following ASP.NET pages to that folder, making sure to associate each page with the Site.master master page:

  • Default.aspx
  • Basics.aspx
  • DataModificationEvents.aspx
  • ErrorHandling.aspx
  • UIValidation.aspx
  • CustomizedUI.aspx
  • OptimisticConcurrency.aspx
  • ConfirmationOnDelete.aspx
  • UserLevelAccess.aspx

Figure 2. Add the ASP.NET pages for the data modification-related tutorials.

As in the other folders, Default.aspx in the EditInsertDelete folder will list the tutorials in its section. Recall that the SectionLevelTutorialListing.ascx User Control provides this functionality. Therefore, add this User Control to Default.aspx by dragging it from the Solution Explorer onto the page's Design view.

Click here for larger image

Figure 3. Add the SectionLevelTutorialListing.ascx User Control to Default.aspx. (Click on the picture for a larger image.)

Lastly, add the pages as entries to the Web.sitemap file. Specifically, add the following markup after the Customized Formatting <siteMapNode>:

<siteMapNode title="Editing, Inserting, and Deleting"

    url="~/EditInsertDelete/Default.aspx"

    description="Samples of Reports that Provide Editing, Inserting,

                  and Deleting Capabilities">

    

    <siteMapNode url="~/EditInsertDelete/Basics.aspx"

        title="Basics"

        description="Examines the basics of data modification with the

                      GridView, DetailsView, and FormView controls." />

    <siteMapNode url="~/EditInsertDelete/DataModificationEvents.aspx"

        title="Data Modification Events"

        description="Explores the events raised by the ObjectDataSource

                      pertinent to data modification." />

    <siteMapNode url="~/EditInsertDelete/ErrorHandling.aspx"

        title="Error Handling"

        description="Learn how to gracefully handle exceptions raised

                      during the data-modification workflow." />

    <siteMapNode url="~/EditInsertDelete/UIValidation.aspx"

        title="Adding Data Entry Validation"

        description="Help prevent data-entry errors by providing 
validation." />

    <siteMapNode url="~/EditInsertDelete/CustomizedUI.aspx"

        title="Customize the User Interface"

        description="Customize the editing and inserting user 
interfaces." />

    <siteMapNode url="~/EditInsertDelete/OptimisticConcurrency.aspx"

        title="Optimistic Concurrency"

        description="Learn how to help prevent simultaneous users from

                      overwritting one another's changes." />

    <siteMapNode url="~/EditInsertDelete/ConfirmationOnDelete.aspx"

        title="Confirm On Delete"

        description="Prompt a user for confirmation when deleting a 
record." />

    <siteMapNode url="~/EditInsertDelete/UserLevelAccess.aspx"

        title="Limit Capabilities Based on User"

        description="Learn how to limit the data-modification functionality

                      based on the user role or permissions." />

</siteMapNode>

After updating Web.sitemap, take a moment to view the tutorials Web site through a browser. The menu on the left now includes items for the editing, inserting, and deleting tutorials, as shown in Figure 4.

Figure 4. The site map now includes entries for the editing, inserting, and deleting tutorials.

Step 2: Adding and Configuring the ObjectDataSource Control

Because the GridView, DetailsView, and FormView differ in their data-modification capabilities and layout, let's examine each one individually. Instead of having each control use its own ObjectDataSource, however, let's just create a single ObjectDataSource that all three control examples can share.

Open the Basics.aspx page, drag an ObjectDataSource from the Toolbox onto the Designer, and click the Configure Data Source link from its smart tag. Because the ProductsBLL is the only BLL class that provides editing, inserting, and deleting methods, configure the ObjectDataSource to use this class.

Click here for larger image

Figure 5. Configure the ObjectDataSource to use the ProductsBLL class. (Click on the picture for a larger image.)

In the next screen we can specify what methods of the ProductsBLL class are mapped to the Select(), Insert(), Update(), and Delete() methods of the ObjectDataSource by selecting the appropriate tab and choosing the method from the drop-down list. Figure 6, which should look familiar by now, maps the Select() method of the ObjectDataSource to the GetProducts() method of the ProductsBLL class. The Insert(), Update(), and Delete() methods can be configured by selecting the appropriate tab from the list along the top.

Click here for larger image

Figure 6. Have the ObjectDataSource return all of the products. (Click on the picture for a larger image.)

Figures 7, 8, and 9 show the UPDATE, INSERT, and DELETE tabs, respectively, of the ObjectDataSource. Configure these tabs, so that the Insert(), Update(), and Delete() methods invoke the UpdateProduct, AddProduct, and DeleteProduct methods, respectively, of the ProductsBLL class.

Click here for larger image

Figure 7. Map the ObjectDataSource's Update() method to the ProductsBLL class's UpdateProduct method. (Click on the picture for a larger image.)

Click here for larger image

Figure 8. Map the ObjectDataSource's Insert() method to the ProductsBLL class's AddProduct method. (Click on the picture for a larger image.)

Click here for larger image

Figure 9. Map the ObjectDataSource's Delete() method to the ProductsBLL class's DeleteProduct method. (Click on the picture for a larger image.)

You might have noticed that the drop-down lists in the UPDATE, INSERT, and DELETE tabs already had these methods selected. This is thanks to our use of the DataObjectMethodAttribute, which decorates the methods of ProductsBLL. For example, the DeleteProduct method has the following signature:

[System.ComponentModel.DataObjectMethodAttribute

    (System.ComponentModel.DataObjectMethodType.Delete, true)]

public bool DeleteProduct(int productID)

{

    ...

}

The DataObjectMethodAttribute attribute indicates the purpose of each method—whether it is for selecting, inserting, updating, or deleting—and whether or not it's the default value. If you omitted these attributes when creating your BLL classes, you'll have to select the methods manually from the UPDATE, INSERT, and DELETE tabs.

After ensuring that the appropriate ProductsBLL methods are mapped to the Insert(), Update(), and Delete() methods of the ObjectDataSource, click Finish to complete the wizard.

Examining the ObjectDataSource's Markup

After configuring the ObjectDataSource through its wizard, go to the Source view to examine the generated declarative markup. The <asp:ObjectDataSource> tag specifies the underlying object and the methods to invoke. Additionally, there are DeleteParameters, UpdateParameters, and InsertParameters that map to the input parameters for the AddProduct, UpdateProduct, and DeleteProduct methods of the ProductsBLL class:

<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"

    DeleteMethod="DeleteProduct" InsertMethod="AddProduct"

    OldValuesParameterFormatString="original_{0}" 
SelectMethod="GetProducts"

    TypeName="ProductsBLL" UpdateMethod="UpdateProduct">

    <DeleteParameters>

        <asp:Parameter Name="productID" Type="Int32" />

    </DeleteParameters>

    <UpdateParameters>

        <asp:Parameter Name="productName" Type="String" />

        <asp:Parameter Name="supplierID" Type="Int32" />

        <asp:Parameter Name="categoryID" Type="Int32" />

        <asp:Parameter Name="quantityPerUnit" Type="String" />

        <asp:Parameter Name="unitPrice" Type="Decimal" />

        <asp:Parameter Name="unitsInStock" Type="Int16" />

        <asp:Parameter Name="unitsOnOrder" Type="Int16" />

        <asp:Parameter Name="reorderLevel" Type="Int16" />

        <asp:Parameter Name="discontinued" Type="Boolean" />

        <asp:Parameter Name="productID" Type="Int32" />

    </UpdateParameters>

    <InsertParameters>

        <asp:Parameter Name="productName" Type="String" />

        <asp:Parameter Name="supplierID" Type="Int32" />

        <asp:Parameter Name="categoryID" Type="Int32" />

        <asp:Parameter Name="quantityPerUnit" Type="String" />

        <asp:Parameter Name="unitPrice" Type="Decimal" />

        <asp:Parameter Name="unitsInStock" Type="Int16" />

        <asp:Parameter Name="unitsOnOrder" Type="Int16" />

        <asp:Parameter Name="reorderLevel" Type="Int16" />

        <asp:Parameter Name="discontinued" Type="Boolean" />

    </InsertParameters>

</asp:ObjectDataSource>

The ObjectDataSource includes a parameter for each of the input parameters for its associated methods, just as a list of SelectParameters is present when the ObjectDataSource is configured to call a select method that expects an input parameter (such as GetProductsByCategoryID(categoryID)). As we'll see shortly, values for these DeleteParameters, UpdateParameters, and InsertParameters are set automatically by the GridView, DetailsView, and FormView, prior to invoking the Insert(), Update(), or Delete() method of the ObjectDataSource. These values can also be set programmatically, as needed, as we'll discuss in a future tutorial.

One side effect of using the wizard to configure to ObjectDataSource is that Visual Studio sets the OldValuesParameterFormatString property to original_{0}. This property value is used to include the original values of the data that is being edited and is useful in two scenarios:

  • If, when editing a record, users are able to change the primary key value. In this case, both the new primary key value and the original primary key value must be provided, so that the record with the original primary key value can be found and have its value updated accordingly.
  • When using optimistic concurrency. Optimistic concurrency is a technique to ensure that two simultaneous users don't overwrite one another's changes, and is the topic for a future tutorial.

The OldValuesParameterFormatString property indicates the name of the input parameters in the underlying object's update and delete methods for the original values. We'll discuss this property and its purpose in greater detail when we explore optimistic concurrency. I bring it up now, however, because our BLL's methods do not expect the original values, and therefore it's important that we remove this property. Leaving the OldValuesParameterFormatString property set to anything other than the default ({0}) will cause an error when a data Web control attempts to invoke the Update() or Delete() method of the ObjectDataSource, because the ObjectDataSource will attempt to pass in both the UpdateParameters or DeleteParameters specified, as well as original value parameters.

If this isn't terribly clear at this juncture, don't worry; we'll examine this property and its utility in a future tutorial. For now, just be certain to either remove this property declaration entirely from the declarative syntax or set the value to the default value ({0}).

Note   If you just clear out the OldValuesParameterFormatString property value from the Properties window in the Design view, the property will still exist in the declarative syntax, but be set to an empty string. This, unfortunately, will still result in the same problem discussed earlier. Therefore, either remove the property altogether from the declarative syntax or, from the Properties window, set the value to the default, {0}.

Step 3: Adding a Data Web Control and Configuring It for Data Modification

After the ObjectDataSource has been added to the page and configured, we're ready to add data Web controls to the page, to both display the data and provide a means for the end user to modify it. We'll look at the GridView, DetailsView, and FormView separately, as these data Web controls differ in their data-modification capabilities and configuration.

As we'll see in the remainder of this article, adding very basic editing, inserting, and deleting support through the GridView, DetailsView, and FormView controls is really as easy as checking a couple of check boxes. There are many subtleties and edge cases in the real-world that make providing such functionality more involved than just point-and-click. This tutorial, however, focuses solely on proving simplistic data-modification capabilities. Future tutorials will examine concerns that will undoubtedly arise in a real-world setting.

Deleting Data from the GridView

Start by dragging a GridView from the Toolbox onto the Designer. Next, bind the ObjectDataSource to the GridView by selecting it from the drop-down list in the GridView's smart tag. At this point, the GridView's declarative markup will be the following:

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"

    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1">

    <Columns>

        <asp:BoundField DataField="ProductID" HeaderText="ProductID"

            InsertVisible="False"

            ReadOnly="True" SortExpression="ProductID" />

        <asp:BoundField DataField="ProductName" 
HeaderText="ProductName"

            SortExpression="ProductName" />

        <asp:BoundField DataField="SupplierID" HeaderText="SupplierID"

           SortExpression="SupplierID" />

        <asp:BoundField DataField="CategoryID" HeaderText="CategoryID"

           SortExpression="CategoryID" />

        <asp:BoundField DataField="QuantityPerUnit"

           HeaderText="QuantityPerUnit"

           SortExpression="QuantityPerUnit" />

        <asp:BoundField DataField="UnitPrice" HeaderText="UnitPrice"

           SortExpression="UnitPrice" />

        <asp:BoundField DataField="UnitsInStock"

           HeaderText="UnitsInStock" SortExpression="UnitsInStock" />

        <asp:BoundField DataField="UnitsOnOrder"

           HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" />

        <asp:BoundField DataField="ReorderLevel"

           HeaderText="ReorderLevel" SortExpression="ReorderLevel" />

        <asp:CheckBoxField DataField="Discontinued"

           HeaderText="Discontinued" SortExpression="Discontinued" />

        <asp:BoundField DataField="CategoryName"

           HeaderText="CategoryName" ReadOnly="True"

            SortExpression="CategoryName" />

        <asp:BoundField DataField="SupplierName"

            HeaderText="SupplierName" ReadOnly="True"

            SortExpression="SupplierName" />

    </Columns>

</asp:GridView>

Binding the GridView to the ObjectDataSource through its smart tag has two benefits:

  • BoundFields and CheckBoxFields are automatically created for each of the fields that is returned by the ObjectDataSource. Moreover, the BoundField and CheckBoxField's properties are set based on the underlying field's metadata. For example, the ProductID, CategoryName, and SupplierName fields are marked as read-only in the ProductsDataTable and, therefore, shouldn't be updatable when editing. To accommodate this, the ReadOnly properties of these BoundFields are set to true.
  • The DataKeyNames property is assigned to the primary key field(s) of the underlying object. This is essential when using the GridView for editing or deleting data, as this property indicates the field (or set of fields) that uniquely identifies each record. For more information on the DataKeyNames property, refer back to the Master/Detail Using a Selectable Master GridView with a Details DetailView tutorial.

While the GridView can be bound to the ObjectDataSource through the Properties window or declarative syntax, doing so requires you to add the appropriate BoundField and DataKeyNames markup manually.

The GridView control provides built-in support for row-level editing and deleting. Configuring a GridView to support deleting adds a column of Delete buttons. When the end user clicks the Delete button for a particular row, a postback ensues and the GridView performs the following steps:

  1. The DeleteParameters value(s) of the ObjectDataSource are assigned.
  2. The Delete() method of the ObjectDataSource is invoked, deleting the specified record.
  3. The GridView rebinds itself to the ObjectDataSource by invoking its Select() method.

The values that are assigned to the DeleteParameters are the values of the DataKeyNames field(s) for the row whose Delete button was clicked. Therefore, it's vital that the DataKeyNames property of a GridView be set correctly. If it's missing, the DeleteParameters will be assigned a value of null in step 1, which in turn will not result in any deleted records in step 2.

Note   The DataKeys collection is stored in the control state of the GridView, meaning that the DataKeys values will be remembered across postback even if the view state of the GridView has been disabled. However, it is very important that view state remain enabled for GridViews that support editing or deleting (the default behavior). If you set the EnableViewState property of the GridView to false, the editing and deleting behavior will work fine for a single user; but, if there are concurrent users deleting data, there exists the possibility that these concurrent users might accidentally delete or edit records that they didn't intend to delete or edit. (See my blog entry, WARNING: Concurrency Issue with ASP.NET 2.0 GridViews/DetailsView/FormViews that Support Editing and/or Deleting and Whose View State is Disabled, for more information.)

This same warning applies also to DetailsViews and FormViews.

To add deleting capabilities to a GridView, just go to its smart tag and check the Enable Deleting check box.

Figure 10. Check the Enable Deleting check box.

Checking the Enable Deleting check box from the smart tag adds a CommandField to the GridView. The CommandField renders a column in the GridView with buttons for performing one or more of the following tasks: selecting a record, editing a record, deleting a record. We previously saw the CommandField in action with selecting records in the Master/Detail Using a Selectable Master GridView with a Details DetailView tutorial.

The CommandField contains a number of ShowXButton properties that indicate what series of buttons are displayed in the CommandField. By checking the Enable Deleting check box, a CommandField whose ShowDeleteButton property is set to true has been added to the Columns collection of the GridView.

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"

    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1">

    <Columns>

        <asp:CommandField ShowDeleteButton="True" />

        ... BoundFields removed for brevity ...

    </Columns>

</asp:GridView>

At this point, believe it or not, we're done with adding deleting support to the GridView! As Figure 11 shows, when visiting this page through a browser, a column of Delete buttons is present.

Click here for larger image

Figure 11. The CommandField adds a column of Delete buttons. (Click on the picture for a larger image.)

If you've been building this tutorial from the ground up on your own, when testing this page, clicking the Delete button will raise an exception. Continue reading to learn about why these exceptions were raised and how to fix them.

Note   If you're following along using the download that accompanies this tutorial, these problems have already been accounted for. However, I encourage you to read through the details that follow to help identify problems that might arise, as well as suitable workarounds.

If, when attempting to delete a product, you get an exception whose message is similar to "ObjectDataSource 'ObjectDataSource1' could not find a non-generic method 'DeleteProduct' that has parameters: productID, original_ProductID," you likely forgot to remove the OldValuesParameterFormatString property from the ObjectDataSource. With the OldValuesParameterFormatString property specified, the ObjectDataSource attempts to pass in both productID and original_ProductID input parameters to the DeleteProduct method. DeleteProduct, however, only accepts a single input parameter, hence the exception. Removing the OldValuesParameterFormatString property (or setting it to {0}) instructs the ObjectDataSource to not attempt to pass in the original input parameter.

Click here for larger image

Figure 12. Ensure that the OldValuesParameterFormatString property has been removed. (Click on the picture for a larger image.)

Even if you had removed the OldValuesParameterFormatString property, you still will get an exception when trying to delete a product, with the message "The DELETE statement conflicted with the REFERENCE constraint 'FK_Order_Details_Products'." The Northwind database contains a foreign-key constraint between the Order Details and Products tables, meaning that a product cannot be deleted from the system if there are one or more records for it in the Order Details table. Because every product in the Northwind database has at least one record in Order Details, we cannot delete any products until we first delete the associated order-details records of the product.

Click here for larger image

Figure 13. A foreign-key constraint prohibits the deletion of products. (Click on the picture for a larger image.)

For our tutorial, let's just delete all of the records from the Order Details table. In a real-world application, we'd have either to:

  • Have another screen to manage order-details information.
  • Augment the DeleteProduct method to include logic to delete the order details of the specified product.
  • Modify the SQL query that is used by the TableAdapter to include deletion of the order details of the specified product.

Let's just delete all of the records from the Order Details table to circumvent the foreign-key constraint. Go to the Server Explorer in Visual Studio, right-click on the NORTHWND.MDF node, and choose New Query. Then, in the query window, run the following SQL statement: DELETE FROM [Order Details]

Click here for larger image

Figure 14. Delete all records from the Order Details table. (Click on the picture for a larger image.)

After clearing out the Order Details table, clicking on the Delete button will delete the product without error. If clicking on the Delete button does not delete the product, check to ensure that the DataKeyNames property of the GridView is set to the primary key field (ProductID).

Note   When clicking on the Delete button, a postback ensues and the record is deleted. This can be dangerous, because it is easy to click on the wrong row's Delete button accidentally. In a future tutorial, we'll see how to add a client-side confirmation when deleting a record.

Editing Data with the GridView

Along with deleting, the GridView control also provides built-in row-level editing support. Configuring a GridView to support editing adds a column of Edit buttons. From the perspective of the end user, clicking a row's Edit button causes that row to become editable, turning the cells into text boxes that contain the existing values, and replacing the Edit button with Update and Cancel buttons. After making their desired changes, end users can click the Update button to commit the changes or the Cancel button to discard them. In either case, after clicking Update or Cancel, the GridView returns to its pre-editing state.

From our perspective as the page developer, when the end user clicks the Edit button for a particular row, a postback ensues and the GridView performs the following steps:

  1. The EditItemIndex property of the GridView is assigned to the index of the row whose Edit button was clicked.
  2. The GridView rebinds itself to the ObjectDataSource by invoking its Select() method.
  3. The row index that matches the EditItemIndex is rendered in "edit mode." In this mode, the Edit button is replaced by Update and Cancel buttons, and BoundFields whose ReadOnly properties are set to False (the default) are rendered as TextBox Web controls whose Text properties are assigned to the values of the data fields.

At this point, the markup is returned to the browser, allowing the end user to make any changes to the row's data. When the user clicks the Update button, a postback occurs and the GridView performs the following steps:

  1. The UpdateParameters value(s) of the ObjectDataSource are assigned the values entered by the end user into the GridView's editing interface.
  2. The Update() method of the ObjectDataSource is invoked, updating the specified record.
  3. The GridView rebinds itself to the ObjectDataSource by invoking its Select() method.

The primary key values that were assigned to the UpdateParameters in step 1 come from the values that are specified in the DataKeyNames property, whereas the non-primary key values come from the text in the TextBox Web controls for the edited row. As with deleting, it is vital that the DataKeyNames property of a GridView be set correctly. If it's missing, the UpdateParameters primary key value will be assigned a value of null in step 1, which in turn will not result in any updated records in step 2.

Editing functionality can be activated by just checking the Enable Editing check box in the GridView's smart tag.

Figure 15. Check the Enable Editing check box.

Checking the Enable Editing check box will add a CommandField (if needed) and set its ShowEditButton property to true. As we saw earlier, the CommandField contains a number of ShowXButton properties that indicate what series of buttons are displayed in the CommandField. Checking the Enable Editing check box adds the ShowEditButton property to the existing CommandField.

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"

    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1">

    <Columns>

        <asp:CommandField ShowDeleteButton="True"

            ShowEditButton="True" />

        ... BoundFields removed for brevity ...

    </Columns>

</asp:GridView>

That's all there is to adding rudimentary editing support. As Figure16 shows, the editing interface is rather crude; each BoundField whose ReadOnly property is set to false (the default) is rendered as a TextBox. This includes fields such as CategoryID and SupplierID, which are keys to other tables.

Click here for larger image

Figure 16. Clicking Chai's Edit button displays the row in "edit mode." (Click on the picture for a larger image.)

In addition to asking users to edit foreign-key values directly, the editing interface's interface is lacking in the following ways:

  • If the user enters a CategoryID or SupplierID that does not exist in the database, the UPDATE will violate a foreign-key constraint, causing an exception to be raised.
  • The editing interface doesn't include any validation. If you don't provide a required value (such as ProductName) or enter a string value in which a numeric value is expected (such as entering "Too much!" into the UnitPrice text box), an exception will be thrown. A future tutorial will examine how to add validation controls to the editing user interface.
  • Currently, all product fields that are not read-only must be included in the GridView. If we were to remove a field from the GridView—say, UnitPrice—when updating the data, the GridView would not set the UpdateParameters value of UnitPrice, which would change the database record's UnitPrice to a NULL value. Similarly, if a required field, such as ProductName, is removed from the GridView, the update will fail with the same "Column 'ProductName' does not allow nulls" exception mentioned earlier.
  • The editing-interface formatting leaves a lot to be desired. The UnitPrice is shown with four decimal points. Ideally, the CategoryID and SupplierID values would contain DropDownLists that list the categories and suppliers in the system.

These are all shortcomings that we'll have to live with for now, but which will be addressed in future tutorials.

Inserting, Editing, and Deleting Data with the DetailsView

As we've seen in earlier tutorials, the DetailsView control displays one record at a time and, like the GridView, allows for editing and deleting of the currently displayed record. Both the experience of the end user with editing and deleting items from a DetailsView and the workflow from the ASP.NET side are identical to that of the GridView. Where the DetailsView differs from the GridView is that it also provides built-in inserting support.

To demonstrate the data-modification capabilities of the GridView, start by adding a DetailsView to the Basics.aspx page above the existing GridView and bind it to the existing ObjectDataSource through the DetailsView's smart tag. Next, clear out the Height and Width properties of the DetailsView, and check the Enable Paging option from the smart tag. To enable editing, inserting, and deleting support, just check the Enable Editing, Enable Inserting, and Enable Deleting check boxes in the smart tag.

Figure 17. Configure the DetailsView to support editing, inserting, and deleting.

As with the GridView, adding editing, inserting, or deleting support adds a CommandField to the DetailsView, as the following declarative syntax shows:

<asp:DetailsView ID="DetailsView1" runat="server" 
AutoGenerateRows="False"

    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" 
AllowPaging="True">

    <Fields>

        <asp:BoundField DataField="ProductID"

            HeaderText="ProductID" InsertVisible="False"

            ReadOnly="True" SortExpression="ProductID" />

        <asp:BoundField DataField="ProductName"

            HeaderText="ProductName" SortExpression="ProductName" />

        <asp:BoundField DataField="SupplierID" HeaderText="SupplierID"

            SortExpression="SupplierID" />

        <asp:BoundField DataField="CategoryID" HeaderText="CategoryID"

            SortExpression="CategoryID" />

        <asp:BoundField DataField="QuantityPerUnit"

            HeaderText="QuantityPerUnit"

            SortExpression="QuantityPerUnit" />

        <asp:BoundField DataField="UnitPrice"

            HeaderText="UnitPrice" SortExpression="UnitPrice" />

        <asp:BoundField DataField="UnitsInStock"

            HeaderText="UnitsInStock" SortExpression="UnitsInStock" />

        <asp:BoundField DataField="UnitsOnOrder"

            HeaderText="UnitsOnOrder" SortExpression="UnitsOnOrder" />

        <asp:BoundField DataField="ReorderLevel"

            HeaderText="ReorderLevel" SortExpression="ReorderLevel" />

        <asp:CheckBoxField DataField="Discontinued"

            HeaderText="Discontinued" SortExpression="Discontinued" />

        <asp:BoundField DataField="CategoryName"

            HeaderText="CategoryName" ReadOnly="True"

            SortExpression="CategoryName" />

        <asp:BoundField DataField="SupplierName"

            HeaderText="SupplierName" ReadOnly="True"

            SortExpression="SupplierName" />

        <asp:CommandField ShowDeleteButton="True"

            ShowEditButton="True" ShowInsertButton="True" />

    </Fields>

</asp:DetailsView>

Note that for the DetailsView, the CommandField appears at the end of the Columns collection by default. Because the DetailsView's fields are rendered as rows, the CommandField appears as a row with Insert, Edit, and Delete buttons at the bottom of the DetailsView.

Click here for larger image

Figure 18. Configure the DetailsView to support editing, inserting, and deleting. (Click on the picture for a larger image.)

Clicking on the Delete button starts the same sequence of events as with the GridView: a postback; followed by the DetailsView populating the DeleteParameters of its ObjectDataSource, based on the DataKeyNames values; and completed with a call to the Delete() method of its ObjectDataSource, which actually removes the product from the database. Editing in the DetailsView also works in a fashion that is identical to that of the GridView.

For inserting, the end user is presented with a New button that, when clicked, renders the DetailsView in "insert mode." With "insert mode," the New button is replaced by Insert and Cancel buttons, and only those BoundFields whose InsertVisible property is set to true (the default) are displayed. Those data fields that are identified as auto-increment fields, such as ProductID, have their InsertVisible property set to false when binding the DetailsView to the data source through the smart tag.

When binding a data source to a DetailsView through the smart tag, Visual Studio sets the InsertVisible property to false only for auto-increment fields. Read-only fields, such as CategoryName and SupplierName, will be displayed in the "insert mode" user interface, unless their InsertVisible property is explicitly set to false. Take a moment to set the InsertVisible properties of these two fields to false, either through the DetailsView's declarative syntax or through the Edit Fields link in the smart tag. Figure 19 shows the setting of the InsertVisible properties to false by clicking on the Edit Fields link.

Click here for larger image

Figure 19. Northwind Traders now offers Acme Tea. (Click on the picture for a larger image.)

After setting the InsertVisible properties, view the Basics.aspx page in a browser and click the New button. Figure 20 shows the DetailsView when adding a new beverage—Acme Tea—to our product line.

Click here for larger image

Figure 20. Northwind Traders now offers Acme Tea. (Click on the picture for a larger image.)

After entering the details for Acme Tea and clicking the Insert button, a postback ensues and the new record is added to the Products database table. Because this DetailsView lists the products in the order in which they exist in the database table, we must page to the last product in order to see the new product.

Click here for larger image

Figure 21. Details for Acme Tea (Click on the picture for a larger image)

Note   The CurrentMode property of the DetailsView indicates the interface that is being displayed and can be one of the following values: Edit, Insert, or ReadOnly. The DefaultMode property indicates the mode to which the DetailsView returns after an edit or insert has been completed and is useful for displaying a DetailsView that is permanently in edit or insert mode.

The point-and-click inserting and editing capabilities of the DetailsView suffer from the same limitations as the GridView: The user must enter existing CategoryID and SupplierID values through a text box; the interface lacks any validation logic; all product fields that do not allow NULL values or don't have a default value specified at the database level must be included in the inserting interface; and so on.

The techniques that we will examine for extending and enhancing the GridView's editing interface in future articles can be applied also to the DetailsView control's editing and inserting interfaces.

Using the FormView for a More Flexible Data-Modification User Interface

The FormView offers built-in support for inserting, editing, and deleting data; but, because it uses templates instead of fields, there's no place to add the BoundFields or the CommandField that is used by the GridView and DetailsView controls to provide the data-modification interface. Instead, this interface—the Web controls for collecting user input when adding a new item or editing an existing one, along with the New, Edit, Delete, Insert, Update, and Cancel buttons—must be added manually to the appropriate templates. Fortunately, Visual Studio will automatically create the needed interface when binding the FormView to a data source through the drop-down list in its smart tag.

To illustrate these techniques, start by adding a FormView to the Basics.aspx page and, from the FormView's smart tag, bind it to the already created ObjectDataSource. This will generate an EditItemTemplate, InsertItemTemplate, and ItemTemplate for the FormView, with TextBox Web controls for collecting the user's input and Button Web controls for the New, Edit, Delete, Insert, Update, and Cancel buttons. Additionally, the DataKeyNames property of the FormView is set to the primary key field (ProductID) of the object that is returned by the ObjectDataSource. Lastly, check the Enable Paging option in the FormView's smart tag.

The following syntax shows the declarative markup for the ItemTemplate of the FormView, after the FormView has been bound to the ObjectDataSource. By default, each non-Boolean value product field is bound to the Text property of a Label Web control, while each Boolean value field (Discontinued) is bound to the Checked property of a disabled CheckBox Web control. In order for the New, Edit, and Delete buttons to trigger certain FormView behavior when they are clicked, it is imperative that their CommandName values be set to New, Edit, and Delete, respectively.

<asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID"

    DataSourceID="ObjectDataSource1" AllowPaging="True">

    <EditItemTemplate>

        ...

    </EditItemTemplate>

    <InsertItemTemplate>

        ...

    </InsertItemTemplate>

    <ItemTemplate>

        ProductID:

        <asp:Label ID="ProductIDLabel" runat="server"

            Text='<%# Eval("ProductID") %>'></asp:Label><br />

        ProductName:

        <asp:Label ID="ProductNameLabel" runat="server"

            Text='<%# Bind("ProductName") %>'>

        </asp:Label><br />

        SupplierID:

        <asp:Label ID="SupplierIDLabel" runat="server"

            Text='<%# Bind("SupplierID") %>'>

        </asp:Label><br />

        CategoryID:

        <asp:Label ID="CategoryIDLabel" runat="server"

            Text='<%# Bind("CategoryID") %>'>

        </asp:Label><br />

        QuantityPerUnit:

        <asp:Label ID="QuantityPerUnitLabel" runat="server"

            Text='<%# Bind("QuantityPerUnit") %>'>

        </asp:Label><br />

        UnitPrice:

        <asp:Label ID="UnitPriceLabel" runat="server"

            Text='<%# Bind("UnitPrice") %>'></asp:Label><br />

        UnitsInStock:

        <asp:Label ID="UnitsInStockLabel" runat="server"

            Text='<%# Bind("UnitsInStock") %>'>

        </asp:Label><br />

        UnitsOnOrder:

        <asp:Label ID="UnitsOnOrderLabel" runat="server"

            Text='<%# Bind("UnitsOnOrder") %>'>

        </asp:Label><br />

        ReorderLevel:

        <asp:Label ID="ReorderLevelLabel" runat="server"

            Text='<%# Bind("ReorderLevel") %>'>

        </asp:Label><br />

        Discontinued:

        <asp:CheckBox ID="DiscontinuedCheckBox" runat="server"

            Checked='<%# Bind("Discontinued") %>'

            Enabled="false" /><br />

        CategoryName:

        <asp:Label ID="CategoryNameLabel" runat="server"

            Text='<%# Bind("CategoryName") %>'>

        </asp:Label><br />

        SupplierName:

        <asp:Label ID="SupplierNameLabel" runat="server"

            Text='<%# Bind("SupplierName") %>'>

        </asp:Label><br />

        <asp:LinkButton ID="EditButton" runat="server"

            CausesValidation="False" CommandName="Edit"

            Text="Edit">

        </asp:LinkButton>

        <asp:LinkButton ID="DeleteButton" runat="server"

            CausesValidation="False" CommandName="Delete"

            Text="Delete">

        </asp:LinkButton>

        <asp:LinkButton ID="NewButton" runat="server"

            CausesValidation="False" CommandName="New"

            Text="New">

        </asp:LinkButton>

    </ItemTemplate>

</asp:FormView>

Figure 22 shows the ItemTemplate of the FormView when viewed through a browser. Each product field is listed with the New, Edit, and Delete buttons at the bottom.

Click here for larger image

Figure 22. The default FormView's ItemTemplate lists each product field, along with New, Edit, and Delete buttons. (Click on the picture for a larger image.)

As with the GridView and DetailsView, clicking the Delete button—or any Button, LinkButton, or ImageButton whose CommandName property is set to Delete—causes a postback; populates the DeleteParameters of the ObjectDataSource, based on the DataKeyNames value of the FormView; and invokes the Delete() method of the ObjectDataSource.

When the Edit button is clicked, a postback ensues and the data is rebound to the EditItemTemplate, which is responsible for rendering the editing interface. This interface includes the Web controls for editing data, along with the Update and Cancel buttons. The default EditItemTemplate that is generated by Visual Studio contains a Label for any auto-increment fields (ProductID), a TextBox for each non-Boolean value field, and a CheckBox for each Boolean value field. This behavior is very similar to that of the auto-generated BoundFields in the GridView and DetailsView controls.

Note   One small issue with the FormView's auto-generation of the EditItemTemplate is that it renders TextBox Web controls for those fields that are read-only, such as CategoryName and SupplierName. We'll see how to account for this shortly.

The TextBox controls in the EditItemTemplate have their Text property bound to the value of their corresponding data field by using two-way data binding. Two-way data binding—denoted by <%# Bind("dataField") %>—performs data binding both when binding data to the template and when populating the ObjectDataSource's parameters for inserting or editing records. That is, when the user clicks the Edit button from the ItemTemplate, the Bind() method returns the specified data-field value. After the user has made changes and clicks Update, the values posted back that correspond to the data fields specified by using Bind() are applied to the UpdateParameters of the ObjectDataSource. Alternatively, one-way data binding—denoted by <%# Eval("dataField") %>—only retrieves the data-field values when binding data to the template and does not return the user-entered values to the data source's parameters on postback.

The following declarative markup shows the EditItemTemplate of the FormView. Note that the Bind() method is used in the data-binding syntax here, and that the Update and Cancel Button Web controls have their CommandName properties set accordingly.

<asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID"

    DataSourceID="ObjectDataSource1" AllowPaging="True">

    <EditItemTemplate>

        ProductID:

        <asp:Label ID="ProductIDLabel1" runat="server"

          Text="<%# Eval("ProductID") %>"></asp:Label><br />

        ProductName:

        <asp:TextBox ID="ProductNameTextBox" runat="server"

          Text="<%# Bind("ProductName") %>">

        </asp:TextBox><br />

        SupplierID:

        <asp:TextBox ID="SupplierIDTextBox" runat="server"

          Text="<%# Bind("SupplierID") %>">

        </asp:TextBox><br />

        CategoryID:

        <asp:TextBox ID="CategoryIDTextBox" runat="server"

          Text="<%# Bind("CategoryID") %>">

        </asp:TextBox><br />

        QuantityPerUnit:

        <asp:TextBox ID="QuantityPerUnitTextBox" runat="server"

           Text="<%# Bind("QuantityPerUnit") %>">

        </asp:TextBox><br />

        UnitPrice:

        <asp:TextBox ID="UnitPriceTextBox" runat="server"

           Text="<%# Bind("UnitPrice") %>">

        </asp:TextBox><br />

        UnitsInStock:

        <asp:TextBox ID="UnitsInStockTextBox" runat="server"

           Text="<%# Bind("UnitsInStock") %>">

        </asp:TextBox><br />

        UnitsOnOrder:

        <asp:TextBox ID="UnitsOnOrderTextBox" runat="server"

           Text="<%# Bind("UnitsOnOrder") %>">

        </asp:TextBox><br />

        ReorderLevel:

        <asp:TextBox ID="ReorderLevelTextBox" runat="server"

           Text="<%# Bind("ReorderLevel") %>">

        </asp:TextBox><br />

        Discontinued:

        <asp:CheckBox ID="DiscontinuedCheckBox" runat="server"

            Checked="<%# Bind("Discontinued") %>" /><br />

        CategoryName:

        <asp:TextBox ID="CategoryNameTextBox" runat="server"

             Text="<%# Bind("CategoryName") %>">

        </asp:TextBox><br />

        SupplierName:

        <asp:TextBox ID="SupplierNameTextBox" runat="server"

             Text="<%# Bind("SupplierName") %>">

        </asp:TextBox><br />

        <asp:LinkButton ID="UpdateButton" runat="server"

            CausesValidation="True" CommandName="Update"

            Text="Update">

        </asp:LinkButton>

        <asp:LinkButton ID="UpdateCancelButton" runat="server"

            CausesValidation="False" CommandName="Cancel"

            Text="Cancel">

        </asp:LinkButton>

    </EditItemTemplate>

    <InsertItemTemplate>

        ...

    </InsertItemTemplate>

    <ItemTemplate>

        ...

    </ItemTemplate>

</asp:FormView>

At this point, our EditItemTemplate will cause an exception to be thrown if we attempt to use it. The problem is that the CategoryName and SupplierName fields are rendered as TextBox Web controls in the EditItemTemplate. We must either change these TextBoxes to Labels or remove them altogether. Let's just delete them entirely from the EditItemTemplate.

Figure 23 shows the FormView in a browser after the Edit button has been clicked for Chai. Note that the SupplierName and CategoryName fields that are shown in the ItemTemplate are no longer present, as we have just removed them from the EditItemTemplate. When the Update button is clicked, the FormView proceeds through the same sequence of steps as the GridView and DetailsView controls.

Click here for larger image

Figure 23. By default, the EditItemTemplate shows each editable product field as a text box or check box. (Click on the picture for a larger image.)

When the Insert button that is within the ItemTemplate of the FormView is clicked, a postback ensues. However, no data is bound to the FormView, because a new record is being added. The InsertItemTemplate interface includes the Web controls for adding a new record, along with the Insert and Cancel buttons. The default InsertItemTemplate that is generated by Visual Studio contains a TextBox for each non-Boolean value field and a CheckBox for each Boolean value field, similar to the auto-generated EditItemTemplate's interface. Each TextBox control has its Text property bound to the value of its corresponding data field by using two-way data binding.

The following declarative markup shows the InsertItemTemplate of the FormView. Note that the Bind() method is used in the data-binding syntax here, and that the Insert and Cancel Button Web controls have their CommandName properties set accordingly.

<asp:FormView ID="FormView1" runat="server" DataKeyNames="ProductID"

    DataSourceID="ObjectDataSource1" AllowPaging="True">

    <EditItemTemplate>

        ...

    </EditItemTemplate>

    <InsertItemTemplate>

        ProductName:

        <asp:TextBox ID="ProductNameTextBox" runat="server"

           Text="<%# Bind("ProductName") %>">

        </asp:TextBox><br />

        SupplierID:

        <asp:TextBox ID="SupplierIDTextBox" runat="server"

           Text="<%# Bind("SupplierID") %>">

        </asp:TextBox><br />

        CategoryID:

        <asp:TextBox ID="CategoryIDTextBox" runat="server"

           Text="<%# Bind("CategoryID") %>">

        </asp:TextBox><br />

        QuantityPerUnit:

        <asp:TextBox ID="QuantityPerUnitTextBox" runat="server"

           Text="<%# Bind("QuantityPerUnit") %>">

        </asp:TextBox><br />

        UnitPrice:

        <asp:TextBox ID="UnitPriceTextBox" runat="server"

           Text="<%# Bind("UnitPrice") %>">

        </asp:TextBox><br />

        UnitsInStock:

        <asp:TextBox ID="UnitsInStockTextBox" runat="server"

           Text="<%# Bind("UnitsInStock") %>">

        </asp:TextBox><br />

        UnitsOnOrder:

        <asp:TextBox ID="UnitsOnOrderTextBox" runat="server"

           Text="<%# Bind("UnitsOnOrder") %>">

        </asp:TextBox><br />

        ReorderLevel:

        <asp:TextBox ID="ReorderLevelTextBox" runat="server"

           Text="<%# Bind("ReorderLevel") %>">

        </asp:TextBox><br />

        Discontinued:

        <asp:CheckBox ID="DiscontinuedCheckBox" runat="server"

           Checked="<%# Bind("Discontinued") %>" /><br />

        CategoryName:

        <asp:TextBox ID="CategoryNameTextBox" runat="server"

            Text="<%# Bind("CategoryName") %>">

        </asp:TextBox><br />

        SupplierName:

        <asp:TextBox ID="SupplierNameTextBox" runat="server"

           Text="<%# Bind("SupplierName") %>">

        </asp:TextBox><br />

        <asp:LinkButton ID="InsertButton" runat="server"

            CausesValidation="True" CommandName="Insert"

            Text="Insert">

        </asp:LinkButton>

        <asp:LinkButton ID="InsertCancelButton" runat="server"

            CausesValidation="False" CommandName="Cancel"

            Text="Cancel">

        </asp:LinkButton>

    </InsertItemTemplate>

    <ItemTemplate>

        ...

    </ItemTemplate>

</asp:FormView>

There's a subtlety with the FormView's auto-generation of the InsertItemTemplate. Specifically, the TextBox Web controls are created even for those fields that are read-only, such as CategoryName and SupplierName. As with the EditItemTemplate, we must remove these TextBoxes from the InsertItemTemplate.

Figure 24 shows the FormView in a browser when adding a new product, Acme Coffee. Note that the SupplierName and CategoryName fields that are shown in the ItemTemplate are no longer present, as we have just removed them. When the Insert button is clicked, the FormView proceeds through the same sequence of steps as the DetailsView control, adding a new record to the Products table. Figure 25 shows the Acme Coffee product's details in the FormView after it has been inserted.

Click here for larger image

Figure 24. The InsertItemTemplate dictates the FormView's inserting interface. (Click on the picture for a larger image.)

Click here for larger image

Figure 25. The details for the new product, Acme Coffee, are displayed in the FormView. (Click on the picture for a larger image.)

By separating out the read-only, editing, and inserting interfaces into three separate templates, the FormView allows for a finer degree of control over these interfaces than the DetailsView and GridView.

Note   As with the DetailsView, the CurrentMode property of the FormView indicates the interface that is being displayed, and its DefaultMode property indicates the mode to which the FormView returns after an edit or insert has been completed.

Conclusion

In this tutorial, we examined the basics of inserting, editing, and deleting data by using the GridView, DetailsView, and FormView. All three of these controls provide some level of built-in data-modification capabilities that can be utilized without writing a single line of code in the ASP.NET page, thanks to the data Web controls and the ObjectDataSource. However, the simple point-and-click techniques render a fairly frail and naive data-modification user interface. In order to provide validation, inject programmatic values, handle exceptions gracefully, customize the user interface, and so on, we'll have to rely on a bevy of techniques that will be discussed over the next several tutorials.

Happy programming!

 

About the author

Scott Mitchell, author of six ASP/ASP.NET books and founder of 4GuysFromRolla.com, has been working with Microsoft Web technologies since 1998. Scott works as an independent consultant, trainer, and writer, and he recently completed his latest book, Sams Teach Yourself ASP.NET 2.0 in 24 Hours. He can be reached at mitchell@4guysfromrolla.com or via his blog, which can be found at http://ScottOnWriting.NET.

© Microsoft Corporation. All rights reserved.