Filtrado de maestro y detalle entre dos páginas mediante un control Repeater y DataList (C#)

por Scott Mitchell

Descargar PDF

En este tutorial veremos cómo separar un informe maestro/detalle en dos páginas. En la página "maestro" usamos un control Repeater para representar una lista de categorías que, cuando se hace clic, llevará al usuario a la página "detalles" donde una lista de datos de dos columnas muestra esos productos pertenecientes a la categoría seleccionada.

Introducción

En el tutorial anterior vimos cómo mostrar informes de maestro y detalle en una sola página web mediante DropDownLists para mostrar los registros "maestros" y una lista de datos para mostrar los "detalles". Otro patrón común que se usa para los informes de maestro y detalle es tener los registros maestros en una página web y los detalles en otra. En el tutorial anterior sobre Filtrado de maestros y detalles entre dos páginas, examinamos este patrón mediante GridView para mostrar todos los proveedores del sistema. Esta clase GridView incluía un HyperLinkField, que se representaba como un vínculo a una segunda página, pasando el elemento SupplierID en la cadena de consulta. La segunda página usó GridView para enumerar los productos proporcionados por el proveedor seleccionado.

Estos informes de maestros y detalles en dos páginas también se pueden realizar mediante controles DataList y Repeater. La única diferencia es que ni DataList ni Repeater proporcionan compatibilidad con el control HyperLinkField. En su lugar, debemos agregar un control web de HyperLink o un elemento HTML delimitador (<a>) dentro del control ItemTemplate. La propiedad NavigateUrl de HyperLink o el atributo href del delimitador se pueden personalizar mediante enfoques declarativos o mediante programación.

En este tutorial exploraremos un ejemplo en el que se enumeran las categorías de una lista con viñetas en una página mediante un control Repeater. Cada elemento de la lista incluirá el nombre y la descripción de la categoría, con el nombre de categoría que se muestra como un vínculo a una segunda página. Al hacer clic en este vínculo, el usuario pasará a la segunda página, donde una lista de datos mostrará los productos que pertenecen a la categoría seleccionada.

Paso 1: Mostrar las categorías en una lista con viñetas

El primer paso para crear cualquier informe maestro o detalle consiste en empezar mostrando los registros "maestros". Por lo tanto, nuestra primera tarea es mostrar las categorías en la página "maestra". Abra la página CategoryListMaster.aspx en la carpeta DataListRepeaterFiltering, agregue un control Repeater y, desde la etiqueta inteligente, opte por agregar un nuevo ObjectDataSource. Configure el nuevo ObjectDataSource para que acceda a sus datos desde el método GetCategories de la clase CategoriesBLL (vea la Ilustración 1).

Configure the ObjectDataSource to Use the CategoriesBLL Class's GetCategories Method

Ilustración 1: Configuración de ObjectDataSource para usar el método GetCategories de la clase CategoriesBLL (haga clic para ver la imagen de tamaño completo)

A continuación, defina las plantillas del control Repeater de modo que muestre cada nombre de categoría y descripción como un elemento de una lista con viñetas. Aún no nos preocuparemos por tener cada categoría enlazada a la página de detalles. A continuación se muestra el marcado declarativo para Repeater y ObjectDataSource:

<asp:Repeater ID="Repeater1" runat="server" DataSourceID="ObjectDataSource1"
    EnableViewState="False">
    <HeaderTemplate>
        <ul>
    </HeaderTemplate>
 
    <ItemTemplate>
        <li><%# Eval("CategoryName") %> - <%# Eval("Description") %></li>
    </ItemTemplate>
 
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater>
 
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>

Con este marcado completo, dedique un momento a ver nuestro progreso a través de un explorador. Como se muestra en la Ilustración 2, Repeater se representa como una lista con viñetas que muestra el nombre y la descripción de cada categoría.

Each Category is Displayed as a Bulleted List Item

Ilustración 2: Cada categoría se muestra como un elemento de lista con viñetas (haga clic para ver la imagen de tamaño completo)

Para permitir que un usuario muestre la información de "detalles" de una categoría determinada, es necesario agregar un vínculo a cada elemento de lista con viñetas que, al hacer clic, llevará al usuario a la segunda página (ProductsForCategoryDetails.aspx). A continuación, esta segunda página mostrará los productos de la categoría seleccionada mediante una lista de datos. Para determinar la categoría en la que se hizo clic en el vínculo, es necesario pasar elCategoryID de la categoría a la segunda página mediante algún mecanismo. La forma más sencilla y sencilla de transferir datos escalares de una página a otra es a través de la cadena de consultas, que es la opción que usaremos en este tutorial. En concreto, la página ProductsForCategoryDetails.aspx espera que el valor categoryID seleccionado se pase a través de un campo de cadena de consulta denominado CategoryID. Por ejemplo, para ver los productos de la categoría Bebidas, que tiene un CategoryID de 1, un usuario visitaría ProductsForCategoryDetails.aspx?CategoryID=1.

Para crear un hipervínculo para cada elemento de lista con viñetas en el Repeater, es necesario agregar un control web de HyperLink o un elemento delimitador HTML (<a>) a ItemTemplate. En escenarios en los que el hipervínculo se muestra igual para cada fila, cualquier enfoque será suficiente. Para Repeaters, prefiero usar el elemento delimitador. Para usar el elemento delimitador, actualice ItemTemplate de Repeater a:

<li>
    <a href='ProductsForCategoryDetails.aspx?CategoryID=<%# Eval("CategoryID") %>'>
        <%# Eval("CategoryName") %>
    </a> - <%# Eval("Description") %>
</li>

Tenga en cuenta que CategoryID se puede insertar directamente dentro del atributo href del elemento delimitador; sin embargo, para hacerlo, asegúrese de delimitar el valor del atributo href con apóstrofos (y comillas), ya que el método Eval dentro del atributo href delimita su cadena ("CategoryID") con comillas. Como alternativa, se puede usar un control web de HyperLink en su lugar:

<li>
    <asp:HyperLink runat="server" Text='<%# Eval("CategoryName") %>'
        NavigateUrl='<%# "ProductsForCategoryDetails.aspx?CategoryID=" &
            Eval("CategoryID") %>'>
    </asp:HyperLink>
    - <%# Eval("Description") %>
</li>

Observe cómo la parte estática de la dirección URL (ProductsForCategoryDetails.aspx?CategoryID) se anexa al resultado de Eval("CategoryID") directamente dentro de la sintaxis de enlace de datos mediante la concatenación de cadenas.

Una ventaja de usar el control HyperLink es que se puede acceder mediante programación desde el controlador de eventos ItemDataBound del Repeater, si es necesario. Por ejemplo, puede que desee mostrar el nombre de categoría como texto en lugar de como un vínculo para categorías sin productos asociados. Esta comprobación podría realizarse mediante programación en el controlador de eventos ItemDataBound; para las categorías sin productos asociados, la propiedad NavigateUrl de HyperLink podría establecerse en una cadena en blanco, lo que da lugar a que ese nombre de categoría concreto se representara como texto sin formato (en lugar de como un vínculo). Consulte el tutorial Formato de DataList y Repeater basado en datos para obtener más información sobre el formato de DataList y el contenido del Repeater en función de la lógica de programaciónItemDataBound mediante el controlador de eventos.

Si sigue estos pasos, no dude en usar el elemento de anclaje o el enfoque de control HyperLink en la página. Independientemente del enfoque, al ver la página a través de un explorador, cada nombre de categoría debe representarse como un vínculo a ProductsForCategoryDetails.aspx, pasando el valor aplicable CategoryID (vea la Ilustración 3).

The Category Names Now Link to ProductsForCategoryDetails.aspx

Ilustración 3: Los nombres de categoría ahora enlazan a ProductsForCategoryDetails.aspx (haga clic para ver la imagen de tamaño completo)

Paso 3: Enumerar los productos que pertenecen a la categoría seleccionada

Con la página completa CategoryListMaster.aspx, estamos listos para poner nuestra atención en la implementación de la página "detalles", ProductsForCategoryDetails.aspx. Abra esta página, arrastre un objeto DataList desde el Cuadro de herramientas al Diseñador y establezca su propiedad ID en ProductsInCategory. A continuación, en la etiqueta inteligente de DataList, elija agregar un nuevo ObjectDataSource a la página y asígnele el nombre ProductsInCategoryDataSource. Configúrelo de forma que llame al método GetProductsByCategoryID(categoryID) de la clase ProductsBLL; establezca las listas desplegables en las pestañas INSERT, UPDATE y DELETE en (None).

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

Ilustración 4. Configure el 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) acepta un parámetro de entrada (categoryID), el Asistente para elegir origen de datos nos ofrece la oportunidad de especificar el origen del parámetro. Establezca el origen del parámetro en QueryString mediante CategoryID de QueryStringField.

