Partilhar via


Controles de Dados Aninhados na Web (VB)

por Scott Mitchell

Descarregar PDF

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.

Cada categoria, juntamente com seus produtos, estão listados

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.

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

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.

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 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 oItemDataBoundmanipulador 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 RepeaterItems. A Figura 5 mostra uma captura de tela da saída resultante.

O repetidor externo lista cada categoria; o Inner One lista os produtos para essa categoria

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.