Maestro y detalles mediante una lista con viñetas de registros maestros con un control DataList de detalles (VB)

por Scott Mitchell

Descargar PDF

En este tutorial se comprimirá el informe maestro/detalle de dos páginas del tutorial anterior en una sola página, en la que se muestra una lista con viñetas de nombres de categoría en el lado izquierdo de la pantalla y los productos de la categoría seleccionada en el derecho.

Introducción

En el tutorial anterior ha visto cómo dividir un informe maestro/detalle en dos páginas. En la página maestra se ha usado un control Repeater para representar una lista con viñetas de categorías. Cada nombre de categoría era un hipervínculo que, al hacer clic en él, llevaba al usuario a la página de detalles, donde un objeto DataList de dos columnas mostraba los productos pertenecientes a la categoría seleccionada.

En este tutorial se comprimirán las dos páginas en una sola, y se mostrará una lista con viñetas de nombres de categoría en el lado izquierdo de la pantalla, con cada nombre de categoría representado como un control LinkButton. Al hacer clic en uno de los controles LinkButton de nombre de categoría, se produce un postback y los productos de la categoría seleccionada se enlazan a un control DataList de dos columnas a la derecha de la pantalla. Además de mostrar cada nombre de categoría, el control Repeater de la izquierda muestra cuántos productos totales hay en una categoría determinada (vea la figura 1).

The Category s Name and Total Number of Products are Displayed on the Left

Figura 1: El nombre de la categoría y el número total de productos se muestran a la izquierda (Haga clic para ver la imagen a tamaño completo)

Paso 1: Representación de un control Repeater en la parte izquierda de la pantalla

En este tutorial es necesario que aparezca una lista con viñetas de categorías a la izquierda de los productos de la categoría seleccionada. El contenido de una página web se puede colocar mediante etiquetas de párrafo de elementos HTML estándar, espacios de no separación, <table>, etc., o bien mediante técnicas de hoja de estilos en cascada (CSS). En todos los tutoriales hasta ahora se han usado técnicas de CSS para la colocación de elementos. Al crear la interfaz de usuario de navegación en la página maestra del tutorial Páginas maestras y navegación del sitio, se usó posicionamiento absoluto, es decir, se indicó el desplazamiento de píxeles exacto para la lista de navegación y el contenido principal. Como alternativa, CSS se puede usar para colocar un elemento a la derecha o a la izquierda de otro si se convierte en flotante. Para que la lista con viñetas de categorías aparezca a la izquierda de los productos de la categoría seleccionada, puede hacer que el control Repeater aparezca como elemento flotante a la izquierda de DataList.

Abra la página CategoriesAndProducts.aspx desde la carpeta DataListRepeaterFiltering y agregue a la página un control Repeater y otro DataList. Establezca el elemento ID de Repeater en Categories y el de DataList en CategoryProducts. Vaya a la vista Origen y coloque los controles Repeater y DataList dentro de sus propios elementos <div>. Es decir, incluya primero el control Repeater en un elemento <div> y, después, DataList en su propio elemento <div>, directamente después de Repeater. El marcado en este punto debe tener un aspecto similar al siguiente:

<div>
    <asp:Repeater ID="Categories" runat="server">
    </asp:Repeater>
</div>
<div>
    <asp:DataList ID="CategoryProducts" runat="server">
    </asp:DataList>
</div>

Para hacer flotante el control Repeater a la izquierda de DataList, es necesario usar el atributo de estilo CSS float de esta forma:

<div>
    Repeater
</div>
<div>
    DataList
</div>

float: left; hace flotar el primer elemento <div> a la izquierda del segundo. Los valores width y padding-right indican el primer elemento width de <div>, y cuánto relleno va a haber entre el contenido del elemento <div> y el margen derecho. Para más información sobre cómo hacer flotar elementos en CSS, vea Floatutorial.

En lugar de especificar el valor de estilo directamente desde el atributo style del primer elemento <p>, se creará una clase CSS denominada FloatLeft en Styles.css:

.FloatLeft
{
    float: left;
    width: 33%;
    padding-right: 10px;
}

Después, puede reemplazar <div> por <div class="FloatLeft">.

Después de agregar la clase CSS y configurar el marcado en la página CategoriesAndProducts.aspx, vaya al Diseñador. El control Repeater flotante debería aparecer a la izquierda de DataList (aunque ahora ambos solo aparecen como cuadros grises, ya que todavía hay que configurar sus orígenes de datos o plantillas).

The Repeater is Floated to the Left of the DataList

Figura 2: El control Repeater flota a la izquierda de DataList (Haga clic para ver la imagen a tamaño completo)

Paso 2: Determinación del número de productos de cada categoría

Con el marcado circundante de Repeater y DataList completado, ya puede enlazar los datos de categoría al control Repeater. Pero como se muestra en la lista con viñetas de categorías de la figura 1, además de cada nombre de categoría, también es necesario mostrar el número de productos asociados a la categoría. Para acceder a esta información, puede hacer lo siguiente:

  • Obtener esta información de la clase de código subyacente de la página ASP.NET. Dado un categoryID determinado, puede determinar el número de productos asociados si llama al método GetProductsByCategoryID(categoryID) de la clase ProductsBLL. Este método devuelve un objeto ProductsDataTable cuya propiedad Count indica el número de ProductsRow que hay, y que es el número de productos del elemento categoryID especificado. Puede crear un controlador de eventos ItemDataBound para el control Repeater que, por cada categoría enlazada que tenga, llame al método GetProductsByCategoryID(categoryID) de la clase ProductsBLL e incluya el recuento en la salida.
  • Actualice CategoriesDataTable en el objeto DataSet con tipo, de forma que incluya una columna NumberOfProducts. Después, puede actualizar el método GetCategories() en CategoriesDataTable para incluir esta información o, como alternativa, dejar GetCategories() como está y crear un método CategoriesDataTable denominado GetCategoriesAndNumberOfProducts().

Ahora se explorarán las dos técnicas. El primer enfoque es más fácil de implementar, ya que no es necesario actualizar la capa de acceso a datos; pero necesita más comunicaciones con la base de datos. La llamada al método GetProductsByCategoryID(categoryID) de la clase ProductsBLL en el controlador de eventos ItemDataBound agrega una llamada de base de datos adicional por cada categoría que se muestra en el control Repeater. Con esta técnica hay N+1 llamadas de base de datos, donde N es el número de categorías mostradas en el control Repeater. El segundo enfoque devuelve el recuento de productos con información sobre cada categoría del método GetCategories() (o del método GetCategoriesAndNumberOfProducts()) de la clase CategoriesBLL, lo que da lugar a una sola comunicación con la base de datos.

Determinación del número de productos en el controlador de eventos ItemDataBound

Para determinar el número de productos de cada categoría en el controlador de eventos ItemDataBound del control Repeater no se necesita ninguna modificación de la capa de acceso a datos existente. Todas las modificaciones se pueden realizar directamente en la página CategoriesAndProducts.aspx. Para empezar, agregue un nuevo objeto ObjectDataSource denominado CategoriesDataSource desde la etiqueta inteligente del control Repeater. Luego, configure ObjectDataSource CategoriesDataSource para que recupere sus datos del método GetCategories() de la clase CategoriesBLL.

Configure the ObjectDataSource to Use the CategoriesBLL class s GetCategories() Method

Figura 3: Configuración de ObjectDataSource para usar el método GetCategories() de la clase CategoriesBLL (Haga clic para ver la imagen a tamaño completo)

Se debe poder hacer clic en cada elemento del control Repeater Categories y, cuando se haga clic en alguno, el control DataList CategoryProducts debe mostrar esos productos de la categoría seleccionada. Esto se puede lograr si convierte cada categoría en un hipervínculo que lleve a esta misma página (CategoriesAndProducts.aspx), pero que pase CategoryID por la cadena de consulta, de forma muy similar a lo que ha visto en el tutorial anterior. La ventaja de este enfoque es que un motor de búsqueda puede marcar e indexar una página en la que se muestren productos de una categoría determinada.

