Sunday, October 23, 2011

ListView and DataPager Controls: Inserting Data with DropDownlist


Introduction


The ListView control is similar to the GridView control in many ways: both display a set of records, both support built-in sorting, paging, editing, and deleting functionality with minimal effort. The ListView differs from the GridView in two key ways:
  • Rather than using fields, the ListView is rendered via templates, which offers the page developer much finer control over the emitted markup, and
  • The ListView supports built-in inserting support
The first installment in this series explored the ListView's template-based rendering. This installment looks at how to use the ListView's inserting functionality.In a nutshell, inserting data from the ListView requires two steps: defining the inserting interface via the InsertItemTemplate and specifying where the inserting interface should go via the InsertItemPosition property. Much like with editing data from within the ListView, the InsertItemTemplate can contain two-way databinding statements when using a data source control to get the inputs entered by the user from the ListView's inserting interface into the parameters of the data source control. And like with the editing and deleting workflows, you can programmatically examine and modify the user's submitted data before inserting the data, cancelling the operation altogether if needed.
This article walks through the steps for creating a ListView that allows users to insert records. It also shows how to optionally cancel the inserting workflow based on programmatic logic. Read on to learn more!


An Overview of the ListView's Inserting Workflow


A ListView control that supports inserting includes some sort of inserting interface. This interface includes input Web controls to collect the data to add a new record - TextBoxes, CheckBoxes, DropDownLists, and so forth - along with Insert and Cancel buttons. Clicking the Insert button adds a new record using the user-supplied values; the Cancel button, when clicked, resets the inserting interface to its initial state by clearing out the TextBoxes, returning the DropDownLists to the first item, and so on.The screen shot below shows a ListView control that has an inserting interface defined and has it displayed as the first item. In other words, it appears before any other content defined in the ItemTemplate. The inserting interface may be displayed as the first or last item; it's location is configured via the InsertItemPosition property. The Insert button in the screen shot below is implemented as an ImageButton and displays a green plus icon.

The inserting interface is displayed before any other items in the ListView.
Clicking the Insert button causes a postback and starts the ListView down the inserting workflow path:
  1. The ListView raises its ItemInserting event.
  2. The ListView assigns the values supplied in the inserting interface to the insert parameters for its associated data source control.
  3. The ListView calls its associated data source control's Insert method, which actually performs the insert.
  4. The ListView raises its ItemInserted event handler.
  5. The ListView rebinds to its data source.
After the inserting workflow completes, the just-added item is included in the ListView and the inserting interface is returned to its initial state (the TextBoxes are cleared out, the DropDownLists reset to the first list item, etc.) because the data is rebound to the ListView (step 5) after the insert is performed (step 3).From the end user's perspective, the inserting workflow unfolds like this: the enters the values for the new record into the TextBoxes, DropDownLists, and other input Web controls in the inserting interface. Next, she clicks the Insert button; there's a short pause and the inserting interface is returned to its initial state and the just-added record is displayed in the ListView.

Implementing the Inserting Interface (InsertItemTemplate) and Setting the 


Adding inserting support to the ListView requires that the ListView's underlying data source control support inserting. That means that if you are using a SqlDataSource or AccessDataSource control as the ListView's data source that the SqlDataSource (or AccessDataSource) must have an InsertCommand specified. If you are using an ObjectDataSource then you will need to have specified what object method to invoke to perform the insert. For more background on configuring the data source controls to support inserting, refer to Accessing and Updating Data in ASP.NET: Inserting Data.Recall that implementing inserting involves two key steps: defining the inserting interface via the InsertItemTemplate and specifying where the inserting interface should be placed via the InsertItemPosition property. The InsertItemTemplate must contain the input Web controls to collect the user's input. The demo available for download at the end of this article shows how to insert new records into the Northwind database's Products table, allowing the user to supply the values for the new product's name, supplier, category, and price. These four values are stored in the Products table's ProductNameSupplierIDCategoryID, and UnitPrice fields, respectively.
The markup for the inserting interface (shown below) includes two TextBoxes - one for the ProductName field and one for UnitPrice - and two DropDownLists - one forSupplierID and one for CategoryID. There's also two ImageButton controls that are the Insert and Cancel buttons. The Insert and Cancel buttons can be implemented as Button, LinkButton, or ImageButton controls, but they must have their CommandName properties set to "Insert" and "Cancel", respectively.
Finally, the markup below also includes the assignment of the InsertItemPosition to the value "FirstItem". This property can be set to "FirstItem", "LastItem", or "None". Setting it to "FirstItem" displays the inserting interface before any of the ListView's rendered items.

