Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
por Scott Mitchell
Neste tutorial, exploraremos como usar um Repetidor aninhado dentro de outro Repetidor. Os exemplos ilustrarão como preencher o Repetidor Interno de forma declarativa e programática.
Introdução
Além de HTML estático e sintaxe de associaçã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 da sintaxe declarativa de vinculação de dados ou podem ser acessados programaticamente nos manipuladores de eventos do lado do servidor apropriados.
Ao inserir controles em um modelo, a aparência e a experiência do usuário podem ser personalizadas e aprimoradas. 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 os tutoriais da 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) em seus templates. O desafio com essa interface é associar os dados apropriados ao controle da Web de dados internos. Há algumas abordagens diferentes disponíveis, que vão desde opções declarativas usando o ObjectDataSource até as programáticas.
Neste tutorial, exploraremos 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. Cada repetidor interno de item de categoria exibirá informações para cada produto que pertence a essa categoria (consulte a Figura 1) em uma lista com marcadores. Nossos exemplos ilustrarão como preencher o Repetidor interno de forma declarativa e programática.
Figura 1: Cada categoria, juntamente com seus produtos, são listadas (clique para exibir 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 primeiro o controle web de dados mais externo, sem se 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 abrindo a NestedControls.aspx
página na DataListRepeaterBasics
pasta e adicione um controle Repeater à página, definindo sua ID
propriedade como CategoryList
. Na tag inteligente do Repetidor, escolha criar um novo ObjectDataSource chamado CategoriesDataSource
.
Figura 2: Nomeie o Novo ObjectDataSource CategoriesDataSource
(Clique para exibir a imagem em tamanho real)
Configure o ObjectDataSource para que ele extraia seus dados da classe CategoriesBLL
usando o método GetCategories
.
Figura 3: Configurar o ObjectDataSource para usar o CategoriesBLL
método da GetCategories
classe (clique para exibir a imagem em tamanho real)
Para especificar o conteúdo do modelo do Repetidor, precisamos ir para o modo de exibição Origem e inserir manualmente a sintaxe declarativa. Adicione um ItemTemplate
que exibe 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 Repetidor e ObjectDataSource semelhante ao 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 exibido por meio de um navegador.
Figura 4: O nome e a descrição de cada categoria estão listados, separados por uma regra horizontal (clique para exibir a imagem em tamanho real)
Etapa 2: Adicionar o repetidor de produto aninhado
Com a listagem de categoria concluída, nossa próxima tarefa é adicionar um Repetidor aos CategoryList
s ItemTemplate
que exibe informações sobre esses produtos que pertencem à categoria apropriada. Há várias maneiras de recuperar os dados desse Repetidor interno, duas das quais exploraremos em breve. Por enquanto, vamos apenas criar o repetidor de produtos dentro do CategoryList
Repeater's ItemTemplate
. Especificamente, vamos fazer com que o repetidor de produto exiba cada produto em uma lista com marcadores com cada item de lista, incluindo o nome e o preço do produto.
Para criar este Repetidor, precisamos inserir manualmente a sintaxe declarativa do Repetidor interno e seus modelos nos CategoryList
ItemTemplate
. Adicione a seguinte marcação dentro do CategoryList
Repetidor 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: Associar os produtos Category-Specific ao repetidor ProductsByCategoryList
Se você visitar a página por meio de um navegador neste ponto, sua tela terá a mesma aparência da Figura 4, pois ainda não associamos nenhum dado ao Repetidor. Há algumas maneiras de pegar os registros de produto apropriados e associá-los ao Repetidor, algumas mais eficientes do que outras. O principal desafio aqui é recuperar os produtos apropriados para a categoria especificada.
Os dados a serem associados ao controle repetidor interno podem ser acessados declarativamente, por meio de um ObjectDataSource no CategoryList
Repeater s ItemTemplate
, ou programaticamente, na página code-behind da página ASP.NET. Da mesma forma, esses dados podem ser associados ao Repetidor interno declarativamente por meio da DataSourceID
propriedade do Repetidor interno ou por meio da sintaxe de vinculação de dados declarativa ou programaticamente, fazendo referência ao Repetidor interno no CategoryList
manipulador de eventos do ItemDataBound
Repetidor, 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 opção mais natural para acessar dados para este exemplo é manter-se 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 aos CategoryList
Repeater ItemTemplate
e configurá-lo para acessar seus dados a partir do método desta classe.
Infelizmente, o Repetidor não permite que seus modelos sejam editados por meio do modo design, portanto, precisamos adicionar a sintaxe declarativa para esse controle ObjectDataSource manualmente. A sintaxe a seguir mostra o CategoryList
Repeater ItemTemplate
após 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 propriedade ProductsByCategoryList
do Repeater DataSourceID
para o ID
do 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 especificamos esse valor? Idealmente, poderíamos apenas definir a DefaultValue
propriedade do <asp:Parameter>
elemento usando a sintaxe de associação de dados, da seguinte maneira:
<asp:Parameter Name="CategoryID" Type="Int32"
DefaultValue='<%# Eval("CategoryID")' />
Infelizmente, a sintaxe de associação de dados só é válida em controles que têm um DataBinding
evento. A Parameter
classe não tem tal evento e, portanto, a sintaxe acima é ilegal e resultará em um erro de runtime.
Para definir esse valor, precisamos criar um manipulador de eventos para o CategoryList
evento do ItemDataBound
Repetidor. Lembre-se de que o ItemDataBound
evento é acionado uma vez para cada item associado ao Repetidor. Portanto, sempre que esse evento é acionado para o Repetidor externo, podemos atribuir o valor atual CategoryID
ao ProductsByCategoryDataSource
parâmetro ObjectDataSource CategoryID
.
Crie um manipulador de eventos para o evento CategoryList
do ItemDataBound
Repetidor com o seguinte código:
protected void CategoryList_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.AlternatingItem ||
e.Item.ItemType == ListItemType.Item)
{
// Reference the CategoriesRow object being bound to this RepeaterItem
Northwind.CategoriesRow category =
(Northwind.CategoriesRow)((System.Data.DataRowView)e.Item.DataItem).Row;
// Reference the ProductsByCategoryDataSource ObjectDataSource
ObjectDataSource ProductsByCategoryDataSource =
(ObjectDataSource)e.Item.FindControl("ProductsByCategoryDataSource");
// Set the CategoryID Parameter value
ProductsByCategoryDataSource.SelectParameters["CategoryID"].DefaultValue =
category.CategoryID.ToString();
}
}
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 associada à atual RepeaterItem
. Por fim, referenciamos o ObjectDataSource no ItemTemplate
e atribuímos o valor do seu parâmetro CategoryID
ao CategoryID
do RepeaterItem
atual.
Com esse manipulador de eventos, o ProductsByCategoryList
Repetidor em cada RepeaterItem
é associado a esses 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 dessa categoria (clique para exibir a imagem em tamanho real)
Acessando os dados de produtos por categoria de forma programática
Em vez de usar um ObjectDataSource para recuperar os produtos para a categoria atual, poderíamos criar um método em nossa classe de code-behind da página ASP.NET (ou na pasta App_Code
ou em um projeto separado de Biblioteca de Classes) que retorne o conjunto apropriado de produtos ao receber um CategoryID
. Imagine que tínhamos um método desse tipo em nossa classe code-behind de página ASP.NET e que ele foi nomeado GetProductsInCategory(categoryID)
. Com esse método em vigor, poderíamos associar os produtos da categoria atual ao Repetidor interno usando a seguinte sintaxe declarativa:
<asp:Repeater runat="server" ID="ProductsByCategoryList" EnableViewState="False"
DataSource='<%# GetProductsInCategory((int)(Eval("CategoryID"))) %>'>
...
</asp:Repeater>
A propriedade Repeater DataSource
s usa a sintaxe de associação de dados para indicar que seus dados são provenientes do GetProductsInCategory(categoryID)
método. Como Eval("CategoryID")
retorna um valor de tipo Object
, convertemos o objeto em um Integer
antes de passá-lo para o GetProductsInCategory(categoryID)
método. Observe que o CategoryID
acessado aqui por meio da sintaxe de associação de dados é o CategoryID
no repetidor externo (CategoryList
), aquele que está vinculado aos registros na tabela Categories
. Portanto, sabemos que CategoryID
não é um valor de banco de dados NULL
, e é por isso que podemos aplicar cegamente o método Eval
sem verificar se estamos lidando com um DBNull
.
Com essa abordagem, precisamos criar o método GetProductsInCategory(categoryID)
e fazer com que ele recupere o conjunto apropriado de produtos fornecido por categoryID
. Podemos fazer isso simplesmente retornando o ProductsDataTable
retornado pelo método ProductsBLL
da classe GetProductsByCategoryID(categoryID)
. Vamos criar o método GetProductsInCategory(categoryID)
na classe de code-behind para a nossa página NestedControls.aspx
. Faça isso usando o seguinte código:
protected Northwind.ProductsDataTable GetProductsInCategory(int categoryID)
{
// Create an instance of the ProductsBLL class
ProductsBLL productAPI = new ProductsBLL();
// Return the products in the category
return productAPI.GetProductsByCategoryID(categoryID);
}
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 na marcação declarativa da página ASP.NET.
Depois de fazer essas alterações para usar essa nova técnica, reserve um momento para exibir a página por meio de um navegador. A saída deve ser idêntica à saída ao usar o ObjectDataSource e a abordagem com manipulador de eventos ItemDataBound
(consulte a Figura 5 para ver uma captura de tela).
Observação
Pode parecer trabalho desnecessário 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 associação de dados no Repetidor interno, como: DataSource='<%# ProductsBLL.GetProductsByCategoryID((int)(Eval("CategoryID"))) %>'
? Embora essa sintaxe não funcione com nossa implementação atual da ProductsBLL
classe (já que o GetProductsByCategoryID(categoryID)
método é um método de 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.
Embora essas modificações eliminem a necessidade do GetProductsInCategory(categoryID)
método na classe code-behind da página ASP.NET, o método de classe code-behind 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 examinamos capturam esses produtos para a categoria atual fazendo uma chamada para o método da classe ProductsBLL
(a primeira abordagem fez isso por meio de um ObjectDataSource, a segunda por meio do método GetProductsByCategoryID(categoryID)
na classe code-behind). Cada vez que esse método é invocado, a Camada lógica de negócios chama a Camada de Acesso a Dados, que consulta o banco de dados com uma instrução SQL que retorna linhas da Products
tabela cujo CategoryID
campo corresponde ao parâmetro de entrada fornecido.
Dadas as N categorias no sistema, essa abordagem resulta em N + 1 chamadas para o banco de dados, uma consulta de banco de dados para obter todas as categorias e, em seguida, N chamadas para obter os produtos específicos para cada categoria. No entanto, podemos 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. Depois que tivermos todos os produtos, podemos filtrar esses produtos para que apenas os produtos correspondentes à corrente CategoryID
sejam associados ao repetidor interno dessa categoria.
Para fornecer essa funcionalidade, só precisamos fazer uma pequena modificação no método GetProductsInCategory(categoryID)
em nossa classe code-behind da página ASP.NET. Em vez de retornar cegamente os resultados do ProductsBLL
método da classe GetProductsByCategoryID(categoryID)
, podemos primeiro acessar todos os produtos (se eles ainda não tiverem sido acessados) e, em seguida, retornar apenas a exibição filtrada dos produtos com base no CategoryID
fornecido.
private Northwind.ProductsDataTable allProducts = null;
protected Northwind.ProductsDataTable GetProductsInCategory(int categoryID)
{
// First, see if we've yet to have accessed all of the product information
if (allProducts == null)
{
ProductsBLL productAPI = new ProductsBLL();
allProducts = productAPI.GetProducts();
}
// Return the filtered view
allProducts.DefaultView.RowFilter = "CategoryID = " + categoryID;
return allProducts;
}
Observe a adição da variável no nível da 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 allProducts
objeto foi criado e preenchido, o método filtra os resultados do DataTable de modo que somente as linhas cujas CategoryID
correspondências são especificadas CategoryID
estejam acessíveis. Essa abordagem reduz o número de vezes que o banco de dados é acessado de N + 1 para dois.
Esse aprimoramento não apresenta nenhuma alteração na marcação renderizada da página nem traz 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 argumentar que a redução do número de acessos de banco de dados melhoraria com certeza o desempenho. No entanto, esse pode não ser o caso. Se você tiver um grande número de produtos cujo CategoryID
é NULL
, por exemplo, a chamada do método GetProducts
retornará alguns produtos que nunca são exibidos. Além disso, retornar 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ê implementou a paginação.
Como sempre, quando se trata de analisar o desempenho de duas técnicas, a única medida surefire é executar testes controlados personalizados para os cenários de casos comuns do aplicativo.
Resumo
Neste tutorial, vimos como aninhar um controle 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 criação de uma interface do usuário aninhada é acessar e associar os dados corretos ao controle 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 nos controles da ItemTemplate
Web de dados externos associados ao controle da Web de dados internos por meio de sua DataSourceID
propriedade. 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 associado à propriedade de controle interno de dados da Web DataSource
através da sintaxe de ligação de dados.
Embora a interface do usuário aninhada examinada neste tutorial tenha usado um Repetidor aninhado em um Repetidor, essas técnicas podem ser estendidas para os outros controles da Web de dados. Você pode aninhar um Repetidor em um GridView ou um GridView em uma Lista de Dados e assim por diante.
Divirta-se programando!
Sobre o autor
Scott Mitchell, autor de sete livros asp/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Microsoft Web desde 1998. Scott trabalha como consultor independente, treinador e escritor. Seu último livro é Sams Teach Yourself ASP.NET 2.0 em 24 Horas. Ele pode ser alcançado em mitchell@4GuysFromRolla.com.
Agradecimentos Especiais a
Esta série de tutoriais foi revisada por muitos revisores úteis. Os principais revisores deste tutorial foram Zack Jones e Liz Shulok. Interessado em revisar meus próximos artigos do MSDN? Se assim for, deixe-me uma linha em mitchell@4GuysFromRolla.com.