Use the Querystring Field CategoryID as the Parameter's Source

Ilustración 5: Usar el campo CategoryID de Querystring como origen del parámetro (haga clic para ver la imagen de tamaño completo)

Como hemos visto en los tutoriales anteriores, después de completar el Asistente para elegir origen de datos, Visual Studio crea automáticamente un ItemTemplate para DataList que enumera cada nombre y valor de campo de datos. Reemplace esta plantilla por una que muestre solo el nombre, el proveedor y el precio del producto. Además, establezca la propiedad RepeatColumns de DataList en 2. Después de estos cambios, el marcado declarativo de DataList y ObjectDataSource debe ser similar al siguiente:

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

Para ver esta página en acción, comience desde la página CategoryListMaster.aspx; a continuación, haga clic en un vínculo de la lista con viñetas de categorías. Si lo hace, pasará a ProductsForCategoryDetails.aspx, pasando CategoryID por la cadena de consulta. El ObjectDataSource ProductsInCategoryDataSource en ProductsForCategoryDetails.aspx obtendrá solo esos productos para la categoría especificada y los mostrará en DataList, que representa dos productos por fila. En la ilustración 6 se muestra una captura de pantalla de ProductsForCategoryDetails.aspx al ver las Bebidas.

The Beverages are Displayed, Two per Row

