Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
En este tutorial exploraremos cómo usar un repetidor anidado dentro de otro repetidor. En los ejemplos se muestra cómo rellenar el repetidor interno tanto mediante declaración como mediante programación.
Introducción
Además de la sintaxis html estática y de enlace de datos, las plantillas también pueden incluir controles web y controles de usuario. Estos controles web pueden tener sus propiedades asignadas a través de la sintaxis declarativa o de enlace de datos, o pueden ser accedidas mediante programación en los controladores de eventos adecuados del servidor.
Al insertar controles dentro de una plantilla, la apariencia y la experiencia del usuario se pueden personalizar y mejorar. Por ejemplo, en el tutorial Uso de TemplateFields en el control GridView , vimos cómo personalizar la presentación de GridView agregando un control Calendar en un TemplateField para mostrar la fecha de contratación de un empleado; en los tutoriales Agregar controles de validación a las interfaces de edición e inserción y personalización de la interfaz de modificación de datos , vimos cómo personalizar las interfaces de edición e inserción agregando controles de validación, Cuadros de texto, DropDownLists y otros controles web.
Las plantillas también pueden contener otros controles web de datos. Es decir, podemos tener un objeto DataList que contenga otro DataList (o Repeater, GridView o DetailsView, etc.) dentro de sus plantillas. El desafío con esta interfaz es enlazar los datos adecuados al control web de datos interno. Hay algunos enfoques diferentes disponibles, que van desde las opciones declarativas mediante ObjectDataSource hasta las mediante programación.
En este tutorial exploraremos cómo usar un repetidor anidado dentro de otro repetidor. El Repetidor externo contendrá un elemento para cada categoría de la base de datos, mostrando el nombre y la descripción de la categoría. Cada elemento de categoría del repetidor interno mostrará información para cada producto que pertenezca a esa categoría (vea la figura 1) en una lista con viñetas. En nuestros ejemplos se muestra cómo rellenar el repetidor interno tanto mediante declaración como mediante programación.
Figura 1: Cada categoría, junto con sus productos, se muestran (haga clic para ver la imagen de tamaño completo)
Paso 1: Crear la lista de categorías
Al compilar una página que usa controles web de datos anidados, me resulta útil diseñar, crear y probar primero el control web de datos más externo, sin preocuparse incluso por el control anidado interno. Por lo tanto, comencemos por los pasos necesarios para agregar un Repetidor a la página que muestra el nombre y la descripción de cada categoría.
Para empezar, abra la página NestedControls.aspx
en la carpeta DataListRepeaterBasics
y agregue un control Repeater, estableciendo su propiedad ID
a CategoryList
. En la etiqueta inteligente Del repetidor, elija crear un objeto ObjectDataSource denominado CategoriesDataSource
.
Figura 2: Asignar un nombre a New ObjectDataSource CategoriesDataSource
(Haga clic para ver la imagen de tamaño completo)
Configure ObjectDataSource para que extraiga sus datos del método de la CategoriesBLL
clase s GetCategories
.
Figura 3: Configurar objectDataSource para usar el método de clase s CategoriesBLL
(GetCategories
imagen de tamaño completo)
Para especificar el contenido de la plantilla del repetidor, es necesario ir a la vista Origen y escribir manualmente la sintaxis declarativa. Agregue un ItemTemplate
que muestre el nombre de la categoría en un <h4>
elemento y la descripción de la categoría en un elemento de párrafo (<p>
). Además, vamos a separar cada categoría con una regla horizontal (<hr>
). Después de realizar estos cambios, la página debe contener sintaxis declarativa para repeater y ObjectDataSource similar a lo siguiente:
<asp:Repeater ID="CategoryList" DataSourceID="CategoriesDataSource"
EnableViewState="False" runat="server">
<ItemTemplate>
<h4><%# Eval("CategoryName") %></h4>
<p><%# Eval("Description") %></p>
</ItemTemplate>
<SeparatorTemplate>
<hr />
</SeparatorTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="CategoriesDataSource" runat="server"
OldValuesParameterFormatString="original_{0}"
SelectMethod="GetCategories" TypeName="CategoriesBLL">
</asp:ObjectDataSource>
En la figura 4 se muestra el progreso cuando se ve a través de un explorador.
Figura 4: Cada nombre y descripción de cada categoría se muestra, separado por una regla horizontal (haga clic para ver la imagen de tamaño completo)
Paso 2: Agregar el repetidor de producto anidado
Una vez completada la lista de categorías, nuestra siguiente tarea es agregar un repetidor a los CategoryList
que ItemTemplate
muestra información sobre esos productos que pertenecen a la categoría adecuada. Hay varias maneras de recuperar los datos de este repetidor interno, dos de los cuales exploraremos en breve. Por ahora, vamos a crear los productos Repeater dentro de repeater CategoryList
s ItemTemplate
. En concreto, vamos a hacer que el repetidor de productos muestre cada producto en una lista con viñetas, donde cada elemento de la lista incluya el nombre y el precio del producto.
Para crear este repetidor, es necesario escribir manualmente la sintaxis declarativa del repetidor interno y las plantillas en s CategoryList
ItemTemplate
. Agregue el siguiente etiquetado dentro del CategoryList
Repeater s ItemTemplate
:
<asp:Repeater ID="ProductsByCategoryList" EnableViewState="False"
runat="server">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li><strong><%# Eval("ProductName") %></strong>
(<%# Eval("UnitPrice", "{0:C}") %>)</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
Paso 3: Enlazar los productos de Category-Specific al repetidor ProductsByCategoryList
Si visita la página a través de un explorador en este momento, la pantalla tendrá el mismo aspecto que en la figura 4, ya que todavía tenemos que enlazar los datos al repetidor. Hay algunas maneras de obtener los registros de producto adecuados y enlazarlos al Repetidor, algunos más eficientes que otros. El principal desafío aquí es recuperar los productos adecuados para la categoría especificada.
Se puede acceder a los datos que se van a enlazar al control Repeater interno de manera declarativa, a través de un ObjectDataSource en el CategoryList
Repeater ItemTemplate
, o mediante programación, desde el código subyacente de la página ASP.NET. Del mismo modo, estos datos se pueden enlazar al repetidor interno mediante declaración, a través de la propiedad del repetidor DataSourceID
interno o a través de la sintaxis declarativa de enlace de datos o mediante programación haciendo referencia al repetidor interno en el CategoryList
controlador de eventos del ItemDataBound
repetidor, estableciendo mediante programación su propiedad y llamando a su DataSource
DataBind()
método. Vamos a explorar cada uno de estos enfoques.
Acceso a los datos mediante declaración con un control ObjectDataSource y elItemDataBound
controlador de eventos
Puesto que hemos usado ObjectDataSource ampliamente en esta serie de tutoriales, la opción más natural para acceder a los datos de este ejemplo es seguir con ObjectDataSource. La ProductsBLL
clase tiene un GetProductsByCategoryID(categoryID)
método que devuelve información sobre los productos que pertenecen al especificado categoryID
. Por lo tanto, podemos agregar un ObjectDataSource a repeater CategoryList
s ItemTemplate
y configurarlo para acceder a sus datos desde este método de clase s.
Desafortunadamente, repeater no permite que sus plantillas se editen a través de la vista Diseño, por lo que es necesario agregar la sintaxis declarativa para este control ObjectDataSource manualmente. La sintaxis siguiente muestra repeater CategoryList
s ItemTemplate
después de agregar este nuevo ObjectDataSource (ProductsByCategoryDataSource
):
<h4><%# Eval("CategoryName") %></h4>
<p><%# Eval("Description") %></p>
<asp:Repeater ID="ProductsByCategoryList" EnableViewState="False"
DataSourceID="ProductsByCategoryDataSource" runat="server">
<HeaderTemplate>
<ul>
</HeaderTemplate>
<ItemTemplate>
<li><strong><%# Eval("ProductName") %></strong> -
sold as <%# Eval("QuantityPerUnit") %> at
<%# Eval("UnitPrice", "{0:C}") %></li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>
<asp:ObjectDataSource ID="ProductsByCategoryDataSource" runat="server"
SelectMethod="GetProductsByCategoryID" TypeName="ProductsBLL">
<SelectParameters>
<asp:Parameter Name="CategoryID" Type="Int32" />
</SelectParameters>
</asp:ObjectDataSource>
Cuando se usa el enfoque ObjectDataSource, es necesario establecer la ProductsByCategoryList
propiedad Repeater al DataSourceID
de ObjectDataSource (ID
). Observe también que objectDataSource tiene un <asp:Parameter>
elemento que especifica el categoryID
valor que se pasará al GetProductsByCategoryID(categoryID)
método . ¿Pero cómo se especifica este valor? Idealmente, podríamos simplemente establecer la propiedad DefaultValue
del elemento <asp:Parameter>
mediante la sintaxis de enlace de datos, de la siguiente manera:
<asp:Parameter Name="CategoryID" Type="Int32"
DefaultValue='<%# Eval("CategoryID")' />
Desafortunadamente, la sintaxis de enlace de datos solo es válida en los controles que tienen un DataBinding
evento. La Parameter
clase carece de este evento y, por tanto, la sintaxis anterior no es válida y producirá un error en tiempo de ejecución.
Para establecer este valor, es necesario crear un controlador de eventos para el CategoryList
evento Repeater.ItemDataBound
Recuerde que el ItemDataBound
evento se activa una vez para cada elemento enlazado al repetidor. Por lo tanto, cada vez que se activa este evento para el repetidor externo, podemos asignar el valor actual CategoryID
al ProductsByCategoryDataSource
parámetro ObjectDataSource s CategoryID
.
Cree un controlador de eventos para el CategoryList
evento Repeater s ItemDataBound
con el código siguiente:
Protected Sub CategoryList_ItemDataBound(sender As Object, e As RepeaterItemEventArgs) _
Handles CategoryList.ItemDataBound
If e.Item.ItemType = ListItemType.AlternatingItem _
OrElse e.Item.ItemType = ListItemType.Item Then
' Reference the CategoriesRow object being bound to this RepeaterItem
Dim category As Northwind.CategoriesRow = _
CType(CType(e.Item.DataItem, System.Data.DataRowView).Row, _
Northwind.CategoriesRow)
' Reference the ProductsByCategoryDataSource ObjectDataSource
Dim ProductsByCategoryDataSource As ObjectDataSource = _
CType(e.Item.FindControl("ProductsByCategoryDataSource"), _
ObjectDataSource)
' Set the CategoryID Parameter value
ProductsByCategoryDataSource.SelectParameters("CategoryID").DefaultValue = _
category.CategoryID.ToString()
End If
End Sub
Este controlador de eventos comienza asegurándonos de que estamos tratando con un elemento de datos en lugar del elemento de encabezado, pie de página o separador. A continuación, hacemos referencia a la instancia real CategoriesRow
que acaba de estar enlazada al objeto actual RepeaterItem
. Por último, hacemos referencia a ObjectDataSource en ItemTemplate
y asignamos su CategoryID
valor de parámetro al CategoryID
del objeto actual RepeaterItem
.
Con este controlador de eventos, el ProductsByCategoryList
repetidor en cada RepeaterItem
se enlaza a esos productos en la categoría RepeaterItem
. En la figura 5 se muestra una captura de pantalla de la salida resultante.
Figura 5: El repetidor externo enumera cada categoría; en El interior se enumeran los productos de esa categoría (haga clic para ver la imagen de tamaño completo).
Acceso a los productos por datos de categoría mediante programación
En lugar de usar objectDataSource para recuperar los productos de la categoría actual, podríamos crear un método en la clase de código subyacente de la página ASP.NET (o en la App_Code
carpeta o en un proyecto de biblioteca de clases independiente) que devuelva el conjunto adecuado de productos cuando se pasa en un CategoryID
. Imagine que teníamos este método en nuestra clase de código subyacente de la página ASP.NET y que se llamaba GetProductsInCategory(categoryID)
. Con este método en su lugar, podríamos enlazar los productos de la categoría actual al repetidor interno mediante la siguiente sintaxis declarativa:
<asp:Repeater runat="server" ID="ProductsByCategoryList" EnableViewState="False"
DataSource='<%# GetProductsInCategory(CType(Eval("CategoryID"), Integer)) %>'>
...
</asp:Repeater>
La propiedad del Repeater usa DataSource
la sintaxis de enlace de datos para indicar que sus datos proceden del método GetProductsInCategory(categoryID)
. Dado que Eval("CategoryID")
devuelve un valor de tipo Object
, convertimos el objeto en un Integer
antes de pasarlo al método GetProductsInCategory(categoryID)
. Tenga en cuenta que el CategoryID
al que se accede aquí mediante la sintaxis de enlace de datos es el CategoryID
en el Repetidor externo (CategoryList
), el que está vinculado a los registros de la tabla Categories
. Por lo tanto, sabemos que CategoryID
no puede ser un valor de base de datos NULL
, por lo que podemos convertir ciegamente el Eval
método sin comprobar si estamos tratando con un DBNull
.
Con este enfoque, es necesario crear el método GetProductsInCategory(categoryID)
para que recupere el conjunto adecuado de productos según el categoryID
proporcionado. Podemos hacerlo simplemente devolviendo el ProductsDataTable
devuelto por el método de la ProductsBLL
clase s GetProductsByCategoryID(categoryID)
. Vamos a crear el GetProductsInCategory(categoryID)
método en la clase de código subyacente de nuestra NestedControls.aspx
página. Hágalo con el código siguiente:
Protected Function GetProductsInCategory(ByVal categoryID As Integer) _
As Northwind.ProductsDataTable
' Create an instance of the ProductsBLL class
Dim productAPI As ProductsBLL = New ProductsBLL()
' Return the products in the category
Return productAPI.GetProductsByCategoryID(categoryID)
End Function
Este método simplemente crea una instancia del ProductsBLL
método y devuelve los resultados del GetProductsByCategoryID(categoryID)
método . Tenga en cuenta que el método debe marcarse Public
o Protected
; si el método está marcado como Private
, no será accesible desde el marcado declarativo de la página ASP.NET.
Después de realizar estos cambios para usar esta nueva técnica, dedique un momento a ver la página a través de un explorador. La salida debe ser idéntica a la salida al usar el enfoque ObjectDataSource y ItemDataBound
el controlador de eventos (consulte la Figura 5 para ver una captura de pantalla).
Nota:
Puede parecer una tarea trivial crear el método GetProductsInCategory(categoryID)
en la clase de code-behind de la página ASP.NET. Después de todo, este método simplemente crea una instancia de la ProductsBLL
clase y devuelve los resultados de su GetProductsByCategoryID(categoryID)
método. ¿Por qué no llamar a este método directamente desde la sintaxis de enlace de datos en el repetidor interno, como: DataSource='<%# ProductsBLL.GetProductsByCategoryID(CType(Eval("CategoryID"), Integer)) %>'
? Aunque esta sintaxis no funcionará con la implementación actual de la ProductsBLL
clase (ya que el GetProductsByCategoryID(categoryID)
método es un método de instancia), podría modificar ProductsBLL
para incluir un método estático GetProductsByCategoryID(categoryID)
o hacer que la clase incluya un método estático Instance()
para devolver una nueva instancia de la ProductsBLL
clase.
Aunque estas modificaciones eliminarían la necesidad del GetProductsInCategory(categoryID)
método en la clase de código subyacente de la página ASP.NET, el método de clase de código subyacente nos ofrece más flexibilidad para trabajar con los datos recuperados, como veremos en breve.
Recuperar toda la información del producto a la vez
Las dos técnicas previas que hemos examinado obtienen esos productos para la categoría actual mediante una llamada al método de la clase ProductsBLL
GetProductsByCategoryID(categoryID)
(el primer enfoque lo hizo a través de ObjectDataSource, el segundo a través del método GetProductsInCategory(categoryID)
en la clase de código subyacente). Cada vez que se invoca este método, la capa lógica de negocios llama a la capa de acceso a datos, que consulta la base de datos con una instrucción SQL que devuelve filas de la Products
tabla cuyo CategoryID
campo coincide con el parámetro de entrada proporcionado.
Dadas N categorías en el sistema, este enfoque resulta en N + 1 llamadas a la base de datos: una consulta para obtener todas las categorías y luego N llamadas para obtener los productos específicos de cada categoría. Sin embargo, podemos recuperar todos los datos necesarios en solo dos llamadas de base de datos para obtener todas las categorías y otra para obtener todos los productos. Una vez que tengamos todos los productos, podemos filtrar esos productos para que solo los productos que coincidan con el actual CategoryID
estén vinculados al Repeater interno de esa categoría.
Para proporcionar esta funcionalidad, solo es necesario realizar una ligera modificación del GetProductsInCategory(categoryID)
método en nuestra clase de código subyacente de la página ASP.NET. En lugar de devolver ciegamente los resultados del método de la clase ProductsBLL
GetProductsByCategoryID(categoryID)
, podemos acceder primero a todos los productos (si no se han accedido previamente) y, a continuación, devolver solo la visualización filtrada de los productos en función del parámetro pasado .
Private allProducts As Northwind.ProductsDataTable = Nothing
Protected Function GetProductsInCategory(ByVal categoryID As Integer) _
As Northwind.ProductsDataTable
' First, see if we've yet to have accessed all of the product information
If allProducts Is Nothing Then
Dim productAPI As ProductsBLL = New ProductsBLL()
allProducts = productAPI.GetProducts()
End If
' Return the filtered view
allProducts.DefaultView.RowFilter = "CategoryID = " & categoryID
Return allProducts
End Function
Tenga en cuenta la adición de la variable de nivel de página, allProducts
. Esto contiene información sobre todos los productos y se rellena la primera vez que se invoca el GetProductsInCategory(categoryID)
método. Después de asegurarse de que el allProducts
objeto se ha creado y rellenado, el método filtra los resultados de DataTable de forma que solo se puedan acceder a las filas cuyas CategoryID
coincidencias coincidan con el especificado CategoryID
. Este enfoque reduce el número de veces que se accede a la base de datos desde N + 1 hasta dos.
Esta mejora no introduce ningún cambio en el marcado representado de la página, ni devuelve menos registros que el otro enfoque. Simplemente reduce el número de llamadas a la base de datos.
Nota:
Uno podría razonar de forma intuitiva que reducir el número de accesos a la base de datos mejoraría seguramente el rendimiento. Sin embargo, esto podría no ser el caso. Si tiene un gran número de productos cuyos CategoryID
es NULL
, por ejemplo, la llamada al GetProducts
método devuelve una serie de productos que nunca se muestran. Además, devolver todos los productos puede ser desperdiciado si solo muestra un subconjunto de las categorías, lo que podría ser el caso si ha implementado la paginación.
Como siempre, cuando se trata de analizar el rendimiento de dos técnicas, la única medida surefire es ejecutar pruebas controladas adaptadas para escenarios comunes de caso de la aplicación.
Resumen
En este tutorial hemos visto cómo anidar un control de datos web dentro de otro, examinando específicamente cómo hacer que un Repeater externo muestre un elemento para cada categoría con un Repeater interno que lista los productos de cada categoría en una lista con viñetas. El principal desafío en la creación de una interfaz de usuario anidada radica en el acceso y enlace de los datos correctos al control web de datos interno. Hay una variedad de técnicas disponibles, dos de las cuales examinamos en este tutorial. El primer enfoque examinado usó un ObjectDataSource en el control web de datos externo ItemTemplate
que estaba enlazado al control web de datos interno a través de su DataSourceID
propiedad. La segunda técnica accedió a los datos a través de un método en la clase de código subyacente de la página ASP.NET. A continuación, este método se puede enlazar a la propiedad del DataSource
control web de datos interno a través de la sintaxis de enlace de datos.
Aunque la interfaz de usuario anidada que se examina en este tutorial usó un repetidor anidado dentro de un repetidor, estas técnicas se pueden extender a los demás controles web de datos. Puede anidar un Repetidor dentro de un GridView, o un GridView dentro de una DataList, y así sucesivamente.
¡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 en 24 horas. Se puede contactar con él en mitchell@4GuysFromRolla.com.
Agradecimientos especiales 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 interesaría revisar mis próximos artículos de MSDN? Si es así, mándame un mensaje a mitchell@4GuysFromRolla.com.