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

Por Scott Mitchell

Descargar PDF

En este tutorial comprimiremos 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 a el derecho.

Introducción

En el tutorial anterior vimos cómo dividir un informe maestro/detalle en dos páginas. En la página maestra usamos 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 comprimiremos las dos páginas en una sola, mostrando 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 LinkButton. Al hacer clic en uno de los LinkButton de nombre de categoría, se produce un postback y los productos de la categoría seleccionada se enlazan a un 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: Mostrar un control Repeater en la parte izquierda de la pantalla

En este tutorial necesitamos 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 duros, <table>, etc., o bien a través de técnicas de hoja de estilos en cascada (CSS). En todos nuestros tutoriales hasta ahora se han usado técnicas de CSS para la colocación de elementos. Cuando creamos la interfaz de usuario de navegación nuestra una página maestra en el tutorial Páginas maestras y navegación del sitio, usamos el posicionamiento absoluto, es decir, indicamos el desplazamiento de píxel exacto de 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 haciéndolo flotante. Para que la lista con viñetas de categorías aparezca a la izquierda de los productos de la categoría seleccionada, podemos 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 <div> elementos. Es decir, incluya primero el control Repeater en un elemento <div> y, a continuación, 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. Así:

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

float: left; hace flotar el primer elemento <div> a la izquierda del segundo. Las configuraciones 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 obtener más información sobre cómo hacer flotar elementos en CSS, consulte Floatutorial.

En lugar de especificar la configuración de estilo directamente a través del atributo style del primer elemento <p>, vamos a crear una nueva clase CSS en Styles.css denominada FloatLeft:

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

A continuación, podemos 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. Debería aparecer el control Repeater flotante a la izquierda de DataList (aunque ahora ambos solo aparecen como cuadros grises, ya que todavía tenemos 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: Determinar el número de productos de cada categoría

Con el marcado circundante de Repeater y DataList completado, ya estamos listos para enlazar los datos de categoría al control Repeater. Sin embargo, 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, podemos:

  • Obtener esta información de la clase de código subyacente de la página ASP.NET. Dado un categoryID determinado, podemos determinar el número de productos asociados llamando 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 categoryID especificado. Podemos 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. A continuación, podemos actualizar el método GetCategories() en CategoriesDataTable para incluir esta información o, como alternativa, dejar GetCategories() tal como está y crear un método CategoriesDataTable nuevo denominado GetCategoriesAndNumberOfProducts().

Vamos a explorar ambas técnicas. El primer enfoque es más fácil de implementar, ya que no es necesario actualizar la capa de acceso a datos; sin embargo, requiere 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 muestre en el control Repeater. Con esta técnica hay N+1 llamadas a 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.

Determinar el número de productos en el controlador de eventos ItemDataBound

Determinar el número de productos de cada categoría en el controlador de eventos ItemDataBound del control Repeater no requiere ninguna modificación en la capa de acceso a datos existente. Todas las modificaciones se pueden realizar directamente en la página CategoriesAndProducts.aspx. Empiece agregando un nuevo objeto ObjectDataSource denominado CategoriesDataSource a través de la etiqueta inteligente del Repeater. Luego, configure el 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 del 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 Repeater Categories y, cuando se haga clic en alguno, el DataList CategoryProducts debe mostrar esos productos de la categoría seleccionada. Esto puede lograrse haciendo que cada categoría sea un hipervínculo que lleve a esta misma página (CategoriesAndProducts.aspx), pero pasando CategoryID por la cadena de consulta, de forma muy similar a la que vimos en el tutorial anterior. La ventaja de este enfoque es que un motor de búsqueda puede marcar e indizar una página en la que se muestren productos de una categoría determinada.

Como alternativa, podemos convertir cada categoría en un control LinkButton, que es el enfoque que usaremos 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; sin embargo, 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, vamos a explorar en su lugar la opción con LinkButton. Como veremos, el uso de un control LinkButton presenta algunos retos que no surgirían con un hipervínculo. Por lo tanto, el uso de LinkButton en este tutorial nos planteará estos desafíos y nos 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 usando un control Hyperlink o un elemento <a> en lugar de un control LinkButton.

El siguiente marcado 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 LinkButton:

<asp:Repeater ID="Categories" runat="server" DataSourceID="CategoriesDataSource">
    <HeaderTemplate>
        <ul>
    </HeaderTemplate>
    <ItemTemplate>
        <li><asp:LinkButton runat="server" ID="ViewCategory" /></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 vista (observe que EnableViewState="False" se omite en sintaxis declarativa del Repeater). En el paso 3 crearemos un controlador de eventos para el evento ItemCommand de Repeater en el que se actualizará la colección SelectParameters del ObjectDataSource de DataList. Sin embargo, el elemento ItemCommand del Repeater no se activará si el estado de vista está deshabilitado.

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

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

Sin embargo, queremos 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 del Repeater realizando una llamada al método GetCategoriesByProductID(categoryID) de la clase ProductBLL, y determina cuántos registros se devuelven en el elemento ProductsDataTable resultante, como se muestra en el código siguiente:

protected void Categories_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    // Make sure we're working with a data item...
    if (e.Item.ItemType == ListItemType.Item ||
        e.Item.ItemType == ListItemType.AlternatingItem)
    {
        // Reference the CategoriesRow instance bound to this RepeaterItem
        Northwind.CategoriesRow category =
            (Northwind.CategoriesRow) ((System.Data.DataRowView) e.Item.DataItem).Row;
        // Determine how many products are in this category
        NorthwindTableAdapters.ProductsTableAdapter productsAPI =
            new NorthwindTableAdapters.ProductsTableAdapter();
        int productCount =
            productsAPI.GetProductsByCategoryID(category.CategoryID).Count;
        // Reference the ViewCategory LinkButton and set its Text property
        LinkButton ViewCategory = (LinkButton)e.Item.FindControl("ViewCategory");
        ViewCategory.Text =
            string.Format("{0} ({1:N0})", category.CategoryName, productCount);
    }
}