Como alternativa, puede convertir cada categoría en un control LinkButton, que es el enfoque que se usará en este tutorial. El control LinkButton se representa en el explorador del usuario como un hipervínculo, pero, cuando se hace clic en él, induce un postback; en el postback, el objeto ObjectDataSource de DataList debe actualizarse para mostrar los productos que pertenecen a la categoría seleccionada. En este tutorial, el uso de un hipervínculo tiene más sentido que usar un control LinkButton; pero puede haber otros escenarios en los que el uso de controles LinkButton sea más ventajoso. Aunque el enfoque de hipervínculo sería ideal en este ejemplo, en su lugar se describirá la opción con LinkButton. Como verá, el uso de un control LinkButton presenta algunos retos que no surgirían con un hipervínculo. Por tanto, el uso de LinkButton en este tutorial planteará estos desafíos y le ayudará a proporcionar soluciones a esos escenarios en los que conviene usar un control LinkButton en lugar de un hipervínculo.

Nota:

Le animamos a repetir este tutorial, pero usar un control Hyperlink o un elemento <a> en lugar de un control LinkButton.

En el siguiente marcado se muestra la sintaxis declarativa de los controles Repeater y ObjectDataSource. Fíjese en que las plantillas de Repeater muestran una lista con viñetas con cada elemento representado como un control LinkButton:

<asp:Repeater ID="Categories" runat="server" DataSourceID="CategoriesDataSource">
    <HeaderTemplate>
        <ul>
    </HeaderTemplate>
    <ItemTemplate>
        <li><asp:LinkButton runat="server" ID="ViewCategory"></asp:LinkButton></li>
    </ItemTemplate>
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>

Nota:

En este tutorial, el control Repeater debe tener habilitado su estado de visualización (observe que EnableViewState="False" se omite en la sintaxis declarativa de Repeater). En el paso 3 creará un controlador de eventos para el evento ItemCommand de Repeater en el que se actualizará la colección SelectParameters de la instancia ObjectDataSource de DataList. Pero el elemento ItemCommand del Repeater no se activará si el estado de visualización está deshabilitado.

El control LinkButton con un valor de propiedad ID de ViewCategory no tiene su propiedad Text establecida. Si solo hubiera querido mostrar el nombre de la categoría, habría establecido la propiedad Text mediante declaración, con la sintaxis de enlace de datos, de la siguiente manera:

<asp:LinkButton runat="server" ID="ViewCategory"
    Text='<%# Eval("CategoryName") %>' />

Pero quiere mostrar el nombre de la categoría y el número de productos que pertenecen a esa categoría. Esta información se puede recuperar del controlador de eventos ItemDataBound de Repeater con una llamada al método GetCategoriesByProductID(categoryID) de la clase ProductBLL, y determinar cuántos registros se devuelven en el elemento ProductsDataTable resultante, como se muestra en el código siguiente:

Protected Sub Categories_ItemDataBound(sender As Object, e As RepeaterItemEventArgs)
    ' Make sure we're working with a data item...
    If e.Item.ItemType = ListItemType.Item OrElse _
        e.Item.ItemType = ListItemType.AlternatingItem Then
        ' Reference the CategoriesRow instance bound to this RepeaterItem
        Dim category As Northwind.CategoriesRow = _
            CType(CType(e.Item.DataItem, System.Data.DataRowView).Row, _
                Northwind.CategoriesRow)
        ' Determine how many products are in this category
        Dim productsAPI As New NorthwindTableAdapters.ProductsTableAdapter
        Dim productCount As Integer = _
            productsAPI.GetProductsByCategoryID(category.CategoryID).Count
        ' Reference the ViewCategory LinkButton and set its Text property
        Dim ViewCategory As LinkButton = _
            CType(e.Item.FindControl("ViewCategory"), LinkButton)
        ViewCategory.Text = _
            String.Format("{0} ({1:N0})", category.CategoryName, productCount)
    End If
