Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
por Scott Mitchell
Neste tutorial, vamos explorar como usar um repetidor aninhado dentro de outro repetidor. Os exemplos ilustrarão como preencher o Repetidor interno declarativa e programaticamente.
Introdução
Além de HTML estático e sintaxe de vinculação de dados, os modelos também podem incluir controles da Web e controles de usuário. Esses controles da Web podem ter suas propriedades atribuídas por meio de sintaxe declarativa, de vinculação de dados, ou podem ser acessados programaticamente nos manipuladores de eventos apropriados do lado do servidor.
Ao incorporar controles em um modelo, a aparência e a experiência do usuário podem ser personalizadas e melhoradas. Por exemplo, no tutorial Usando TemplateFields no Controle GridView , vimos como personalizar a exibição do GridView adicionando um controle Calendar em um TemplateField para mostrar a data de contratação de um funcionário; nos tutoriais Adicionando controles de validação às interfaces de edição e inserção e Personalizando a interface de modificação de dados , vimos como personalizar as interfaces de edição e inserção adicionando controles de validação, TextBoxes, DropDownLists e outros controles da Web.
Os modelos também podem conter outros controles da Web de dados. Ou seja, podemos ter um DataList que contém outro DataList (ou Repeater ou GridView ou DetailsView, e assim por diante) dentro dos seus templates. O desafio com essa interface é vincular os dados apropriados ao controle interno da Web de dados. Existem algumas abordagens diferentes disponíveis, que vão desde opções declarativas usando o ObjectDataSource até opções programáticas.
Neste tutorial, vamos explorar como usar um repetidor aninhado dentro de outro repetidor. O repetidor externo conterá um item para cada categoria no banco de dados, exibindo o nome e a descrição da categoria. O Repetidor interno de cada item de categoria exibirá informações para cada produto pertencente a essa categoria (consulte a Figura 1) em uma lista com marcadores. Nossos exemplos ilustrarão como preencher o Repetidor interno declarativa e programaticamente.
Figura 1: Cada categoria, juntamente com seus produtos, estão listados (Clique para visualizar a imagem em tamanho real)
Etapa 1: Criando a listagem de categorias
Ao criar uma página que usa controles da Web de dados aninhados, acho útil projetar, criar e testar o controle da Web de dados mais externos primeiro, sem nem mesmo me preocupar com o controle aninhado interno. Portanto, vamos começar percorrendo as etapas necessárias para adicionar um repetidor à página que lista o nome e a descrição de cada categoria.
Comece por abrir a página NestedControls.aspx
na pasta DataListRepeaterBasics
e adicione um controlo Repeater à página, definindo a sua propriedade ID
como CategoryList
. Na etiqueta inteligente do Repeater, escolha criar um novo objeto DataSource chamado CategoriesDataSource
.
Nomeie o novo CategoriesDataSource do ObjectDataSource
Figura 2: Nomeie o novo ObjectDataSource CategoriesDataSource
(Clique para visualizar a imagem em tamanho real)
Configure o ObjectDataSource para que ele extraia seus dados do CategoriesBLL
método s GetCategories
da classe.
Figura 3: Configurar o ObjectDataSource para usar o método Class s CategoriesBLL
(GetCategories
imagem em tamanho real)
Para especificar o conteúdo do modelo do Repetidor, precisamos ir para a vista de Código-fonte, e inserir manualmente a sintaxe declarativa. Adicione um ItemTemplate
que exiba o nome da categoria em um <h4>
elemento e a descrição da categoria em um elemento de parágrafo (<p>
). Além disso, vamos separar cada categoria com uma regra horizontal (<hr>
). Depois de fazer essas alterações, sua página deve conter sintaxe declarativa para o Repeater e ObjectDataSource semelhante à seguinte:
<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>
A Figura 4 mostra nosso progresso quando visualizado através de um navegador.
Figura 4: O nome e a descrição de cada categoria estão listados, separados por uma regra horizontal (Clique para visualizar a imagem em tamanho real)
Etapa 2: Adicionar o "Nested Product Repeater"
Com a listagem de categorias concluída, nossa próxima tarefa é adicionar um repetidor ao CategoryList
s ItemTemplate
que exibe informações sobre os produtos pertencentes à categoria apropriada. Existem várias maneiras de recuperar os dados para este repetidor interno, duas das quais exploraremos em breve. Por enquanto, vamos apenas criar os produtos Repetidor dentro do elemento CategoryList
Repetidor ItemTemplate
. Especificamente, deixe que o Repetidor do produto exiba cada produto em uma lista com marcadores com cada item da lista, incluindo o nome e o preço do produto.
Para criar este Repetidor, precisamos inserir manualmente a sintaxe declarativa interna do Repetidor e os modelos no CategoryList
s ItemTemplate
. Adicione a seguinte marcação dentro dos CategoryList
repetidores :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>
Etapa 3: Vinculando os produtos Category-Specific ao repetidor ProductsByCategoryList
Se você visitar a página através de um navegador neste momento, sua tela terá a mesma aparência da Figura 4, porque ainda não vinculamos nenhum dado ao Repetidor. Existem algumas maneiras de pegar os registros de produto apropriados e vinculá-los ao Repetidor, algumas mais eficientes do que outras. O principal desafio aqui é recuperar os produtos adequados para a categoria especificada.
Os dados a serem vinculados ao controle Repeater interno podem ser acessados declarativamente, por meio de um ObjectDataSource no CategoryList
Repeater s ItemTemplate
, ou programaticamente, a partir da página code-behind da página ASP.NET. Da mesma forma, esses dados podem ser vinculados ao Repeater interno declarativamente - através da propriedade interna do Repeater ou através da sintaxe declarativa de DataSourceID
vinculação de dados ou programaticamente fazendo referência ao Repeater interno no CategoryList
manipulador de eventos do ItemDataBound
Repeater, definindo programaticamente sua DataSource
propriedade e chamando seu DataBind()
método. Vamos explorar cada uma dessas abordagens.
Acessando os dados declarativamente com um controle ObjectDataSource e oItemDataBound
manipulador de eventos
Como usamos o ObjectDataSource extensivamente ao longo desta série de tutoriais, a escolha mais natural para acessar dados para este exemplo é ficar com o ObjectDataSource. A ProductsBLL
classe tem um GetProductsByCategoryID(categoryID)
método que retorna informações sobre os produtos que pertencem ao especificado categoryID
. Portanto, podemos adicionar um ObjectDataSource ao CategoryList
Repeater s ItemTemplate
e configurá-lo para acessar seus dados a partir do método s dessa classe.
Infelizmente, o Repeater não permite que seus modelos sejam editados através da visualização Design, portanto, precisamos adicionar a sintaxe declarativa para esse controle ObjectDataSource manualmente. A sintaxe a seguir mostra o CategoryList
Repeater s ItemTemplate
depois de adicionar este novo 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>
Ao usar a abordagem ObjectDataSource, precisamos definir a ProductsByCategoryList
propriedade Repeater s DataSourceID
como a ID
de ObjectDataSource (ProductsByCategoryDataSource
). Além disso, observe que nosso ObjectDataSource tem um <asp:Parameter>
elemento que especifica o categoryID
valor que será passado para o GetProductsByCategoryID(categoryID)
método. Mas como especificar esse valor? Idealmente, poderíamos simplesmente definir a propriedade DefaultValue
do elemento <asp:Parameter>
usando a sintaxe de ligação de dados, assim:
<asp:Parameter Name="CategoryID" Type="Int32"
DefaultValue='<%# Eval("CategoryID")' />
Infelizmente, a sintaxe de vinculação de dados só é válida em controles que têm um DataBinding
evento. A Parameter
classe não possui tal evento e, portanto, a sintaxe acima é ilegal e resultará em um erro de tempo de execução.
Para definir esse valor, precisamos criar um manipulador de eventos para o CategoryList
evento Repeater ItemDataBound
. Lembre-se de que o ItemDataBound
evento é acionado uma vez para cada item vinculado ao Repetidor. Portanto, cada vez que esse evento é acionado para o Repeater externo, podemos atribuir o valor atual CategoryID
ao ProductsByCategoryDataSource
parâmetro s CategoryID
ObjectDataSource.
Crie um manipulador de eventos para o CategoryList
evento Repeater s ItemDataBound
com o seguinte código:
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
Esse manipulador de eventos começa garantindo que estamos lidando com um item de dados em vez do cabeçalho, rodapé ou item separador. Em seguida, fazemos referência à instância real CategoriesRow
que acabou de ser vinculada ao atual RepeaterItem
. Finalmente, fazemos referência ao ObjectDataSource no ItemTemplate
e atribuímos seu CategoryID
valor de parâmetro ao CategoryID
do atual RepeaterItem
.
Com este manipulador de eventos, o ProductsByCategoryList
Repetidor em cada RepeaterItem
está vinculado aos produtos na categoria RepeaterItem
s. A Figura 5 mostra uma captura de tela da saída resultante.
Figura 5: O repetidor externo lista cada categoria; o Interno Lista os Produtos para essa Categoria (Clique para ver a imagem em tamanho real)
Acessando os produtos por categoria de dados programaticamente
Em vez de usar um ObjectDataSource para recuperar os produtos para a categoria atual, poderíamos criar um método na nossa classe code-behind da página de ASP.NET (ou na pasta App_Code
ou num projeto de Biblioteca de Classes separado) que retorna o conjunto apropriado de produtos quando passado um CategoryID
. Imagine que tínhamos esse método em nossa classe code-behind da página ASP.NET e que ele foi nomeado GetProductsInCategory(categoryID)
. Com este método em vigor, poderíamos vincular os produtos para a categoria atual ao repetidor interno usando a seguinte sintaxe declarativa:
<asp:Repeater runat="server" ID="ProductsByCategoryList" EnableViewState="False"
DataSource='<%# GetProductsInCategory(CType(Eval("CategoryID"), Integer)) %>'>
...
</asp:Repeater>
A propriedade Repeater s DataSource
usa a sintaxe de vinculação de dados para indicar que seus dados vêm do GetProductsInCategory(categoryID)
método. Como Eval("CategoryID")
retorna um valor do tipo Object
, convertemos o objeto em um Integer
antes de passá-lo para o GetProductsInCategory(categoryID)
método. Note que o CategoryID
acessado aqui através da sintaxe de vinculação de dados é o CategoryID
no Repetidor (CategoryList
) externo, aquele que está vinculado aos registros na tabela Categories
. Portanto, sabemos que CategoryID
não pode ser um valor de banco de dados, e é por isso que podemos lançar o método NULL
cegamente sem verificar se estamos a lidar com um Eval
.
Com esta abordagem, precisamos criar o método GetProductsInCategory(categoryID)
e fazê-lo recuperar o conjunto apropriado de produtos fornecidos categoryID
. Podemos fazer isso simplesmente devolvendo o ProductsDataTable
retornado pelo ProductsBLL
método da GetProductsByCategoryID(categoryID)
classe. Vamos criar o GetProductsInCategory(categoryID)
método na classe code-behind para nossa NestedControls.aspx
página. Faça isso usando o seguinte código:
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
Esse método simplesmente cria uma instância do ProductsBLL
método e retorna os resultados do GetProductsByCategoryID(categoryID)
método. Observe que o método deve ser marcado Public
ou Protected
; se o método estiver marcado Private
, ele não estará acessível a partir da marcação declarativa da página ASP.NET.
Depois de fazer essas alterações para usar essa nova técnica, reserve um momento para visualizar a página através de um navegador. A saída deve ser idêntica à saída ao usar a abordagem ObjectDataSource e ItemDataBound
manipulador de eventos (consulte a Figura 5 para ver uma captura de tela).
Observação
Pode parecer um trabalho repetitivo criar o método GetProductsInCategory(categoryID)
na classe code-behind da página ASP.NET. Afinal, esse método simplesmente cria uma instância da ProductsBLL
classe e retorna os resultados de seu GetProductsByCategoryID(categoryID)
método. Por que não chamar esse método diretamente da sintaxe de vinculação de dados no Repetidor interno, como: DataSource='<%# ProductsBLL.GetProductsByCategoryID(CType(Eval("CategoryID"), Integer)) %>'
? Embora essa sintaxe não funcione com nossa implementação atual da classe (já que o ProductsBLL
método é um método de GetProductsByCategoryID(categoryID)
instância), você pode modificar ProductsBLL
para incluir um método estático GetProductsByCategoryID(categoryID)
ou fazer com que a classe inclua um método estático Instance()
para retornar uma nova instância da ProductsBLL
classe.
Enquanto tais modificações eliminariam a necessidade do GetProductsInCategory(categoryID)
método na classe code-behind da página ASP.NET, o método code-behind class nos dá mais flexibilidade para trabalhar com os dados recuperados, como veremos em breve.
Recuperando todas as informações do produto de uma só vez
As duas técnicas anteriores que examinámos obtêm esses produtos para a categoria atual fazendo uma chamada para o método ProductsBLL
na classe GetProductsByCategoryID(categoryID)
(a primeira abordagem fez isso por meio de um ObjectDataSource, a segunda através do método GetProductsInCategory(categoryID)
na classe code-behind). Cada vez que este método é invocado, a Camada de Lógica de Negócios chama para baixo a Camada de Acesso a Dados, que consulta o banco de dados com uma instrução SQL que retorna da tabela Products
as linhas cujo campo CategoryID
corresponde ao parâmetro de entrada fornecido.
Dadas as N categorias no sistema, esta abordagem resulta em uma consulta ao banco de dados para obter todas as categorias, seguida por N + 1 chamadas ao banco de dados para obter os produtos específicos para cada categoria. Podemos, no entanto, recuperar todos os dados necessários em apenas duas chamadas de banco de dados, uma chamada para obter todas as categorias e outra para obter todos os produtos. Uma vez que temos todos os produtos, podemos filtrar esses produtos para que apenas os produtos correspondentes à corrente CategoryID
estejam vinculados ao repetidor interno dessa categoria.
Para fornecer essa funcionalidade, só precisamos fazer uma pequena modificação no GetProductsInCategory(categoryID)
método em nossa classe code-behind da página ASP.NET. Em vez de retornar cegamente os resultados do método da classe ProductsBLL
e GetProductsByCategoryID(categoryID)
, podemos primeiro acessar todos os produtos (se eles ainda não tiverem sido acessados) e, em seguida, retornar apenas a vista filtrada dos produtos com base no fornecido.
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
Observe a adição da variável de nível de página, allProducts
. Isso contém informações sobre todos os produtos e é preenchido na primeira vez que o GetProductsInCategory(categoryID)
método é invocado. Depois de garantir que o objeto allProducts
foi criado e preenchido, o método filtra os resultados da DataTable, de modo que apenas as linhas cujos CategoryID
correspondam ao CategoryID
especificado sejam acessíveis. Essa abordagem reduz o número de vezes que o banco de dados é acessado de N + 1 para duas.
Esse aprimoramento não introduz nenhuma alteração na marcação renderizada da página, nem traz de volta menos registros do que a outra abordagem. Ele simplesmente reduz o número de chamadas para o banco de dados.
Observação
Pode-se intuitivamente raciocinar que reduzir o número de acessos ao banco de dados certamente melhoraria o desempenho. No entanto, pode não ser esse o caso. Se você tiver um grande número de produtos cujo CategoryID
é NULL
, por exemplo, a chamada para o GetProducts
método retorna um número de produtos que nunca são exibidos. Além disso, devolver todos os produtos pode ser um desperdício se você estiver mostrando apenas um subconjunto das categorias, o que pode ser o caso se você tiver implementado a paginação.
Como sempre, quando se trata de analisar o desempenho de duas técnicas, a única medida infalível é executar testes controlados adaptados para os cenários de casos comuns do seu aplicativo.
Resumo
Neste tutorial, vimos como aninhar um controle da Web de dados dentro de outro, examinando especificamente como fazer com que um Repetidor externo exiba um item para cada categoria com um Repetidor interno listando os produtos para cada categoria em uma lista com marcadores. O principal desafio na construção de uma interface de utilizador aninhada reside em aceder e ligar os dados corretos ao controlo Web de dados internos. Há uma variedade de técnicas disponíveis, duas das quais examinamos neste tutorial. A primeira abordagem examinada usou um ObjectDataSource no controlo web de dados externo ItemTemplate
que estava vinculado ao controlo web de dados interno por meio da sua propriedade DataSourceID
. A segunda técnica acessou os dados por meio de um método na classe code-behind da página ASP.NET. Esse método pode então ser vinculado à propriedade de controle da Web de dados internos por meio da sintaxe de vinculação de DataSource
dados.
Embora a interface do usuário aninhada examinada neste tutorial usasse um Repetidor aninhado dentro de um Repetidor, essas técnicas podem ser estendidas para os outros controles da Web de dados. Pode-se aninhar um Repeater dentro de um GridView, ou um GridView dentro de um DataList, e assim sucessivamente.
Feliz Programação!
Sobre o Autor
Scott Mitchell, autor de sete livros sobre ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias Web da Microsoft desde 1998. Scott trabalha como consultor, formador e escritor independente. Seu último livro é Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Ele pode ser contatado em mitchell@4GuysFromRolla.com.
Um agradecimento especial a
Esta série de tutoriais foi revisada por muitos revisores úteis. Os principais revisores para este tutorial foram Zack Jones e Liz Shulok. Interessado em rever meus próximos artigos do MSDN? Se for o caso, envie-me uma mensagem para mitchell@4GuysFromRolla.com.