Empezaremos asegurándonos de que estamos trabajando con un elemento de datos (uno cuyo ItemType es Item o AlternatingItem) y, a continuación, haremos referencia a la instancia de CategoriesRow que acaba de enlazarse al objeto RepeaterItem actual. A continuación, averiguamos el número de productos de esta categoría creando una instancia de la clase ProductsBLL, llamando a su método GetCategoriesByProductID(categoryID) y determinando el número de registros devueltos mediante la propiedad Count. Por último, se hace referencia al 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íamos 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 pueden asignar mediante declaración a la propiedad Text del LinkButton, acabando así con la necesidad de usar el controlador de eventos ItemDataBound. Consulte los tutoriales Usar TemplateField en el control GridView o Aplicar formato a DataList y Repeater en función de los datos) para obtener 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 a través de 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)

Actualizar 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, podemos simplificar este proceso ajustando CategoriesDataTable y CategoriesTableAdapter en la capa de acceso a datos para incluir esta información de forma nativa. Para ello, debemos 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 aquí 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. Podemos actualizar el método GetCategories() para devolver esta información si queremos que dicha información se devuelva cada vez que se recupere información de la categoría. Sin embargo, si solo necesitamos obtener el número de productos asociados de las categorías de forma muy ocasional (como simplemente para este tutorial, por ejemplo), podemos dejar GetCategories() tal como está y crear un nuevo método que devuelva esta información. Vamos a usar este último enfoque y a crear un nuevo 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 hemos 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)

La siguiente pantalla del asistente nos pide 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 un nuevo nombre a los métodos FillWithNumberOfProducts y GetCategoriesAndNumberOfProducts de TableAdapter (haga clic aquí 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. Puesto que nuestra capa de presentación enruta todas las llamadas a la DAL a través de una capa de lógica empresarial independiente, es necesario agregar el correspondiente método GetCategoriesAndNumberOfProducts a la clase CategoriesBLL:

[System.ComponentModel.DataObjectMethodAttribute
    (System.ComponentModel.DataObjectMethodType.Select, false)]
public Northwind.CategoriesDataTable GetCategoriesAndNumberOfProducts()
{
    return Adapter.GetCategoriesAndNumberOfProducts();
}

Con la DAL y la BLL completadas, ¡ya estamos listos para enlazar estos datos al Repeater Categories en CategoriesAndProducts.aspx! Si ya creó un ObjectDataSource para el Repeater en la sección Determinar el número de productos en el controlador de eventos ItemDataBound, elimínelo y quite el valor de la propiedad DataSourceID del Repeater; asimismo, desasocie el evento de Repeater ItemDataBound del controlador de eventos quitando la sintaxis Handles Categories.OnItemDataBound de la clase de código subyacente de ASP.NET.

Con el Repeater de nuevo en su estado original, agregue un nuevo ObjectDataSource denominado CategoriesDataSource a través de la etiqueta inteligente del Repeater. Configure el 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 del 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 Repeater y el 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 de Repeater que muestra los nombres de categoría y el número de productos).

Paso 3: Mostrar los productos de la categoría seleccionada

En este momento, nuestro Repeater Categories muestra la lista de categorías junto con el número de productos de cada categoría. El 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 DataList CategoryProducts.