<asp:ListView InsertItemPosition="FirstItem" ID="lvProducts" runat="server" DataSourceID="dsNorthwind">
   ...
  
   <InsertItemTemplate>
      <p>
         <b>Product Name:</b>
         <asp:TextBox ID="txtProductName" runat="server" Text='<%# Bind("ProductName") %>'></asp:TextBox>
         <asp:RequiredFieldValidator ID="rfvProductName" ControlToValidate="txtProductName" Display="Dynamic"
                            runat="server" ErrorMessage="[Required]"></asp:RequiredFieldValidator>
         <br />
        
         <b>Supplier:</b>
         <asp:DropDownList ID="ddlSuppliers" runat="server" DataSourceID="dsSuppliers" AppendDataBoundItems="true"
               DataTextField="CompanyName" DataValueField="SupplierID">
            <asp:ListItem Value="">None / Unknown</asp:ListItem>
         </asp:DropDownList>
         <br />
        
         <b>Category:</b>
         <asp:DropDownList ID="ddlCategories" runat="server" DataSourceID="dsCategories" AppendDataBoundItems="true"
               DataTextField="CategoryName" DataValueField="CategoryID">
            <asp:ListItem Value="">None / Unknown</asp:ListItem>
         </asp:DropDownList>
         <br />

         <b>Price:</b>
         $<asp:TextBox ID="txtUnitPrice" runat="server" Text='<%# Bind("UnitPrice", "{0:N2}") %>'></asp:TextBox>
         <asp:CompareValidator ID="cvUnitPrice" ControlToValidate="txtUnitPrice" runat="server"
               ErrorMessage="[Invalid]" Operator="GreaterThanEqual" ValueToCompare="0" Type="Currency"></asp:CompareValidator>
         <br />
      </p>
     
      <p>
         <asp:ImageButton ID="imgbInsertProduct" runat="server" ToolTip="Insert Product" AlternateText="Insert Product"
                     CommandName="Insert" ImageUrl="~/Images/add.png" />
         <asp:ImageButton ID="imgbCancelInsert" runat="server" ToolTip="Cancel" AlternateText="Cancel"
                  CausesValidation="false" CommandName="Cancel" ImageUrl="~/Images/cancel.png" />
      </p>
   </InsertItemTemplate>
</asp:ListView>

As the markup above illustrates, the InsertItemTemplate can also include validation Web controls. The txtProductName TextBox has a RequiredFieldValidator associated with it, as the ProductName field is required, and the txtUnitPrice TextBox has a CompareValidator to ensure that a non-negative, currency-formatted value is supplied.

Validation With a ListView That Supports Inserting and Editing
The ListView used in this demo supports inserting, but does not support editing. However, it is possible to create a ListView that provides both inserting and editing functionality. If you create such a ListView and add validation controls to both the inserting and editing interfaces you may find that you cannot update a record until you "satisfy" the validators in the inserting interface. This is because by default ASP.NET checks all validation controls on the page when submitting. Long story short, even though the user is not inserting a record the Update button from the editing interface triggers the inserting-related validation controls to fire.The good news is that it's easy to partition the validation controls into separate validation groups - one for inserting and one for editing - which alleviates this issue. For more information on ASP.NET's validation controls and separating them into distinct groups, refer to Dissecting the Validation Controls in ASP.NET.


Two-Way Databinding in the Inserting Interface


As discussed in the Editing Data installment we explored how to use ASP.NET's two-way databinding syntax to get the values entered by the user into the editing interface into the data source control's update parameters. In short, you use the Bind statement to both assign a value into a Web control property when the editing interface is displayed and to take the value of that property and send it back out to the update parameters when the Update button is clicked. For example, with a TextBox you'd use the Bind statement on the Text property like so:
<asp:TextBox ID="ID" runat="server" Text='<%# Bind("columnName") %>' />

The inserting interface shown above includes Bind statements on the txtProductName and txtUnitPrice TextBox controls. When the user clicks the Insert button the ListView takes the values entered into these TextBoxes and puts them in the corresponding insert parameters in its data source control. You may have noticed that there are no Bind statements on the DropDownLists. Usually you'd have a Bind statement on the DropDownList's SelectedValue property. However, if you are using ASP.NET version 3.5 and add such a Bind statement you'll get the following error when visiting the page in a browser: System.InvalidOperationException: Databinding methods such as Eval(), XPath(), and Bind() can only be used in the context of a databound control.
This behavior is a bug in the ListView control in ASP.NET version 3.5. According to Microsoft it will be fixed in ASP.NET version 4.0. In the meantime to work around it you need to do the following:
  • Remove any Bind statements from the DropDownList controls in the InsertItemTemplate
  • Create an event handler for the ListView's ItemInserting event, which fires after the user has clicked the Insert button but before the insert command is sent to the data source control
  • Programmatically reference the DropDownList control(s) in the InsertItemTemplate using the syntax: e.Item.FindControl("DDLcontrolID")
  • Set the appropriate value(s) in the e.Values collection to the SelectedValue property of the DropDownList control(s)