End Sub

Primero se asegurará de que trabaja con un elemento de datos (cuyo valor ItemType es Item o AlternatingItem) y, después, hará referencia a la instancia de CategoriesRow que acaba de enlazarse al objeto RepeaterItem actual. A continuación, debe determinar el número de productos de esta categoría con la creación de una instancia de la clase ProductsBLL, la llamada a su método GetCategoriesByProductID(categoryID) y la determinación del número de registros devueltos mediante la propiedad Count. Por último, se hace referencia al control LinkButton ViewCategory de ItemTemplate, y su propiedad Text se establece en CategoryName (NumberOfProductsInCategory), donde NumberOfProductsInCategory tiene un formato de número con cero posiciones decimales.

Nota:

Como alternativa, podría haber agregado una función de formato a la clase de código subyacente de la página ASP.NET que acepte los valores CategoryName y CategoryID de una categoría y devuelva el CategoryName concatenado con el número de productos de la categoría (según lo determinado mediante una llamada al método GetCategoriesByProductID(categoryID)). Los resultados de esta función de formato se podrían asignar mediante declaración a la propiedad Text de LinkButton, acabando así con la necesidad de usar el controlador de eventos ItemDataBound. Consulte los tutoriales Uso de controles TemplateField en el control GridView o Aplicación de formato a DataList y Repeater en función de los datos para más información sobre cómo usar funciones de formato.

Después de agregar este controlador de eventos, dedique un momento a probar la página en un explorador. Observe cómo cada categoría aparece en una lista con viñetas que muestra el nombre de la categoría y el número de productos asociados a la categoría (vea la figura 4).

Each Category s Name and Number of Products are Displayed

Figura 4: Se muestra cada nombre de categoría y el número de productos (Haga clic para ver la imagen a tamaño completo)

Actualización de CategoriesDataTable y CategoriesTableAdapter para incluir el número de productos de cada categoría

En lugar de determinar el número de productos de cada categoría a partir de su enlace con un control Repeater, se puede simplificar este proceso si se ajustan CategoriesDataTable y CategoriesTableAdapter en la capa de acceso a datos para incluir esta información de forma nativa. Para ello, debe agregar una nueva columna a CategoriesDataTable para contener en ella el número de productos asociados. Para agregar una nueva columna a DataTable, abra el objeto DataSet con tipo (App_Code\DAL\Northwind.xsd), haga clic con el botón derecho en el objeto DataTable que desea modificar y elija Agregar / Columna. Agregue una nueva columna a CategoriesDataTable (vea la figura 5).

Add a New Column to the CategoriesDataSource

Figura 5: Adición de una nueva columna a CategoriesDataSource (Haga clic para ver la imagen a tamaño completo)

Esto agregará una nueva columna denominada Column1, nombre que puede cambiar simplemente escribiendo otro. Cambie el nombre de esta nueva columna a NumberOfProducts. A continuación, es necesario configurar las propiedades de esta columna. Haga clic en la nueva columna y vaya a la ventana Propiedades. Cambie la propiedad DataType de la columna de System.String a System.Int32 y establezca la propiedad ReadOnly en True, como se muestra en la figura 6.

Set the DataType and ReadOnly Properties of the New Column

Figura 6: Establecimiento de las propiedades DataType y ReadOnly de la nueva columna

Aunque ahora CategoriesDataTable tiene una columna NumberOfProducts, el valor no se establece mediante ninguna de las consultas de TableAdapter correspondientes. Se puede actualizar el método GetCategories() para devolver esta información para que se devuelva cada vez que se recupere información de la categoría. Pero si solo es necesario obtener el número de productos asociados de las categorías de forma muy ocasional (como simplemente para este tutorial, por ejemplo), se puede dejar GetCategories() como está y crear un método que devuelva esta información. Se usará este último enfoque para crear un método denominado GetCategoriesAndNumberOfProducts().

