Controles da Web de dados aninhadas (C#)
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 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 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 GridView Control , 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 uma DataList que contém outro DataList (ou Repeater ou GridView ou DetailsView e assim por diante) dentro de seus modelos. 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 ObjectDataSource até 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 pertencente a essa categoria (consulte 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 da Web de dados mais externos, sem sequer 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 abrindo a NestedControls.aspx
página na DataListRepeaterBasics
pasta e adicione um controle Repeater à página, definindo sua ID
propriedade como CategoryList
. Na marca inteligente do Repetidor, escolha criar um novo ObjectDataSource chamado CategoriesDataSource
.
Figura 2: Nomeie o Novo ObjetoDataSource CategoriesDataSource
(Clique para exibir a imagem em tamanho real)
Configure o ObjectDataSource para que ele extraia seus dados do CategoriesBLL
método da classe s 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 Repeater 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 sã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 categorias 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 os produtos Repeater dentro do CategoryList
Repeater s ItemTemplate
. Especificamente, vamos fazer com que o repositório de produtos exiba cada produto em uma lista com marcadores com cada item de lista, incluindo o nome e o preço do produto.
Para criar esse Repetidor, precisamos inserir manualmente a sintaxe declarativa do Repetidor interno e os modelos nos CategoryList
s ItemTemplate
. Adicione a seguinte marcação no 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>
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 desafio main aqui é recuperar os produtos apropriados para a categoria especificada.
Os dados a serem associados ao controle repeater 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 propriedade inner Repeater s DataSourceID
ou por meio da sintaxe declarativa de vinculação de dados ou programaticamente fazendo referência ao Repetidor interno no CategoryList
manipulador de eventos repeater ItemDataBound
, 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 é 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 aos CategoryList
Repeater s ItemTemplate
e configurá-lo para acessar seus dados desse método de classe.
Infelizmente, o Repetidor não permite que seus modelos sejam editados por meio da exibição Design, portanto, precisamos adicionar a sintaxe declarativa para esse controle ObjectDataSource manualmente. A sintaxe a seguir mostra o CategoryList
Repeater s depois de ItemTemplate
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 para DataSourceID
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? O ideal é que possamos apenas definir a DefaultValue
propriedade do <asp:Parameter>
elemento usando a sintaxe de vinculação de dados, da seguinte maneira:
<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 tem esse 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 Repeater s ItemDataBound
. 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 CategoryList
evento Repeater ItemDataBound
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, referenciamos a instância real CategoriesRow
que acabou de ser associada ao atual RepeaterItem
. Por fim, referenciamos ObjectDataSource no ItemTemplate
e atribuimos seu CategoryID
valor de parâmetro ao CategoryID
do atual RepeaterItem
.
Com esse manipulador de eventos, o ProductsByCategoryList
Repetidor em cada RepeaterItem
um está associado a esses produtos na RepeaterItem
categoria s. A Figura 5 mostra uma captura de tela da saída resultante.
Figura 5: o repetidor externo Listas cada categoria; o interno Listas os produtos dessa categoria (clique para exibir a imagem em tamanho real)
Acessando os produtos por dados de categoria programaticamente
Em vez de usar um ObjectDataSource para recuperar os produtos para a categoria atual, poderíamos criar um método em nossa classe code-behind da página ASP.NET (ou na App_Code
pasta ou em um projeto separado da Biblioteca de Classes) que retorna o conjunto apropriado de produtos quando passado em um CategoryID
. Imagine que tínhamos um método desse tipo em nossa classe code-behind da página ASP.NET e que ele se chamava 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 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 . Observe que o CategoryID
acessado aqui por meio da sintaxe de vinculação de dados é o CategoryID
no Repetidor externo (CategoryList
), aquele associado aos registros na Categories
tabela. Portanto, sabemos que CategoryID
não pode ser um valor de banco de dados NULL
, razão pela qual podemos converter cegamente o Eval
método sem verificar se estamos lidando com um DBNull
.
Com essa abordagem, precisamos criar o GetProductsInCategory(categoryID)
método e fazer com que ele recupere o conjunto apropriado de produtos, considerando o fornecido categoryID
. Podemos fazer isso simplesmente retornando o ProductsDataTable
retornado pelo ProductsBLL
método da classe.GetProductsByCategoryID(categoryID)
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 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
como , ele não será 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 exibir a página por meio de um navegador. A saída deve ser idêntica à saída ao usar o ObjectDataSource e ItemDataBound
a abordagem do manipulador de eventos (consulte a Figura 5 para ver uma captura de tela).
Observação
Pode parecer trabalho ocupado criar o GetProductsInCategory(categoryID)
método 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((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 pervious que examinamos capturam esses produtos para a categoria atual fazendo uma chamada para o ProductsBLL
método da GetProductsByCategoryID(categoryID)
classe (a primeira abordagem fez isso por meio de um ObjectDataSource, o segundo por meio do GetProductsInCategory(categoryID)
método na classe code-behind). Sempre que esse método é invocado, a Camada de 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.
Considerando N categorias no sistema, essa abordagem nets N + 1 chama 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 ao atual CategoryID
sejam associados 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 ProductsBLL
método da GetProductsByCategoryID(categoryID)
classe, podemos primeiro acessar todos os produtos (se eles ainda não tiverem sido acessados) e retornar apenas a exibição filtrada dos produtos com base no CategoryID
passado.
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 tenha sido criado e preenchido, o método filtra os resultados do DataTable de modo que somente as linhas que CategoryID
correspondem ao especificado 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 introduz 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 ao 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 para o GetProducts
método retornará vários produtos que nunca são exibidos. Além disso, retornar todos os produtos poderá 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 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 desafio main na criação de uma interface do usuário aninhada está no acesso e na associação dos dados corretos ao controle interno da Web de dados. Há uma variedade de técnicas disponíveis, duas das quais examinamos neste tutorial. A primeira abordagem examinada usou um ObjectDataSource nos controles da Web de dados externos ItemTemplate
que estava associado ao controle interno da Web de dados 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 ser associado à propriedade de controle da Web de DataSource
dados internos por meio da sintaxe de vinculação de dados.
Embora a interface do usuário aninhada examinada neste tutorial tenha usado um Repetidor aninhado em um Repeater, 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 DataList e assim por diante.
Programação feliz!
Sobre o autor
Scott Mitchell, autor de sete livros do 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 contatado em mitchell@4GuysFromRolla.com. ou através de seu blog, que pode ser encontrado em http://ScottOnWriting.NET.
Agradecimentos Especiais
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? Nesse caso, solte-me uma linha em mitchell@4GuysFromRolla.com.