Ilustración 6: Se muestran las bebidas, dos por fila (haga clic para ver la imagen de tamaño completo)

Paso 4: Mostrar información de categoría en ProductsForCategoryDetails.aspx

Cuando un usuario hace clic en una categoría de CategoryListMaster.aspx, se le lleva a ProductsForCategoryDetails.aspx y se muestran los productos que pertenecen a la categoría seleccionada. Sin embargo, en ProductsForCategoryDetails.aspx no hay indicaciones visuales sobre qué categoría se seleccionó. Un usuario que quería hacer clic en Bebidas, pero que accidentalmente hizo clic en Condimentos, no tiene forma de darse cuenta de su error una vez que llega a ProductsForCategoryDetails.aspx. Para aliviar este posible problema, podemos mostrar información sobre la categoría seleccionada (su nombre y descripción) en la parte superior de la página ProductsForCategoryDetails.aspx.

Para ello, agregue un FormView encima del control Repeater en ProductsForCategoryDetails.aspx. A continuación, agregue un nuevo ObjectDataSource a la página desde la etiqueta inteligente de FormView denominada CategoryDataSource y configúrelo para usar el método GetCategoryByCategoryID(categoryID) de la clase CategoriesBLL.

Access Information about the Category through the CategoriesBLL Class's GetCategoryByCategoryID(categoryID) Method

Ilustración 7: Obtener acceso a la información sobre la categoría a través del método GetCategoryByCategoryID(categoryID) de la clase CategoriesBLL (haga clic para ver la imagen de tamaño completo)