Para agregar este nuevo método GetCategoriesAndNumberOfProducts(), haga clic con el botón derecho en CategoriesTableAdapter y seleccione Nueva consulta. Esto abre el asistente para la configuración de consultas de TableAdapter, que se ha usado numerosas veces en tutoriales anteriores. Para este método, inicie el asistente indicando que la consulta usa una instrucción SQL ad hoc que devuelve filas.

Create the Method Using an Ad-Hoc SQL Statement

Figura 7: Creación del método mediante una instrucción SQL ad hoc (Haga clic para ver la imagen a tamaño completo)

The SQL Statement Returns Rows

Figura 8: La instrucción SQL devuelve filas (Haga clic para ver la imagen a tamaño completo)

En la siguiente pantalla del asistente se solicita la consulta que se va a usar. Para devolver los campos CategoryID, CategoryName y Description de cada categoría, junto con el número de productos asociados a la categoría, use la siguiente instrucción SELECT:

SELECT CategoryID, CategoryName, Description,
       (SELECT COUNT(*) FROM Products p WHERE p.CategoryID = c.CategoryID)
            as NumberOfProducts
FROM Categories c

Specify the Query to Use

Figura 9: Especificación de la consulta que se va a usar (Haga clic para ver la imagen a tamaño completo)

Fíjese en que la subconsulta que calcula el número de productos asociados a la categoría recibe el alias NumberOfProducts. Esta coincidencia de nomenclatura hace que el valor devuelto por esta subconsulta se asocie a la columna NumberOfProducts de CategoriesDataTable.

Después de escribir la consulta, el último paso es elegir el nombre del nuevo método. Use FillWithNumberOfProducts y GetCategoriesAndNumberOfProducts en los campos Rellenar un DataTable y Devolver un DataTable, respectivamente.

Name the New TableAdapter s Methods FillWithNumberOfProducts and GetCategoriesAndNumberOfProducts

Figura 10: Asignación de los nombres FillWithNumberOfProducts y GetCategoriesAndNumberOfProducts a los nuevos métodos de TableAdapter (Haga clic para ver la imagen a tamaño completo)

Llegado este punto, la capa de acceso a datos se ha ampliado para incluir el número de productos por categoría. Como la capa de presentación enruta todas las llamadas a la DAL mediante una capa de lógica de negocios independiente, es necesario agregar el correspondiente método GetCategoriesAndNumberOfProducts a la clase CategoriesBLL:

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Select, False)> _
Public Function GetCategoriesAndNumberOfProducts() As Northwind.CategoriesDataTable
    Return Adapter.GetCategoriesAndNumberOfProducts()
End Function

Con la DAL y la BLL completadas, ya puede enlazar estos datos al control Repeater Categories en CategoriesAndProducts.aspx. Si ya ha creado una instancia de ObjectDataSource para el control Repeater en la sección Determinación del número de productos en el controlador de eventos ItemDataBound, elimínelo y quite el valor de la propiedad DataSourceID del control Repeater; asimismo, desasocie el evento ItemDataBound del control Repeater del controlador de eventos quitando la sintaxis Handles Categories.OnItemDataBound de la clase de código subyacente de ASP.NET.

Con el control Repeater de nuevo en su estado original, agregue un nuevo elemento ObjectDataSource denominado CategoriesDataSource desde la etiqueta inteligente de Repeater. Configure ObjectDataSource para que use la clase CategoriesBLL, pero en lugar de tener que usar el método GetCategories(), haga que use GetCategoriesAndNumberOfProducts() en su lugar (vea la figura 11).

Configure the ObjectDataSource to Use the GetCategoriesAndNumberOfProducts Method

Figura 11: Configuración de ObjectDataSource para usar el método GetCategoriesAndNumberOfProducts (Haga clic para ver la imagen a tamaño completo)

A continuación, actualice ItemTemplate para que la propiedad Text de LinkButton se asigne mediante declaración usando la sintaxis de enlace de datos e incluya los campos de datos CategoryName y NumberOfProducts. Este es el marcado declarativo completo del control Repeater y e ObjectDataSource CategoriesDataSource:

<asp:Repeater ID="Categories" runat="server" DataSourceID="CategoriesDataSource">
    <HeaderTemplate>
        <ul>
    </HeaderTemplate>
    <ItemTemplate>
        <li><asp:LinkButton runat="server" ID="ViewCategory"
                Text='<%# String.Format("{0} ({1:N0})", _
                    Eval("CategoryName"), Eval("NumberOfProducts")) %>' />
        </li>
    </ItemTemplate>
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetCategoriesAndNumberOfProducts" TypeName="CategoriesBLL">
</asp:ObjectDataSource>

La salida representada mediante la actualización de la DAL para incluir una columna NumberOfProducts es la misma que si se usara el enfoque del controlador de eventos ItemDataBound (vaya a la figura 4 para ver una captura de pantalla del control Repeater que muestra los nombres de categoría y el número de productos).

Paso 3: Representación de los productos de la categoría seleccionada

En este momento, en el control Repeater Categories se muestra la lista de categorías junto con el número de productos de cada categoría. Repeater usa un control LinkButton en cada categoría que, cuando se presiona, provoca un postback, momento en el que es necesario mostrar esos productos de la categoría seleccionada en el control DataList CategoryProducts.

Un reto que debe afrontar es cómo hacer que el control DataList muestre solo esos productos de la categoría seleccionada. En el tutorial Informe maestro/detalle mediante un control GridView maestro seleccionable con un control DetailView de detalles ha visto cómo crear un objeto GridView cuyas filas se pueden seleccionar y que los detalles de la fila seleccionada se mostraran en un control DetailsView en la misma página. El elemento ObjectDataSource de GridView devolvía información sobre todos los productos mediante el método GetProducts() de ProductsBLL, mientras que el elemento ObjectDataSource de DetailsView recuperaba información sobre el producto seleccionado mediante el método GetProductsByProductID(productID). El valor del parámetro productID se obtenía mediante declaración al asociarlo con el valor de la propiedad SelectedValue de GridView. Desafortunadamente, Repeater no tiene una propiedad SelectedValue y no puede servir como origen de parámetros.

Nota:

Este es uno de los retos que surgen al usar LinkButton en un control Repeater. Si, en su lugar, se usa un hipervínculo que pase CategoryID a través de la cadena de consulta, se podría usar ese campo QueryString como origen del valor del parámetro.

Antes de preocuparse por la falta de una propiedad SelectedValue en Repeater, primero se enlazará el control DataList a un elemento ObjectDataSource y se especificará ItemTemplate.

En la etiqueta inteligente de DataList, agregue un nuevo objeto ObjectDataSource llamado CategoryProductsDataSource y configúrelo para usar el método GetProductsByCategoryID(categoryID) de la clase ProductsBLL. Como el control DataList de este tutorial ofrece una interfaz de solo lectura, no dude en establecer las listas desplegables en las pestañas INSERT, UPDATE y DELETE en (None).

Configure the ObjectDataSource to Use ProductsBLL Class s GetProductsByCategoryID(categoryID) Method

Figura 12: Configuración de ObjectDataSource para usar el método GetProductsByCategoryID(categoryID) de la clase ProductsBLL (Haga clic para ver la imagen a tamaño completo)

Como el método GetProductsByCategoryID(categoryID) espera un parámetro de entrada (categoryID), el asistente para la configuración de orígenes de datos permite especificar el origen del parámetro. Si las categorías se hubieran enumerado en un control GridView o DataList, se habría establecido la lista desplegable Origen de parámetro en Control y ControlID, en el valor ID del control web de datos. Pero como el control Repeater carece de una propiedad SelectedValue, no se puede usar como origen de parámetros. Si se fija, verá que la lista desplegable ControlID solo contiene un control ID``CategoryProducts, el valor ID de DataList.

Por ahora, establezca la lista desplegable Origen de parámetro en Ninguno. Para finalizar, se asignará mediante programación este valor de parámetro cuando se haga clic en un control LinkButton de categoría de Repeater.

Do Not Specify a Parameter Source for the categoryID Parameter

