Compartilhar via


Controles da Web de dados aninhadas (C#)

por Scott Mitchell

Baixar PDF

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.

Cada categoria, juntamente com seus produtos, são listadas

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.

Nomeie o Novo ObjetoDataSource 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 .

Configurar o ObjectDataSource para usar o método GetCategories da classe CategoriesBLL

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.

O nome e a descrição de cada categoria são listados, separados por uma regra horizontal

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 ItemTemplateou 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 oItemDataBoundmanipulador 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.

O repetidor externo Listas cada categoria; o interno Listas os produtos dessa categoria

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 Privatecomo , 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 CategoryIDpassado.

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.