The code for the ItemInserting event handler follows. Note that if you are using C# you would reference the Values collection items using square brackets instead of parentheses, like: e.Values["CategoryID"] = ....
Protected Sub lvProducts_ItemInserting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.ListViewInsertEventArgs) Handles lvProducts.ItemInserting
   'Set the values from the DropDownLists in the InsertItemTemplate
   Dim ddlSuppliers = CType(e.Item.FindControl("ddlSuppliers"), DropDownList)
   Dim ddlCategories = CType(e.Item.FindControl("ddlCategories"), DropDownList)

   e.Values("SupplierID") = ddlSuppliers.SelectedValue
   e.Values("CategoryID") = ddlCategories.SelectedValue
End Sub

With this code in place our ListView is complete! The user can visit the page, enter values into the TextBoxes and DropDownLists in the inserting interface, and click the Insert button to add a new record to the Products table.
The screen shots below show the end user's experience as she adds a new product to the database. The first screen shot shows the inserting interface after the user has entered in the new product's information. The second screen shot shows the ListView after the user has clicked the Insert button. Note that the inserting interface is returned to its initial state and the ListView displays the just-added record.

A new product, Scott's Ale, is being added to the Products table.


After clicking the Insert button, the product is added to the database and displayed in the ListView.

Short-Circuiting the Inserting Workflow


In some scenarios you may want to cancel the inserting workflow based on the user's input or some other type of programmatic logic. Perhaps the range of values that a user can enter for the new product's price depend on the supplier or category, or maybe there are certain business rules that disallow certain supplier/category pairings.Whatever the rationale may be, you have an opportunity to examine the values to be inserted before the insert is committed via the ItemInserting event handler. The demo available at the end of this article includes such an event handler that checks to see if the ProductName value entered by the user contains more than three words. If such a lengthy product name is found then the update is canceled and the user is shown a message explaining the issue.
The code for the ItemInserting event handler is straightforward. It pulls the value entered into the ProductName field via the e.Values collection. It then splits on spaces (" "). If there are more than two spaces in the product name then, presumably, there are more than three words, so the update is canceled by setting e.Cancel to True and a message is displayed via the DisplayAlert method, which is defined in a custom base Page class. If the check passes then the values from the DropDownLists are assigned to the appropriate parameters in the Values collection and a message is displayed informing the user that their product was added.

Protected Sub lvProducts_ItemInserting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.ListViewInsertEventArgs) Handles lvProducts.ItemInserting
   'Make sure product name does not contain more than three words
   'Split on " "
   Dim words() As String = e.Values("ProductName").ToString().Trim().Split(New Char() {" "})

   If words.Length > 2 Then
      'Invalid product name!
      e.Cancel = True

      MyBase.DisplayAlert("You cannot have more than three words in the product name. Please shorten your product name and try again.")
   
Else
      'Data is kosher, so set the values from the DropDownLists in the InsertItemTemplate
      Dim ddlSuppliers = CType(e.Item.FindControl("ddlSuppliers"), DropDownList)
      Dim ddlCategories = CType(e.Item.FindControl("ddlCategories"), DropDownList)

      e.Values("SupplierID") = ddlSuppliers.SelectedValue
      e.Values("CategoryID") = ddlCategories.SelectedValue

      MyBase.DisplayAlert(String.Format("The product {0} has been added.", e.Values("ProductName").ToString()))
   End If
End Sub 

The following screen shot shows the client-side messagebox that is displayed when the user attempts to add a new product with a name that's more than three words (such as "Aniseed Syrup Is Good!")

Product names with more than three words are not permitted.

Conclusion


The ListView control provides inserting, updating, and deleting functionality. As we saw in this installment, adding inserting capabilities to the ListView is relatively straightforward: create an inserting interface and set the ListView's InsertItemPosition property to either "FirstItem" or "LastItem", depending on where you want the inserting interface to appear. Keep in mind that in the inserting interface you cannot use two-way databinding to retrieve data from a DropDownList control; instead, you have to programmatically reference the DropDownLists and assign their SelectedValue properties to the appropriate values in the Values collection. This assignment can be performed from the ListView's ItemInserting event handler. And that's it! The data source control handles the actual insert logic.