Figura 13: Origen de parámetro sin especificar para el parámetro categoryID (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, Visual Studio genera automáticamente el objeto ItemTemplate de DataList. Reemplace este valor ItemTemplate predeterminado por la plantilla del tutorial anterior; establezca también la propiedad RepeatColumns de DataList en 2. Después de realizar estos cambios, el marcado declarativo de DataList y su objeto ObjectDataSource asociado debe tener un aspecto similar al siguiente:

<asp:DataList ID="CategoryProducts" runat="server" DataKeyField="ProductID"
    DataSourceID="CategoryProductsDataSource" RepeatColumns="2"
    EnableViewState="False">
    <ItemTemplate>
        <h5><%# Eval("ProductName") %></h5>
        <p>
            Supplied by <%# Eval("SupplierName") %><br />
            <%# Eval("UnitPrice", "{0:C}") %>
        </p>
    </ItemTemplate>
</asp:DataList>
<asp:ObjectDataSource ID="CategoryProductsDataSource"
    OldValuesParameterFormatString="original_{0}"  runat="server"
    SelectMethod="GetProductsByCategoryID" TypeName="ProductsBLL">
    <SelectParameters>
        <asp:Parameter Name="categoryID" Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

Actualmente, el parámetro categoryID de ObjectDataSource CategoryProductsDataSource no se ha configurado, por lo que no se muestra ningún producto al ver la página. Lo que debe hacer es configurar este valor de parámetro en función del valor CategoryID de la categoría en la que se haga clic en el control Repeater. Esto plantea dos retos. Primero: ¿cómo se sabe cuándo se ha hecho clic en un control LinkButton en el objeto ItemTemplate de Repeater? Y segundo: ¿cómo se puede determinar el valor CategoryID de la categoría correspondiente en cuyo control LinkButton se ha hecho clic?

El control LinkButton, al igual que los controles Button e ImageButton, tiene un evento Click y un evento Command. El evento Click está diseñado simplemente para señalar que se ha hecho clic en un control LinkButton. Pero en ocasiones, además de señalar que se ha hecho clic en un control LinkButton, también es necesario pasar información adicional al controlador de eventos. Si este es el caso, esta información adicional se puede asignar a las propiedades CommandName y CommandArgument de LinkButton. Así, cuando se haga clic en LinkButton, se activa su evento Command (en lugar del evento Click) y se pasan los valores de las propiedades CommandName y CommandArgument al controlador de eventos.

Cuando se genera un evento Command desde una plantilla de Repeater, el evento ItemCommand de Repeater se activa y se pasan los valores de CommandName y CommandArgument del control LinkButton (o Button, o ImageButton). Por tanto, para saber cuándo se ha hecho clic en el control LinkButton de una categoría en Repeater, es necesario hacer lo siguiente:

  1. Establezca la propiedad CommandName de LinkButton del objeto ItemTemplate de Repeater en un valor (aquí se ha usado ListProducts). Al establecer este valor de CommandName, se activa el evento Command de LinkButton cuando se haga clic en ese control.
  2. Establezca la propiedad CommandArgument del control LinkButton en el valor de CategoryID del elemento actual.
  3. Cree un controlador de eventos para el evento ItemCommand del control Repeater. En el controlador de eventos, establezca el parámetro CategoryID de ObjectDataSource CategoryProductsDataSource en el valor del objeto CommandArgument pasado.

El siguiente marcado de ItemTemplate del control Repeater de categorías implementa los pasos 1 y 2. Fíjese en cómo el valor de CommandArgument se ha establecido en el valor CategoryID del elemento de datos mediante la sintaxis de enlace de datos:

<ItemTemplate>
    <li>
        <asp:LinkButton CommandName="ListProducts"  runat="server"
            CommandArgument='<%# Eval("CategoryID") %>' ID="ViewCategory"
            Text='<%# string.Format("{0} ({1:N0})", _
                Eval("CategoryName"), Eval("NumberOfProducts")) %>'>
        </asp:LinkButton>
    </li>
