Insertar un nuevo registro desde el pie de página de GridView (C#)

Por Scott Mitchell

Descargar PDF

Aunque el control GridView no proporciona compatibilidad integrada para insertar un nuevo registro de datos, en este tutorial se muestra cómo mejorar el GridView para incluir una interfaz de inserción.

Introducción

Como se describe en el tutorial Información general sobre la inserción, actualización y eliminación de datos, los controles web GridView, DetailsView y FormView incluyen cada uno capacidades de modificación de datos. Cuando se usa con controles de origen de datos declarativos, estos tres controles web se pueden configurar de forma rápida y sencilla para modificar datos y en escenarios sin necesidad de escribir una sola línea de código. Por desgracia, solo los controles DetailsView y FormView proporcionan capacidades integradas de inserción, edición y eliminación. GridView solo ofrece compatibilidad con la edición y la eliminación. Sin embargo, con un poco de esfuerzo, podemos mejorar el GridView para incluir una interfaz de inserción.

Al agregar capacidades de inserción a GridView, debemos encargarnos de decidir cómo se van a agregar los nuevos registros, a crear la interfaz de inserción y a escribir el código para insertar el nuevo registro. En este tutorial veremos cómo agregar la interfaz de inserción a una fila de pie de tabla de GridView (vea la figura 1). La celda de pie de tabla de cada columna incluye el elemento de interfaz de usuario de colección de datos adecuado (un TextBox si es un producto, un DropDownList si es un proveedor, etc.). También necesitamos una columna para un botón Agregar que, cuando se presione, provoque un postback e inserte un nuevo registro en la tabla Products con los valores proporcionados en la fila del pie de tabla.

The Footer Row Provides an Interface for Adding New Products

Figura 1: La fila pie de tabla proporciona una interfaz para agregar nuevos productos (haga clic para ver la imagen a tamaño completo)

Paso 1: Mostrar información del producto en un GridView

Antes de preocuparnos por crear la interfaz de inserción en el pie de tabla del GridView, primero nos centraremos en agregar un GridView a la página donde se muestran los productos de la base de datos. Para empezar, abra la página InsertThroughFooter.aspx en la carpeta EnhancedGridView, arrastre un control GridView desde el cuadro de herramientas al Diseñador y establezca la propiedad ID del GridView enProducts. Luego, en la etiqueta inteligente del GridView, elija enlazarlo a un nuevo ObjectDataSource denominado ProductsDataSource.

Create a New ObjectDataSource Named ProductsDataSource

Figura 2: Creación de un nuevo ObjectDataSource denominado ProductsDataSource (haga clic para ver la imagen a tamaño completo)

Configure el ObjectDataSource para que use el método GetProducts() de la clase ProductsBLL para recuperar información de producto. En este tutorial, nos centraremos exclusivamente en agregar capacidades de inserción; no nos preocuparemos por la edición o la eliminación. Por lo tanto, asegúrese de que la lista desplegable de la pestaña INSERTAR está establecida en AddProduct() y de que las listas desplegables de las pestañas ACTUALIZAR y ELIMINAR están establecidas en (Ninguno).

Map the AddProduct Method to the ObjectDataSource s Insert() Method

Figura 3: Asignación del método AddProduct al método Insert() del ObjectDataSource (haga clic para ver la imagen a tamaño completo)

Set the UPDATE and DELETE Tabs Drop-Down Lists to (None)

Figura 4: Establecimiento de las listas desplegables ACTUALIZAR y ELIMINAR en (Ninguno) (haga clic para ver la imagen a tamaño completo)

Después de completar el asistente para la configuración de orígenes de datos del ObjectDataSource, Visual Studio agregará automáticamente campos al GridView por cada uno de los campos de datos correspondientes. Por ahora, deje todos los campos que Visual Studio ha agregado. Más adelante en este tutorial, volveremos para quitar algunos de los campos cuyos valores no es necesario especificar al agregar un nuevo registro.

Dado que hay cerca de 80 productos en la base de datos, un usuario tendrá que desplazarse hasta la parte inferior de la página web para agregar un nuevo registro. Por lo tanto, vamos a habilitar la paginación para que la interfaz de inserción sea más visible y accesible. Para activar la paginación, simplemente active la casilla Habilitar paginación en la etiqueta inteligente del GridView.

Llegado este punto, el marcado declarativo del GridView y ObjectDataSource debe tener un aspecto similar al siguiente:

<asp:GridView ID="Products" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="ProductID" DataSourceID="ProductsDataSource" 
    AllowPaging="True" EnableViewState="False">
    <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>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
    InsertMethod="AddProduct" OldValuesParameterFormatString="original_{0}" 
    SelectMethod="GetProducts" TypeName="ProductsBLL">
    <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>

All Product Data Fields are Displayed in a Paged GridView

Figura 5: Todos los campos de datos de producto se muestran en un GridView con paginación (haga clic para ver la imagen a tamaño completo)

Además de filas de encabezado y de datos, GridView incluye una fila de pie de tabla. Las filas de encabezado y de pie de tabla se muestran en función de los valores de las propiedades ShowHeader y ShowFooter del GridView. Para mostrar la fila de pie de tabla, simplemente establezca la propiedad ShowFooter en true. Como se muestra en la figura 6, al establecer la propiedad ShowFooter en true, se agrega una fila de pie de tabla a la tabla.

To Display the Footer Row, Set ShowFooter to True

Figura 6: Para mostrar la fila de pie de tabla, establezca ShowFooter en True (haga clic para ver la imagen a tamaño completo)

Observe que la fila del pie de tabla tiene un color de fondo rojo oscuro. Esto se debe al tema DataWebControls que creamos y aplicamos a todas las páginas en el tutorial Visualizar datos con ObjectDataSource. En concreto, el archivo GridView.skin configura la propiedad FooterStyle de forma que usa la clase CSS FooterStyle. La clase FooterStyle se define en Styles.css del modo siguiente:

.FooterStyle
{
    background-color: #a33;
    color: White;
    text-align: right;
}

Nota:

En tutoriales anteriores, analizamos el uso de la fila de pie de tabla de GridView. Si es necesario, consulte el tutorial Mostrar información de resumen en el pie de tabla de GridView a modo de repaso.

Después de establecer la propiedad ShowFooter en true, dedique un momento a ver la salida en un explorador. Actualmente, la fila de pie de tabla no contiene ningún texto ni controles web. En el paso 3, modificaremos el pie de tabla de cada campo del GridView para que incluya la interfaz de inserción adecuada.

The Empty Footer Row is Displayed Above the Paging Interface Controls

Figura 7: La fila pie de tabla vacía se muestra encima de los controles de interfaz de paginación (haga clic para ver la imagen a tamaño completo)

En el tutorial Usar TemplateField en el control GridView vimos cómo personalizar profundamente la visualización de una determinada columna de GridView usando objetos TemplateField (en lugar de BoundField o CheckBoxField); en Personalizar la interfaz de modificación de datos, examinamos el uso de TemplateField para personalizar la interfaz de edición en un GridView. Recordemos que un TemplateField se compone de una serie de plantillas que definen la combinación de marcado, controles web y sintaxis de enlace de datos usada para determinados tipos de filas. Por ejemplo, ItemTemplate especifica la plantilla usada para las filas de solo lectura, mientras que EditItemTemplate define la plantilla usada para la fila editable.

Junto con ItemTemplate y EditItemTemplate, TemplateField incluye también un objeto FooterTemplate que especifica el contenido de la fila de pie de tabla. Por lo tanto, podemos agregar a FooterTemplate los controles web necesarios para la interfaz de inserción de cada campo. Para empezar, convierta todos los campos del GridView en TemplateField. Para ello, haga clic en el vínculo Editar columnas de la etiqueta inteligente del GridView, seleccione cada campo de la esquina inferior izquierda y haga clic en el vínculo Convertir este campo en un TemplateField.

Convert Each Field Into a TemplateField

Figura 8: Conversión de cada campo en un TemplateField

Al hacer clic en Convertir este campo en un TemplateField, el tipo de campo actual se convierte en un TemplateField equivalente. Por ejemplo, cada BoundField se reemplazará por un TemplateField con un ItemTemplate que contiene una etiqueta donde se muestra el campo de datos correspondiente y un EditItemTemplate que muestra el campo de datos en un TextBox. El BoundField ProductName se ha convertido en el siguiente marcado de TemplateField:

<asp:TemplateField HeaderText="ProductName" SortExpression="ProductName">
    <EditItemTemplate>
        <asp:TextBox ID="TextBox1" runat="server" 
            Text='<%# Bind("ProductName") %>'></asp:TextBox>
    </EditItemTemplate>
    <ItemTemplate>
        <asp:Label ID="Label2" runat="server" 
            Text='<%# Bind("ProductName") %>'></asp:Label>
    </ItemTemplate>
</asp:TemplateField>

Del mismo modo, el CheckBoxField Discontinued se ha convertido en un TemplateField cuyos elementos ItemTemplate y EditItemTemplate contienen un control web CheckBox (con el CheckBox ItemTemplate deshabilitado). El BoundField de solo lectura ProductID se ha convertido en un TemplateField con un control Label en ItemTemplate y EditItemTemplate. En resumen, convertir un campo de GridView existente en un TemplateField es una manera rápida y fácil de cambiar al uso de TemplateField, que son más personalizables, sin perder ninguna de las funciones de campo existentes.

Como el GridView con el que estamos trabajando no admite la edición, no dude en quitar EditItemTemplate de cada TemplateField y dejar únicamente ItemTemplate. Tras ello, el marcado declarativo de GridView debe tener un aspecto similar al siguiente:

<asp:GridView ID="Products" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="ProductID" DataSourceID="ProductsDataSource" 
    AllowPaging="True" EnableViewState="False" ShowFooter="True">
    <Columns>
        <asp:TemplateField HeaderText="ProductID" InsertVisible="False" 
            SortExpression="ProductID">
            <ItemTemplate>
                <asp:Label ID="Label1" runat="server" 
                    Text='<%# Bind("ProductID") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="ProductName" SortExpression="ProductName">
            <ItemTemplate>
                <asp:Label ID="Label2" runat="server" 
                    Text='<%# Bind("ProductName") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="SupplierID" SortExpression="SupplierID">
            <ItemTemplate>
                <asp:Label ID="Label3" runat="server" 
                    Text='<%# Bind("SupplierID") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="CategoryID" SortExpression="CategoryID">
            <ItemTemplate>
                <asp:Label ID="Label4" runat="server" 
                    Text='<%# Bind("CategoryID") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="QuantityPerUnit" 
            SortExpression="QuantityPerUnit">
            <ItemTemplate>
                <asp:Label ID="Label5" runat="server" 
                    Text='<%# Bind("QuantityPerUnit") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="UnitPrice" SortExpression="UnitPrice">
            <ItemTemplate>
                <asp:Label ID="Label6" runat="server" 
                    Text='<%# Bind("UnitPrice") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="UnitsInStock" 
            SortExpression="UnitsInStock">
            <ItemTemplate>
                <asp:Label ID="Label7" runat="server" 
                    Text='<%# Bind("UnitsInStock") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="UnitsOnOrder" 
            SortExpression="UnitsOnOrder">
            <ItemTemplate>
                <asp:Label ID="Label8" runat="server" 
                    Text='<%# Bind("UnitsOnOrder") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="ReorderLevel" 
            SortExpression="ReorderLevel">
            <ItemTemplate>
                <asp:Label ID="Label9" runat="server" 
                    Text='<%# Bind("ReorderLevel") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Discontinued" 
            SortExpression="Discontinued">
            <ItemTemplate>
                <asp:CheckBox ID="CheckBox1" runat="server" 
                    Checked='<%# Bind("Discontinued") %>' Enabled="false" />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="CategoryName" 
            SortExpression="CategoryName">
            <ItemTemplate>
                <asp:Label ID="Label10" runat="server" 
                    Text='<%# Bind("CategoryName") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="SupplierName" 
            SortExpression="SupplierName">
            <ItemTemplate>
                <asp:Label ID="Label11" runat="server" 
                    Text='<%# Bind("SupplierName") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

Ahora que cada campo de GridView se ha convertido en un TemplateField, podemos escribir la interfaz de inserción adecuada en el FooterTemplate de cada campo. Algunos campos no tendrán una interfaz de inserción (ProductID, por ejemplo); otros variarán en los controles web usados para recopilar la nueva información de producto.

Para crear la interfaz de edición, seleccione el vínculo Editar plantillas de la etiqueta inteligente del GridView. A continuación, en la lista desplegable, seleccione el FooterTemplate del campo adecuado y arrastre el control adecuado desde el cuadro de herramientas al Diseñador.

Add the Appropriate Inserting Interface to Each Field s FooterTemplate

Figura 9: Adición de la interfaz de inserción adecuada a cada campo FooterTemplate (haga clic para ver la imagen a tamaño completo)

En la siguiente lista con viñetas se enumeran los campos de GridView y se especifica la interfaz de inserción que se va a agregar:

  • ProductID: ninguna.
  • ProductName: agregue un TextBox y establezca su ID en NewProductName. Agregue también un control RequiredFieldValidator para asegurarse de que el usuario escribe un valor de nombre del nuevo producto.
  • SupplierID: ninguna.
  • CategoryID: ninguna.
  • QuantityPerUnit: agregue un TextBox y establezca su ID en NewQuantityPerUnit.
  • UnitPrice: agregue un TextBox denominado NewUnitPrice y un CompareValidator que garantice que el valor especificado sea un valor de moneda mayor o igual que cero.
  • UnitsInStock: use un TextBox cuyo ID esté establecido en NewUnitsInStock. Incluya un CompareValidator que garantice que el valor especificado sea un valor entero mayor o igual que cero.
  • UnitsOnOrder: use un TextBox cuyo ID esté establecido en NewUnitsOnOrder. Incluya un CompareValidator que garantice que el valor especificado sea un valor entero mayor o igual que cero.
  • ReorderLevel: use un TextBox cuyo ID esté establecido en NewReorderLevel. Incluya un CompareValidator que garantice que el valor especificado sea un valor entero mayor o igual que cero.
  • Discontinued: agregue un CheckBox y establezca su ID en NewDiscontinued.
  • CategoryName: agregue un DropDownList y establezca su ID en NewCategoryID. Enlácelo a un nuevo ObjectDataSource denominado CategoriesDataSource y configúrelo para usar el método CategoriesBLL de la clase GetCategories(). Haga que los elementos ListItem del DropDownList muestren el campo de datos CategoryName, utilizando para ello el campo de datos CategoryID como sus valores.
  • SupplierName: agregue un DropDownList y establezca su ID en NewSupplierID. Enlácelo a un nuevo ObjectDataSource denominado SuppliersDataSource y configúrelo para usar el método SuppliersBLL de la clase GetSuppliers(). Haga que los elementos ListItem del DropDownList muestren el campo de datos CompanyName, utilizando para ello el campo de datos SupplierID como sus valores.

En cada uno de los controles de validación, borre la propiedad ForeColor para que se use el color de primer plano blanco de la clase CSS FooterStyle en lugar del rojo predeterminado. Use también la propiedad ErrorMessage para obtener una descripción detallada, pero establezca la propiedad Text en un asterisco. Para evitar que el texto del control de validación haga que la interfaz de inserción se ajuste en dos líneas, establezca la propiedad Wrap de FooterStyle en false en cada uno de los objetos FooterTemplate que usen un control de validación. Por último, agregue un control ValidationSummary debajo del GridView y establezca su propiedad ShowMessageBox en true y su propiedad ShowSummary en false.

Al agregar un nuevo producto, debemos proporcionar los objetos CategoryID y SupplierID. Esta información se captura a través de los DropDownList en las celdas del pie de tabla de los campos CategoryName y SupplierName. Aquí he optado por usar estos campos en lugar de los TemplateField CategoryID y SupplierID porque en las filas de datos de la tabla probablemente el usuario esté más interesado en ver los nombres de categoría y proveedor que sus identificadores. Como ahora los valores de CategoryID y SupplierID se capturan en las interfaces de inserción de los campos CategoryName y SupplierName, podemos quitar los TemplateField CategoryID y SupplierID del GridView.

Del mismo modo, ProductID no se usa al agregar un nuevo producto, por lo que el TemplateField ProductID también se puede quitar. Sin embargo, vamos a dejar el campo ProductID en la tabla. Además de los controles TextBox, DropDownList, CheckBox y de validación que componen la interfaz de inserción, también necesitaremos un botón Agregar que, cuando se presione, ponga en marcha la lógica para agregar el nuevo producto a la base de datos. En el paso 4 incluiremos un botón Agregar en la interfaz de inserción, en el elemento FooterTemplate del TemplateField ProductID.

No dude en mejorar la apariencia de los distintos campos del GridView. Por ejemplo, puede que quiera dar un formato de moneda a los valores de UnitPrice, alinear a la derecha los campos UnitsInStock, UnitsOnOrder y ReorderLevel y actualizar los valores de HeaderText de los TemplateField.

Después de crear las interfaces de inserción en FooterTemplate, de quitar los TemplateField SupplierID y CategoryID y de mejorar la estética de la tabla aplicando formato y alineando los TemplateField, el marcado declarativo del GridView debe tener un aspecto similar al siguiente:

<asp:GridView ID="Products" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="ProductID" DataSourceID="ProductsDataSource" 
    AllowPaging="True" EnableViewState="False" ShowFooter="True">
    <Columns>
        <asp:TemplateField HeaderText="ProductID" InsertVisible="False" 
            SortExpression="ProductID">
            <ItemTemplate>
                <asp:Label ID="Label1" runat="server" 
                    Text='<%# Bind("ProductID") %>'></asp:Label>
            </ItemTemplate>
            <ItemStyle HorizontalAlign="Center" />
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Product" SortExpression="ProductName">
            <ItemTemplate>
                <asp:Label ID="Label2" runat="server" 
                    Text='<%# Bind("ProductName") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:TextBox ID="NewProductName" runat="server"></asp:TextBox>
                <asp:RequiredFieldValidator ID="RequiredFieldValidator1" 
                    runat="server" ControlToValidate="NewProductName"
                    Display="Dynamic"  ForeColor="
                    ErrorMessage="You must enter a name for the new product.">
                    * </asp:RequiredFieldValidator>
            </FooterTemplate>
            <FooterStyle Wrap="False" />
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Category" SortExpression="CategoryName">
            <ItemTemplate>
                <asp:Label ID="Label10" runat="server" 
                    Text='<%# Bind("CategoryName") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:DropDownList ID="NewCategoryID" runat="server" 
                    DataSourceID="CategoriesDataSource"
                    DataTextField="CategoryName" DataValueField="CategoryID">
                </asp:DropDownList>
                <asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
                    OldValuesParameterFormatString="original_{0}" 
                    SelectMethod="GetCategories" TypeName="CategoriesBLL">
                </asp:ObjectDataSource>
            </FooterTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Supplier" SortExpression="SupplierName">
            <ItemTemplate>
                <asp:Label ID="Label11" runat="server" 
                    Text='<%# Bind("SupplierName") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:DropDownList ID="NewSupplierID" runat="server" 
                    DataSourceID="SuppliersDataSource"
                    DataTextField="CompanyName" DataValueField="SupplierID">
                </asp:DropDownList><asp:ObjectDataSource ID="SuppliersDataSource" 
                    runat="server" OldValuesParameterFormatString="original_{0}" 
                    SelectMethod="GetSuppliers" TypeName="SuppliersBLL">
                </asp:ObjectDataSource>
            </FooterTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Qty/Unit" SortExpression="QuantityPerUnit">
            <ItemTemplate>
                <asp:Label ID="Label5" runat="server" 
                    Text='<%# Bind("QuantityPerUnit") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:TextBox ID="NewQuantityPerUnit" runat="server"></asp:TextBox>
            </FooterTemplate>
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Price" SortExpression="UnitPrice">
            <ItemTemplate>
                <asp:Label ID="Label6" runat="server" 
                    Text='<%# Bind("UnitPrice", "{0:c}") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                $<asp:TextBox ID="NewUnitPrice" runat="server" Columns="8" />
                <asp:CompareValidator ID="CompareValidator1" runat="server" 
                    ControlToValidate="NewUnitPrice"
                    ErrorMessage="You must enter a valid currency value greater than 
                        or equal to 0.00. Do not include the currency symbol."
                    ForeColor="" Operator="GreaterThanEqual" Type="Currency" 
                    ValueToCompare="0" Display="Dynamic">
                    * </asp:CompareValidator>
            </FooterTemplate>
            <ItemStyle HorizontalAlign="Right" />
            <FooterStyle Wrap="False" />
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Units In Stock" 
            SortExpression="Units In Stock">
            <ItemTemplate>
                <asp:Label ID="Label7" runat="server" 
                    Text='<%# Bind("UnitsInStock") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:TextBox ID="NewUnitsInStock" runat="server" Columns="5" />
                <asp:CompareValidator ID="CompareValidator2" runat="server" 
                    ControlToValidate="NewUnitsInStock" Display="Dynamic" 
                    ErrorMessage="You must enter a valid numeric value for units 
                        in stock that's greater than or equal to zero."
                    ForeColor="" Operator="GreaterThanEqual" Type="Integer" 
                        ValueToCompare="0">*</asp:CompareValidator>
            </FooterTemplate>
            <ItemStyle HorizontalAlign="Right" />
            <FooterStyle Wrap="False" />
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Units On Order" SortExpression="UnitsOnOrder">
            <ItemTemplate>
                <asp:Label ID="Label8" runat="server" 
                    Text='<%# Bind("UnitsOnOrder") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:TextBox ID="NewUnitsOnOrder" runat="server" Columns="5" />
                <asp:CompareValidator ID="CompareValidator3" runat="server" 
                    ControlToValidate="NewUnitsOnOrder" Display="Dynamic" 
                    ErrorMessage="You must enter a valid numeric value for units on 
                        order that's greater than or equal to zero."
                    ForeColor="" Operator="GreaterThanEqual" Type="Integer" 
                    ValueToCompare="0">*</asp:CompareValidator>
            </FooterTemplate>
            <ItemStyle HorizontalAlign="Right" />
            <FooterStyle Wrap="False" />
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Reorder Level" SortExpression="ReorderLevel">
            <ItemTemplate>
                <asp:Label ID="Label9" runat="server" 
                    Text='<%# Bind("ReorderLevel") %>'></asp:Label>
            </ItemTemplate>
            <FooterTemplate>
                <asp:TextBox ID="NewReorderLevel" runat="server" Columns="5" />
                <asp:CompareValidator ID="CompareValidator4" runat="server" 
                    ControlToValidate="NewReorderLevel" Display="Dynamic" 
                    ErrorMessage="You must enter a valid numeric value for reorder 
                        level that's greater than or equal to zero."
                    ForeColor="" Operator="GreaterThanEqual" Type="Integer" 
                    ValueToCompare="0">*</asp:CompareValidator>
            </FooterTemplate>
            <ItemStyle HorizontalAlign="Right" />
            <FooterStyle Wrap="False" />
        </asp:TemplateField>
        <asp:TemplateField HeaderText="Discontinued" SortExpression="Discontinued">
            <ItemTemplate>
                <asp:CheckBox ID="CheckBox1" runat="server" 
                    Checked='<%# Bind("Discontinued") %>' Enabled="false" />
            </ItemTemplate>
            <FooterTemplate>
                <asp:CheckBox ID="NewDiscontinued" runat="server" />
            </FooterTemplate>
            <ItemStyle HorizontalAlign="Center" />
            <FooterStyle HorizontalAlign="Center" />
        </asp:TemplateField>
    </Columns>
</asp:GridView>

Cuando se visualiza a través de un explorador, la fila de pie de tabla del GridView incluye ahora la interfaz de inserción completada (vea la figura 10). En este momento, la interfaz de inserción no incluye un medio para que el usuario indique que ha introducido los datos del nuevo producto y que desea insertar un nuevo registro en la base de datos. Además, todavía no hemos abordado cómo se van a convertir los datos introducidos en el pie de tabla en un nuevo registro de la base de datos Products. En el paso 4 veremos cómo incluir un botón Agregar a la interfaz de inserción y cómo ejecutar código en un postback cuando se haga clic en él. En el paso 5 se describe cómo insertar un nuevo registro usando los datos del pie de tabla.

The GridView Footer Provides an Interface for Adding a New Record

Figura 10: El pie de tabla del GridView proporciona una interfaz para agregar un nuevo registro (haga clic para ver la imagen a tamaño completo).

Paso 4: Incluir un botón Agregar en la interfaz de inserción

Necesitamos incluir un botón Agregar en algún lugar de la interfaz de inserción de fila del pie de tabla, ya que esta carece actualmente de alguna forma de que el usuario indique que ha completado la entrada de la nueva información del producto. Este botón podría colocarse en uno de los elementos FooterTemplate existentes, o podríamos agregar una nueva columna a la tabla para este propósito. En este tutorial, vamos a colocar el botón Agregar en el elemento FooterTemplate del TemplateField ProductID.

En el Diseñador, haga clic en el vínculo Editar plantillas de la etiqueta inteligente del GridView y, a continuación, seleccione el elemento FooterTemplate del campo ProductID en la lista desplegable. Agregue a la plantilla un control web Button (o LinkButton o ImageButton, si lo prefiere), estableciendo su identificador en AddProduct, su CommandName en Insertar y su propiedad Text en Agregar, como se muestra en la figura 11.

Place the Add Button in the ProductID TemplateField s FooterTemplate

Figura 11: Colocación del botón Agregar en el elemento FooterTemplate del TemplateField ProductID (haga clic para ver la imagen a tamaño completo)

Una vez que haya incluido el botón Agregar, pruebe la página en un explorador. Fíjese en que, al hacer clic en el botón Agregar con datos no válidos en la interfaz de inserción, el postback se cortocircuita y el control ValidationSummary indica los datos no válidos (vea la figura 12). Cuando se especifican datos adecuados, al hacer clic en el botón Agregar, se produce un postback, pero no se agrega ningún registro a la base de datos. Tendremos que escribir un poco de código para que la inserción se realice de verdad.

The Add Button s Postback is Short Circuited if There is Invalid Data in the Inserting Interface

Figura 12: El postback del botón Agregar se cortocircuita si hay datos no válidos en la interfaz de inserción (haga clic para ver la imagen a tamaño completo).

Nota:

Los controles de validación de la interfaz de inserción no estaban asignados a un grupo de validación. Esto funciona bien siempre que la interfaz de inserción sea el único conjunto de controles de validación de la página. Sin embargo, si hay otros controles de validación en la página (como los controles de validación en la interfaz de edición de la tabla), los controles de validación de la interfaz de inserción y las propiedades ValidationGroup del botón Agregar deben tener asignado el mismo valor para asociar estos controles a un grupo de validación determinado. Consulte Disección de los controles de validación de ASP.NET 2.0 para obtener más información sobre cómo particionar en grupos de validación los controles de validación y los botones de una página.

Paso 5: Insertar un nuevo registro en la tabla Products

Al usar las características de edición integradas del GridView, este controla automáticamente todo el trabajo necesario para realizar la actualización. En concreto, cuando se hace clic en el botón Actualizar, copia los valores especificados desde la interfaz de edición a los parámetros de la colección UpdateParameters del ObjectDataSource e inicia la actualización invocando el método Update() del ObjectDataSource. Dado que el GridView no proporciona una funcionalidad integrada de este tipo para realizar inserciones, debemos implementar código que llame al método Insert() del ObjectDataSource copie los valores de la interfaz de inserción en la colección InsertParameters del ObjectDataSource.

Esta lógica de inserción debe ejecutarse después de hacer clic en el botón Agregar. Como se describe en el tutorial Agregar y responder a los botones en un control GridView, cuando se hace clic en un control Button, LinkButton o ImageButton de un GridView, se activa el evento RowCommand del GridView en el postback. Este evento se activa tanto si el control Button, LinkButton o ImageButton se ha agregado explícitamente (como el botón Agregar en la fila del pie de tabla), como si se ha agregado automáticamente mediante el GridView (como los controles LinkButton de la parte superior de cada columna cuando se selecciona Habilitar ordenación, o los controles LinkButton de la interfaz de paginación cuando se selecciona Habilitar paginación).

Por lo tanto, para responder al usuario que hace clic en el botón Agregar, es necesario crear un controlador de eventos para el evento RowCommand del GridView. Dado que este evento se activa cada vez que se hace clic en cualquier control Button, LinkButton o ImageButton del GridView, es fundamental que continuemos con la lógica de inserción solo si la propiedad CommandName pasada al controlador de eventos se asigna al valor de CommandName del botón Agregar (Insertar). Además, solo deberíamos continuar si los controles de validación notifican datos válidos. Para dar cabida a esto, cree un controlador de eventos para el evento RowCommand con el código siguiente:

protected void Products_RowCommand(object sender, GridViewCommandEventArgs e)
{
    // Insert data if the CommandName == "Insert" 
    // and the validation controls indicate valid data...
    if (e.CommandName == "Insert" && Page.IsValid)
    {
        // TODO: Insert new record...
    }
}

Nota:

Es posible que se pregunte por qué el controlador de eventos se molesta en comprobar la propiedad Page.IsValid. Al fin y al cabo, ¿no se suprimirá el postback si se proporcionan datos no válidos en la interfaz de inserción? Esta suposición es correcta siempre que el usuario no haya deshabilitado JavaScript o haya tomado medidas para eludir la lógica de validación del lado cliente. En resumen, nunca se debe confiar estrictamente en la validación del lado cliente; siempre se debe realizar una comprobación de validez del lado servidor antes de trabajar con los datos.

En el paso 1 creamos el ObjectDataSource ProductsDataSource de forma que su método Insert() se asigna al método AddProduct de la clase ProductsBLL. Para insertar el nuevo registro en la tabla Products, simplemente podemos invocar el método Insert() del ObjectDataSource:

protected void Products_RowCommand(object sender, GridViewCommandEventArgs e)
{
    // Insert data if the CommandName == "Insert" 
    // and the validation controls indicate valid data...
    if (e.CommandName == "Insert" && Page.IsValid)
    {
        // Insert new record
        ProductsDataSource.Insert();
    }
}

Ahora que se ha invocado el método Insert(), lo único que nos queda es copiar los valores de la interfaz de inserción en los parámetros pasados al método AddProduct de la clase ProductsBLL. Como vimos en el tutorial Examinar los eventos relacionados con la inserción, actualización y eliminación, esto se puede lograr a través del evento Inserting del ObjectDataSource. En el evento Inserting, es necesario hacer referencia mediante programación a los controles de la fila de pie de tabla del GridView Products y asignar sus valores a la colección e.InputParameters. Si el usuario omite un valor, como dejar el TextBox ReorderLevel en blanco, es necesario especificar que el valor insertado en la base de datos debe ser NULL. Dado que el método AddProducts admite tipos que aceptan valores NULL en los campos de base de datos que aceptan valores NULL, simplemente use un tipo que acepte valores NULL y establezca su valor en null en caso de que la entrada del usuario se omita.

protected void ProductsDataSource_Inserting
    (object sender, ObjectDataSourceMethodEventArgs e)
{
    // Programmatically reference Web controls in the inserting interface...
    TextBox NewProductName = 
        (TextBox)Products.FooterRow.FindControl("NewProductName");
    DropDownList NewCategoryID = 
        (DropDownList)Products.FooterRow.FindControl("NewCategoryID");
    DropDownList NewSupplierID = 
        (DropDownList)Products.FooterRow.FindControl("NewSupplierID");
    TextBox NewQuantityPerUnit = 
        (TextBox)Products.FooterRow.FindControl("NewQuantityPerUnit");
    TextBox NewUnitPrice = 
        (TextBox)Products.FooterRow.FindControl("NewUnitPrice");
    TextBox NewUnitsInStock = 
        (TextBox)Products.FooterRow.FindControl("NewUnitsInStock");
    TextBox NewUnitsOnOrder = 
        (TextBox)Products.FooterRow.FindControl("NewUnitsOnOrder");
    TextBox NewReorderLevel = 
        (TextBox)Products.FooterRow.FindControl("NewReorderLevel");
    CheckBox NewDiscontinued = 
        (CheckBox)Products.FooterRow.FindControl("NewDiscontinued");
    // Set the ObjectDataSource's InsertParameters values...
    e.InputParameters["productName"] = NewProductName.Text;
    
    e.InputParameters["supplierID"] = 
        Convert.ToInt32(NewSupplierID.SelectedValue);
    e.InputParameters["categoryID"] = 
        Convert.ToInt32(NewCategoryID.SelectedValue);
    
    string quantityPerUnit = null;
    if (!string.IsNullOrEmpty(NewQuantityPerUnit.Text))
        quantityPerUnit = NewQuantityPerUnit.Text;
    e.InputParameters["quantityPerUnit"] = quantityPerUnit;
    decimal? unitPrice = null;
    if (!string.IsNullOrEmpty(NewUnitPrice.Text))
        unitPrice = Convert.ToDecimal(NewUnitPrice.Text);
    e.InputParameters["unitPrice"] = unitPrice;
    short? unitsInStock = null;
    if (!string.IsNullOrEmpty(NewUnitsInStock.Text))
        unitsInStock = Convert.ToInt16(NewUnitsInStock.Text);
    e.InputParameters["unitsInStock"] = unitsInStock;
    short? unitsOnOrder = null;
    if (!string.IsNullOrEmpty(NewUnitsOnOrder.Text))
        unitsOnOrder = Convert.ToInt16(NewUnitsOnOrder.Text);
    e.InputParameters["unitsOnOrder"] = unitsOnOrder;
    short? reorderLevel = null;
    if (!string.IsNullOrEmpty(NewReorderLevel.Text))
        reorderLevel = Convert.ToInt16(NewReorderLevel.Text);
    e.InputParameters["reorderLevel"] = reorderLevel;
    
    e.InputParameters["discontinued"] = NewDiscontinued.Checked;
}

Una vez completado el controlador de eventos Inserting, se pueden agregar nuevos registros a la tabla de base de datos Products a través de la fila de pie de tabla del GridView. Pruebe a agregar varios productos nuevos.

Mejorar y personalizar la operación de adición

Actualmente, al hacer clic en el botón Agregar, se agrega un nuevo registro a la tabla de base de datos, pero no se proporciona ningún tipo de pista visual que indique que el registro se ha agregado correctamente. Lo ideal es que un control web Label o un cuadro de alerta del lado cliente informe al usuario de que la inserción se ha completado correctamente. Lo dejo como ejercicio para el lector.

El GridView usado en este tutorial no aplica ningún criterio de ordenación a los productos enumerados, ni permite al usuario final ordenar los datos. Por lo tanto, los registros se ordenan tal como están en la base de datos, por el campo de clave principal. Puesto que cada nuevo registro tiene un valor de ProductID mayor que el último, cada vez que se agrega un nuevo producto, se coloca al final de la tabla. Por lo tanto, seguramente sea interesante dirigir al usuario de forma automática a la última página del GridView después de agregar un nuevo registro. Esto se puede lograr agregando la siguiente línea de código después de la llamada a ProductsDataSource.Insert() en el controlador de eventos RowCommand para indicar que hay que dirigir al usuario a la última página después de enlazar los datos al GridView:

// Indicate that the user needs to be sent to the last page
SendUserToLastPage = true;

SendUserToLastPage es una variable booleana de nivel de página que tiene asignado un valor inicial de false. En el controlador de eventos DataBound del GridView, si SendUserToLastPage es false, la propiedad PageIndex se actualiza para dirigir al usuario a la última página.

protected void Products_DataBound(object sender, EventArgs e)
{
    // Send user to last page of data, if needed
    if (SendUserToLastPage)
        Products.PageIndex = Products.PageCount - 1;
}

La razón por la que la propiedad PageIndex se establece en el controlador de eventos DataBound (en lugar del controlador de eventos RowCommand) es que cuando el controlador de eventos RowCommand se activa, aún nos queda agregar el nuevo registro a la tabla de base de datos Products. Por lo tanto, en el controlador de eventos RowCommand, el índice de última página (PageCount - 1) representa el índice de última página antes de agregar el nuevo producto. En la mayoría de los productos que se agregan, el índice de última página es el mismo después de agregar el nuevo producto. Sin embargo, cuando el producto agregado da como resultado un nuevo índice de última página, si actualizamos incorrectamente el elemento PageIndex en el controlador de eventos RowCommand, se nos dirigirá a la antepenúltima página (el índice de última página anterior a la adición del nuevo producto) en lugar del nuevo índice de última página. Dado que el controlador de eventos DataBound se activa después de agregar el nuevo producto y los datos se vuelven a enlazar a la tabla, al establecer la propiedad PageIndex allí, sabremos que estamos en el índice de última página correcto.

Por último, el GridView usado en este tutorial es bastante amplio debido al número de campos que se deben recopilar para agregar un nuevo producto. Debido a este ancho, es posible que se prefiera un diseño vertical del elemento DetailsView. El ancho general del GridView podría reducirse recopilando menos entradas. Quizás no sea necesario recopilar los campos UnitsOnOrder, UnitsInStock y ReorderLevel al agregar un nuevo producto, en cuyo caso estos campos se podrían quitar del GridView.

Para ajustar los datos recopilados, podemos usar uno de estos dos enfoques:

  • Seguir usando el método AddProduct, que espera valores en los campos UnitsOnOrder, UnitsInStock y ReorderLevel. En el controlador de eventos Inserting, proporcione valores predeterminados codificados de forma rígida que se usarán para estas entradas que se han quitado de la interfaz de inserción.
  • Crear una nueva sobrecarga del método AddProduct en la claseProductsBLL que no acepte entradas en los campos UnitsOnOrder, UnitsInStock y ReorderLevel. A continuación, en la página ASP.NET, configure el ObjectDataSource para usar esta nueva sobrecarga.

Ambas opciones funcionarán igual de bien. En los tutoriales anteriores usamos la última opción y creamos varias sobrecargas para el método UpdateProduct de la clase ProductsBLL.

Resumen

El GridView carece de las capacidades de inserción integradas que sí tienen DetailsView y FormView, pero con un poco de esfuerzo se puede agregar una interfaz de inserción a la fila de pie de tabla. Para mostrar la fila de pie de tabla en el GridView, simplemente establezca su propiedad ShowFooter en true. El contenido de la fila de pie de tabla se puede personalizar para cada campo; para ello, el campo se convierte en TemplateField y se agrega la interfaz de inserción a FooterTemplate. Como vimos en este tutorial, FooterTemplate puede contener controles Button, TextBox, DropDownList y CheckBox, así como controles de origen de datos para rellenar controles web basados en datos (como DropDownList) y controles de validación. Junto con los controles para recopilar la entrada del usuario, es necesario agregar un control Button, LinkButton o ImageButton.

Cuando se hace clic en el botón Agregar, se invoca el método Insert() del ObjectDataSource para iniciar el flujo de trabajo de inserción. A continuación, el ObjectDataSource llamará al método de inserción configurado (en este tutorial, el método AddProduct de la clase ProductsBLL). Debemos copiar los valores de la interfaz de inserción del GridView en la colección InsertParameters del ObjectDataSource antes de invocar el método de inserción. Esto se puede lograr haciendo referencia mediante programación a los controles web de la interfaz de inserción en el controlador de eventos Inserting del ObjectDataSource.

Con este tutorial se completa el panorama de técnicas existentes para mejorar la apariencia del GridView. En la siguiente tanda de tutoriales se examinará cómo trabajar con datos binarios, como imágenes, PDF, documentos de Word, etc. y los controles web de datos.

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, lleva trabajando con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, entrenador y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Puede ponerse en contacto con él a través de mitchell@4GuysFromRolla.com. o a través de su blog, que se puede encontrar en http://ScottOnWriting.NET.

Agradecimientos especiales a

Muchos revisores han evaluado esta serie de tutoriales. La revisora principal de este tutorial ha sido Bernadette Leigh. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.