Al igual que con el ObjectDataSource ProductsInCategoryDataSource agregado en el paso 3, el asistente para configurar orígenes de datos de CategoryDataSource nos pide un origen para el parámetro de entrada del método GetCategoryByCategoryID(categoryID). Use la misma configuración exacta que antes, estableciendo el origen del parámetro en QueryString y el valor QueryStringField en CategoryID (consulte la Ilustración 5).

Después de completar el asistente, Visual Studio crea automáticamente un objeto ItemTemplate, EditItemTemplate y InsertItemTemplate para FormView. Puesto que proporcionamos una interfaz de solo lectura, no dude en quitar EditItemTemplate y InsertItemTemplate. Además, no dude en personalizar el ItemTemplate de FormView. Después de quitar las plantillas superfluas y personalizar ItemTemplate, el marcado declarativo de FormView y ObjectDataSource debe ser similar al siguiente:

<asp:FormView ID="FormView1" runat="server" DataKeyNames="CategoryID"
    DataSourceID="CategoryDataSource" EnableViewState="False" Width="100%">
    <ItemTemplate>
        <h3>
            <asp:Label ID="CategoryNameLabel" runat="server"
                Text='<%# Bind("CategoryName") %>' />
        </h3>
        <p>
            <asp:Label ID="DescriptionLabel" runat="server"
                Text='<%# Bind("Description") %>' />
        </p>
    </ItemTemplate>
</asp:FormView>
 
<asp:ObjectDataSource ID="CategoryDataSource" runat="server"
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetCategoryByCategoryID" TypeName="CategoriesBLL">
    <SelectParameters>
        <asp:QueryStringParameter Name="categoryID" Type="Int32"
            QueryStringField="CategoryID" />
    </SelectParameters>
</asp:ObjectDataSource>

En la Ilustración 8 se muestra una captura de pantalla al ver esta página a través de un explorador.

Nota:

Además de FormView, también he agregado un control HyperLink encima de FormView que volverá a llevar al usuario a la lista de categorías (CategoryListMaster.aspx). No dude en colocar este vínculo en otro lugar u omitirlo por completo.

Category Information is Now Displayed at the Top of the Page

Ilustración 8: La información de categoría se muestra ahora en la parte superior de la página (haga clic para ver la imagen de tamaño completo)

Ilustración 5: Mostrar un mensaje si ningún producto pertenece a la categoría seleccionada

En la página CategoryListMaster.aspx se enumeran todas las categorías del sistema, independientemente de si hay productos asociados. Si un usuario hace clic en una categoría sin productos asociados, la lista de datos de ProductsForCategoryDetails.aspx no se representará, ya que su origen de datos no tendrá ningún elemento. Como hemos visto en los tutoriales anteriores, GridView proporciona una propiedad EmptyDataText que se puede usar para especificar un mensaje de texto para mostrar si no hay registros en su origen de datos. Desafortunadamente, ni DataList ni Repeater tienen dicha propiedad.

Para mostrar un mensaje que informa al usuario de que no hay productos coincidentes para la categoría seleccionada, es necesario agregar un control Label a la página cuya propiedad Text está asignada al mensaje para mostrar en caso de que no haya productos coincidentes. A continuación, es necesario establecer mediante programación su propiedad Visible en función de si DataList contiene o no elementos.

Para ello, empiece agregando una etiqueta debajo de DataList. Establezca su propiedad ID en NoProductsMessage y su propiedad Text en "No hay productos para la categoría seleccionada...". A continuación, es necesario establecer mediante programación la propiedad Visible de Label en función de si los datos se enlazaron a ProductsInCategory de DataList. Esta asignación debe realizarse después de que los datos se hayan enlazado a DataList. Para GridView, DetailsView y FormView, podríamos crear un controlador de eventos para el evento del control DataBound, que se desencadena después de que se haya completado el enlace de datos. Sin embargo, ni DataList ni Repeater tienen un evento DataBound disponible.