Un reto que debemos afrontar es cómo hacer que el DataList muestre solo esos productos de la categoría seleccionada. En el tutorial Informe maestro/detalle mediante un GridView maestro seleccionable con un DetailView de detalles vimos cómo crear un objeto GridView cuyas filas se pueden seleccionar y que los detalles de la fila seleccionada se mostraran en un DetailsView en la misma página. El ObjectDataSource de GridView devolvía información sobre todos los productos mediante el método GetProducts() de ProductsBLL, mientras que el 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 del GridView. Desafortunadamente, Repeater no tiene una propiedad SelectedValue y no nos puede servir como origen de parámetros.

Nota:

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

Antes de preocuparnos por la falta de una propiedad SelectedValue en el Repeater, vamos a enlazar primero el DataList a un ObjectDataSource y a especificar su ItemTemplate.

En la etiqueta inteligente del DataList, agregue si lo desea un nuevo ObjectDataSource llamado CategoryProductsDataSource y configúrelo para usar el método GetProductsByCategoryID(categoryID) de la clase ProductsBLL. Dado que DataList de este tutorial ofrece una interfaz de solo lectura, no dude en configurar las listas desplegables en las pestañas INSERTAR, ACTUALIZAR y ELIMINAR en (Ninguno).

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)

Dado que el método GetProductsByCategoryID(categoryID) espera un parámetro de entrada (categoryID), el asistente para la configuración de orígenes de datos nos permite especificar el origen del parámetro. Si las categorías se hubieran enumerado en un GridView o un DataList, habríamos establecido la lista desplegable Origen de parámetro en Control y ControlID, en el ID del control web de datos. Sin embargo, dado que el 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 ID de DataList.

Por ahora, establezca la lista desplegable Origen de parámetro en Ninguno. Finalizaremos asignando mediante programación este valor de parámetro cuando se haga clic en una categoría LinkButton del 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 del DataList. Reemplace este ItemTemplate predeterminado por la plantilla que usamos en el tutorial anterior; establezca también la propiedad RepeatColumns del DataList en 2. Después de realizar estos cambios, el marcado declarativo del DataList y su 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 del ObjectDataSource CategoryProductsDataSourceno se ha configurado, por lo que no se muestra ningún producto al ver la página. Lo que debemos hacer es configurar este valor de parámetro en función del CategoryID de la categoría en la que se haga clic en el Repeater. Esto plantea dos retos. Primero: ¿cómo sabemos cuándo se ha hecho clic en un LinkButton en el objeto ItemTemplate del Repeater? Y segundo: ¿cómo podemos determinar el CategoryID de la categoría correspondiente en cuyo 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 LinkButton. Sin embargo, en ocasiones, además de señalar que se ha hecho clic en un 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 del LinkButton. Así, cuando se haga clic en el 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 del Repeater, el evento ItemCommand del Repeater se activa y se pasan los valores de CommandName y CommandArgument del LinkButton (o Button, o ImageButton). Por lo tanto, para saber cuándo se ha hecho clic en una categoría LinkButton del Repeater, es necesario hacer lo siguiente:

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

El siguiente marcado de ItemTemplate del Repeater de categorías implementa los pasos 1 y 2. Fíjese en cómo el valor de CommandArgument se ha establecido en el CategoryID del elemento de datos mediante 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 siempre el valor de CommandName entrante, ya que cualquier evento Command generado por cualquier Button, LinkButton o ImageButton dentro del Repeater hará que se active el evento ItemCommand. Aunque por ahora solo tenemos un LinkButton de este tipo, en el futuro podríamos agregar más controles web Button al Repeater que, cuando se presionen, activen el mismo controlador de eventos ItemCommand. Por lo 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 del ObjectDataSource CategoryProductsDataSource al valor del CommandArgument pasado. Esta modificación del elemento SelectParameters del ObjectDataSource hace que el DataList se vuelva a enlazar automáticamente al origen de datos, mostrando así los productos de la categoría recién seleccionada.

protected void Categories_ItemCommand(object source, RepeaterCommandEventArgs e)
{
    // If it's the "ListProducts" command that has been issued...
    if (string.Compare(e.CommandName, "ListProducts", true) == 0)
    {
        // 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();
    }
}

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. Puesto que 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 hemos visto en este tutorial y en el anterior, los informes maestro/detalle se pueden distribuir entre dos páginas o condensarse en uno solo. Sin embargo, 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 GridView maestro seleccionable con un DetailView de detalles hicimos que los registros de detalles aparecieran encima de los registros maestros; en este tutorial, usamos técnicas de CSS para que los registros maestros floten a la izquierda de los detalles.

Junto con la visualización de informes maestro/detalle, también hemos tenido la oportunidad de explorar cómo recuperar el número de productos asociados a cada categoría, además de cómo implantar una lógica del lado servidor cuando se hace clic en un 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 obtener 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/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. El revisor principal de este tutorial ha sido Zack Jones. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.