</ItemTemplate>

Cada vez que cree un controlador de eventos ItemCommand, es prudente comprobar el valor de CommandName entrante, ya que cualquier evento Command generado por cualquier control Button, LinkButton o ImageButton dentro de Repeater hará que se active el evento ItemCommand. Aunque por ahora solo hay un control LinkButton de este tipo, en el futuro podría agregar más controles web Button a Repeater que, cuando se presionen, activen el mismo controlador de eventos ItemCommand. Por tanto, es mejor asegurarse siempre de comprobar la propiedad CommandName y continuar solo con la lógica de programación si coincide con el valor esperado.

Después de asegurarse de que el valor de CommandName pasado es igual a ListProducts, el controlador de eventos asigna el parámetro CategoryID de ObjectDataSource CategoryProductsDataSource al valor del elemento CommandArgument pasado. Esta modificación del elemento SelectParameters de ObjectDataSource hace que el control DataList se vuelva a enlazar automáticamente al origen de datos y muestre los productos de la categoría recién seleccionada.

Protected Sub Categories_ItemCommand(source As Object, e As RepeaterCommandEventArgs) _
    Handles Categories.ItemCommand
    ' If it's the "ListProducts" command that has been issued...
    If String.Compare(e.CommandName, "ListProducts", True) = 0 Then
        ' Set the CategoryProductsDataSource ObjectDataSource's CategoryID parameter
        ' to the CategoryID of the category that was just clicked (e.CommandArgument)...
        CategoryProductsDataSource.SelectParameters("CategoryID").DefaultValue = _
            e.CommandArgument.ToString()
    End If
End Sub

Con estas adiciones, el tutorial ha acabado. Dedique un momento a probarlo en un explorador. En la figura 14 se muestra la pantalla cuando se visita la página por primera vez. Como aún no se ha seleccionado una categoría, no se muestra ningún producto. Al hacer clic en una categoría, como Produce, se muestran los productos correspondientes en la categoría de productos, en una vista de dos columnas (vea la figura 15).

No Products are Displayed When First Visiting the Page

Figura 14: No se muestran productos al visitar la página por primera vez (Haga clic para ver la imagen a tamaño completo)

Clicking the Produce Category Lists the Matching Products to the Right

Figura 15: Al hacer clic en la categoría Produce, se muestran los productos coincidentes a la derecha (Haga clic para ver la imagen a tamaño completo)

Resumen

Como ha visto en este tutorial y en el anterior, los informes maestro/detalle se pueden distribuir entre dos páginas o condensarse en una sola. Pero mostrar un informe maestro/detalle en una sola página plantea algunos desafíos sobre cómo diseñar mejor los registros maestros y de detalles en la página. En el tutorial Informe maestro/detalle mediante un control GridView maestro seleccionable con un control DetailView de detalles hizo que los registros de detalles aparecieran encima de los registros maestros; en este tutorial, se usan técnicas de CSS para que los registros maestros floten a la izquierda de los detalles.

Junto con la representación de informes maestro/detalle, también ha tenido la oportunidad de explorar cómo recuperar el número de productos asociados a cada categoría, además de cómo ejecutar lógica del lado servidor cuando se hace clic en un control LinkButton (o Button, o ImageButton) desde un control Repeater.

Con este tutorial finaliza el análisis de informes maestro/detalle con DataList y Repeater. El siguiente lote de tutoriales ilustrará cómo agregar funcionalidades de edición y eliminación al control DataList.

¡Feliz programación!

Lecturas adicionales

Para más información sobre los temas tratados en este tutorial, consulte los siguientes recursos:

Acerca del autor

Scott Mitchell, autor de siete libros de ASP y ASP.NET, y fundador de 4GuysFromRolla.com, trabaja 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 de su blog, que se puede encontrar en http://ScottOnWriting.NET.

Agradecimientos especiales a

Esta serie de tutoriales fue revisada por muchos revisores. El revisor principal de este tutorial ha sido Zack Jones. ¿Le interesaría revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.