En este ejemplo concreto se puede asignar la propiedad Visible de Label en el controlador de eventos Page_Load, ya que los datos se asignarán a DataList antes del evento Load de la página. Sin embargo, este enfoque no funcionaría en el caso general, ya que los datos de ObjectDataSource podrían estar enlazados a DataList más adelante en el ciclo de vida de la página. Por ejemplo, si los datos mostrados se basan en el valor de otro control, como cuando se muestra un informe maestro o detallado mediante DropDownList para contener los registros "maestros", es posible que los datos no vuelvan al control web de datos hasta la fase PreRender del ciclo de vida de la página.

Una solución que funcionará para todos los casos es asignar la propiedad Visible a False en el controlador de eventos ItemDataBound (o ItemCreated) de DataList al enlazar un tipo de elemento de Item o AlternatingItem. En tal caso sabemos que hay al menos un elemento de datos en el origen de datos y, por lo tanto, puede ocultar la etiqueta NoProductsMessage. Además de este controlador de eventos, también necesitamos un controlador de eventos para el evento DataBinding de DataList, donde inicializamos la propiedad Visible de Label en True. Dado que el evento DataBinding se desencadena antes de los eventos ItemDataBound, la propiedad Visible de Label se establecerá inicialmente en True; si hay elementos de datos, sin embargo, se establecerá en False. El código siguiente implementa esta lógica:

protected void ProductsInCategory_DataBinding(object sender, EventArgs e)
{
    // Show the Label
    NoProductsMessage.Visible = true;
}
 
protected void ProductsInCategory_ItemDataBound(object sender, DataListItemEventArgs e)
{
    // If we have a data item, hide the Label
    if (e.Item.ItemType == ListItemType.Item ||
        e.Item.ItemType == ListItemType.AlternatingItem)
        NoProductsMessage.Visible = false;
}

Todas las categorías de la base de datos Northwind están asociadas a uno o varios productos. Para probar esta característica, he ajustado manualmente la base de datos Northwind para este tutorial, reasignando todos los productos asociados a la categoría Producto (CategoryID = 7) a la categoría Marisco (CategoryID = 8). Esto se puede lograr desde el Explorador de servidores eligiendo Nueva consulta y usando la instrucción siguiente UPDATE:

UPDATE Products SET
    CategoryID = 8
WHERE CategoryID = 7

Después de actualizar la base de datos en consecuencia, vuelva a la página CategoryListMaster.aspx y haga clic en el vínculo Generar. Puesto que ya no hay ningún producto que pertenezca a la categoría Producto, debería ver el mensaje "No hay productos para la categoría seleccionada...", como se muestra en la Ilustración 9.

A Message is Displayed if there are No Products Belonging to the Selected Category

Ilustración 9: Se muestra un mensaje si no hay productos pertenecientes a la categoría seleccionada (haga clic para ver la imagen de tamaño completo)

Resumen

Aunque los informes maestros y detalles pueden mostrar los registros maestros y detallados en una sola página, en muchos sitios web se separan en dos páginas web. En este tutorial hemos visto cómo implementar este informe maestro/detalle teniendo las categorías enumeradas en una lista con viñetas mediante un repetidor en la página web "maestra" y los productos asociados enumerados en la página "detalles". Cada elemento de lista de la página web maestra contenía un vínculo a la página de detalles que se pasó a lo largo del valor CategoryID de la fila.

En la página de detalles que recupera esos productos para el proveedor especificado se realizó a través del método GetProductsByCategoryID(categoryID) de la clase ProductsBLL. El valor del parámetro categoryID se especificó mediante declaración mediante el valor CategoryID de querystring como origen del parámetro. También hemos visto cómo mostrar los detalles de categoría en la página de detalles mediante FormView y cómo mostrar un mensaje si no había productos que pertenezcan a la categoría seleccionada.

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, ha estado 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 vía mitchell@4GuysFromRolla.com. o a través de su blog, que se puede encontrar en http://ScottOnWriting.NET.

Gracias especial a…

Esta serie de tutoriales fue revisada por muchos revisores de gran ayuda. Los revisores principales de este tutorial fueron Zack Jones y Liz Shulok. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.