Condividi tramite


Controlli Web dei dati annidati (VB)

di Scott Mitchell

Scarica il PDF

In questa esercitazione verrà illustrato come usare un ripetitore annidato all'interno di un altro ripetitore. Gli esempi illustrano come popolare il ripetitore interno sia dichiarativo che a livello di codice.

Introduzione

Oltre alla sintassi HTML statica e databinding, i modelli possono includere anche controlli Web e Controlli utente. Questi controlli Web possono avere le proprietà assegnate tramite sintassi dichiarativa, databinding o possono essere accessibili a livello di codice nei gestori eventi lato server appropriati.

Incorporando controlli all'interno di un modello, è possibile personalizzare e migliorare l'esperienza utente e l'aspetto. Ad esempio, nell'esercitazione Using TemplateFields nell'esercitazione GridView Control è stato illustrato come personalizzare la visualizzazione di GridView aggiungendo un controllo Calendar in un ModelloField per visualizzare la data di assunzione di un dipendente; nell'esercitazione Aggiunta di controlli di convalida alle interfacce di modifica e inserimento e personalizzazione dell'interfaccia di modifica dei dati , è stato illustrato come personalizzare le interfacce di modifica e inserimento aggiungendo controlli di convalida, Caselle di testo, DropDownList e altri controlli Web.

I modelli possono anche contenere altri controlli Web dati. Vale a dire, è possibile disporre di un oggetto DataList contenente un altro oggetto DataList (o Repeater o GridView o DetailsView e così via) all'interno dei relativi modelli. La sfida con tale interfaccia è associare i dati appropriati al controllo Web dei dati interni. Sono disponibili alcuni approcci diversi, che variano da opzioni dichiarative usando ObjectDataSource a quelle a livello di codice.

In questa esercitazione verrà illustrato come usare un ripetitore annidato all'interno di un altro ripetitore. Il ripetitore esterno conterrà un elemento per ogni categoria nel database, visualizzando il nome e la descrizione della categoria. Ogni elemento di categoria interno Ripetitore visualizzerà le informazioni per ogni prodotto appartenente a tale categoria (vedere la figura 1) in un elenco puntato. Gli esempi illustrano come popolare il ripetitore interno sia dichiarativo che a livello di codice.

Ogni categoria, insieme ai suoi prodotti, è elencata

Figura 1: Ogni categoria, insieme ai relativi prodotti, è elencata (Fare clic per visualizzare l'immagine full-size)

Passaggio 1: Creazione dell'elenco di categorie

Quando si crea una pagina che usa controlli Web dati annidati, è utile progettare, creare e testare prima il controllo Web dati più esterno, senza preoccuparsi nemmeno del controllo annidato interno. Per iniziare, quindi, seguire i passaggi necessari per aggiungere un ripetitore alla pagina che elenca il nome e la descrizione per ogni categoria.

Iniziare aprendo la NestedControls.aspx pagina nella DataListRepeaterBasics cartella e aggiungendo un controllo Repeater alla pagina, impostandone la ID proprietà su CategoryList. Dallo smart tag di Repeater scegliere di creare un nuovo oggetto ObjectDataSource denominato CategoriesDataSource.

Assegnare un nome alle nuove categorie ObjectDataSourceDataSource

Figura 2: Assegnare un nome a New ObjectDataSource CategoriesDataSource (Fare clic per visualizzare l'immagine full-size)

Configurare ObjectDataSource in modo da eseguire il pull dei dati dal CategoriesBLL metodo della GetCategories classe.

Configurare ObjectDataSource per usare il metodo GetCategories della classe CategoriesBLL

Figura 3: Configurare ObjectDataSource per usare il metodo della GetCategories classe (fare clic per visualizzare l'immagineCategoriesBLL full-size)

Per specificare il contenuto del modello del ripetitore, è necessario passare alla visualizzazione Origine e immettere manualmente la sintassi dichiarativa. Aggiungere un oggetto ItemTemplate che visualizza il nome della categoria in un <h4> elemento e la descrizione della categoria in un elemento paragrafo (<p>). Inoltre, consente di separare ogni categoria con una regola orizzontale (<hr>). Dopo aver apportato queste modifiche, la pagina deve contenere una sintassi dichiarativa per Repeater e ObjectDataSource simile alla seguente:

<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>

La figura 4 mostra lo stato di avanzamento quando viene visualizzato tramite un browser.

Ogni nome e descrizione di ogni categoria è elencato, separato da una regola orizzontale

Figura 4: Ogni nome e descrizione categoria è elencato, separato da una regola orizzontale (fare clic per visualizzare l'immagine full-size)

Passaggio 2: Aggiunta del ripetitore del prodotto annidato

Con il completamento dell'elenco di categorie, l'attività successiva consiste nell'aggiungere un ripetitore all'oggetto CategoryList che ItemTemplate visualizza informazioni su tali prodotti appartenenti alla categoria appropriata. Esistono diversi modi in cui è possibile recuperare i dati per questo ripetitore interno, due dei quali si esaminerà brevemente. Per il momento, consente di creare solo i prodotti Ripetitore all'interno CategoryList del ripetitore s ItemTemplate. In particolare, è possibile visualizzare il ripetitore del prodotto in un elenco puntato con ogni elemento elenco, incluso il nome e il prezzo del prodotto.

Per creare questo ripetitore, è necessario immettere manualmente la sintassi dichiarativa del ripetitore interno e i modelli in CategoryList s ItemTemplate. Aggiungere il markup seguente all'interno del CategoryList ripetitore 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>

Passaggio 3: Associazione dei prodotti Category-Specific al ripetitore ProductsByCategoryList

Se si visita la pagina tramite un browser a questo punto, la schermata avrà lo stesso aspetto della figura 4 perché non è ancora stato eseguito il binding di dati al ripetitore. Esistono alcuni modi per acquisire i record di prodotto appropriati e associarli al ripetitore, alcuni più efficienti di altri. La sfida principale è tornare ai prodotti appropriati per la categoria specificata.

I dati da associare al controllo ripetitore interno possono essere accessibili in modo dichiarativo tramite ObjectDataSource nella CategoryList pagina Del ripetitore o ItemTemplatea livello di codice dalla pagina code-behind della pagina ASP.NET. Analogamente, questi dati possono essere associati al ripetitore interno dichiarativo, tramite la proprietà del ripetitore interno DataSourceID o tramite la sintassi di associazione dati dichiarativa o a livello di codice facendo riferimento al ripetitore interno nel CategoryList gestore eventi ItemDataBound ripetitore, impostandone a livello di codice la proprietà e chiamando il DataBind()DataSource relativo metodo. Esplorare ognuno di questi approcci.

Accesso ai dati dichiarativamente con un controllo ObjectDataSource e ilItemDataBoundgestore eventi

Poiché in questa serie di esercitazioni è stata usata l'oggetto ObjectDataSource, la scelta più naturale per l'accesso ai dati per questo esempio consiste nell'usare ObjectDataSource. La ProductsBLL classe ha un GetProductsByCategoryID(categoryID) metodo che restituisce informazioni sui prodotti appartenenti all'oggetto specificato categoryID. È pertanto possibile aggiungere un oggetto ObjectDataSource all'oggetto CategoryList Repeater e ItemTemplate configurarlo per accedere ai dati da questo metodo di classe.

Sfortunatamente, il ripetitore non consente la modifica dei relativi modelli tramite la visualizzazione Progettazione, quindi è necessario aggiungere la sintassi dichiarativa per questo controllo ObjectDataSource in base alla mano. La sintassi seguente mostra l'oggetto ItemTemplate Repeater dopo l'aggiunta di CategoryList questo nuovo OggettoDataSource (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>

Quando si usa l'approccio ObjectDataSource, è necessario impostare la ProductsByCategoryList proprietà Repeater sull'oggetto DataSourceIDID ObjectDataSource (ProductsByCategoryDataSource). Si noti inoltre che ObjectDataSource ha un <asp:Parameter> elemento che specifica il categoryID valore che verrà passato al GetProductsByCategoryID(categoryID) metodo. Ma come si specifica questo valore? Idealmente, sarebbe possibile impostare semplicemente la proprietà dell'elemento <asp:Parameter> usando la DefaultValue sintassi databinding, come segue:

<asp:Parameter Name="CategoryID" Type="Int32"
    DefaultValue='<%# Eval("CategoryID")' />

Sfortunatamente, la sintassi databinding è valida solo nei controlli che hanno un DataBinding evento. La Parameter classe manca di un evento di questo tipo e pertanto la sintassi precedente è illegale e genererà un errore di runtime.

Per impostare questo valore, è necessario creare un gestore eventi per l'evento CategoryList Repeater.ItemDataBound Si ricordi che l'evento ItemDataBound viene generato una volta per ogni elemento associato al ripetitore. Pertanto, ogni volta che questo evento viene generato per il ripetitore esterno, è possibile assegnare il valore corrente CategoryID al ProductsByCategoryDataSource parametro ObjectDataSource.CategoryID

Creare un gestore eventi per l'evento CategoryListItemDataBound Repeater con il codice seguente:

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

Questo gestore eventi inizia assicurandosi di gestire un elemento di dati anziché l'intestazione, il piè di pagina o l'elemento separatore. A questo punto, si fa riferimento all'istanza effettiva CategoriesRow appena associata all'oggetto corrente RepeaterItem. Infine, si fa riferimento a ObjectDataSource nell'oggetto ItemTemplate e si assegna il CategoryID relativo valore di parametro all'oggetto CategoryID corrente RepeaterItem.

Con questo gestore eventi, il ProductsByCategoryList ripetitore in ogni RepeaterItem oggetto è associato a tali prodotti nella RepeaterItem categoria s. La figura 5 mostra una schermata dell'output risultante.

Il ripetitore esterno Elenchi ogni categoria; quello interno Elenchi i prodotti per tale categoria

Figura 5: Il ripetitore esterno Elenchi ogni categoria; l'interno uno Elenchi i prodotti per tale categoria (fare clic per visualizzare l'immagine full-size)

Accesso ai prodotti per categoria dati a livello di codice

Anziché usare ObjectDataSource per recuperare i prodotti per la App_Code categoria corrente, è possibile creare un metodo nella classe code-behind della pagina ASP.NET (o nella cartella o in un progetto libreria di classi separato) che restituisce il set appropriato di prodotti quando passato in un CategoryIDoggetto . Si supponga di avere un metodo simile nella classe code-behind della pagina ASP.NET e che sia stato denominato GetProductsInCategory(categoryID). Con questo metodo è possibile associare i prodotti per la categoria corrente al ripetitore interno usando la sintassi dichiarativa seguente:

<asp:Repeater runat="server" ID="ProductsByCategoryList" EnableViewState="False"
      DataSource='<%# GetProductsInCategory(CType(Eval("CategoryID"), Integer)) %>'>
  ...
</asp:Repeater>

La proprietà Repeater DataSource usa la sintassi databinding per indicare che i dati provengono dal GetProductsInCategory(categoryID) metodo . Poiché Eval("CategoryID") restituisce un valore di tipo Object, viene eseguito il cast dell'oggetto in un Integer oggetto prima di passarlo al GetProductsInCategory(categoryID) metodo. Si noti che l'accesso qui tramite la CategoryID sintassi databinding è quello CategoryID nel ripetitore esterno (CategoryList), quello associato ai record nella Categories tabella. Di conseguenza, sappiamo che CategoryID non può essere un valore di database NULL , motivo per cui è possibile eseguire il cast cieco del Eval metodo senza verificare se si sta gestendo un DBNulloggetto .

Con questo approccio è necessario creare il metodo e recuperarlo nel GetProductsInCategory(categoryID) set appropriato di prodotti forniti categoryID. A tale scopo, è sufficiente restituire il ProductsDataTable restituito dal ProductsBLL metodo della classe.GetProductsByCategoryID(categoryID) Consente di creare il GetProductsInCategory(categoryID) metodo nella classe code-behind per la NestedControls.aspx pagina. Eseguire questa operazione usando il codice seguente:

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

Questo metodo crea semplicemente un'istanza ProductsBLL del metodo e restituisce i risultati del GetProductsByCategoryID(categoryID) metodo. Si noti che il metodo deve essere contrassegnato o Protected; se il metodo è contrassegnato PublicPrivate, non sarà accessibile dal markup dichiarativo della pagina ASP.NET.

Dopo aver apportato queste modifiche per usare questa nuova tecnica, passare un momento per visualizzare la pagina tramite un browser. L'output deve essere identico all'output quando si usa l'approccio del gestore objectDataSource e ItemDataBound dell'evento (fare riferimento alla figura 5 per visualizzare una schermata).

Nota

Potrebbe sembrare occupato per creare il GetProductsInCategory(categoryID) metodo nella classe code-behind della pagina ASP.NET. Dopo tutto, questo metodo crea semplicemente un'istanza della ProductsBLL classe e restituisce i risultati del GetProductsByCategoryID(categoryID) relativo metodo. Perché non chiamare questo metodo direttamente dalla sintassi databinding nel ripetitore interno, come: DataSource='<%# ProductsBLL.GetProductsByCategoryID(CType(Eval("CategoryID"), Integer)) %>'? Anche se questa sintassi non funziona con l'implementazione corrente della ProductsBLL classe (poiché il GetProductsByCategoryID(categoryID) metodo è un metodo di istanza), è possibile modificare ProductsBLL per includere un metodo statico o avere una classe include un metodo statico GetProductsByCategoryID(categoryID)Instance() per restituire una nuova istanza della ProductsBLL classe.

Sebbene tali modifiche eliminino la necessità GetProductsInCategory(categoryID) del metodo nella classe code-behind della pagina ASP.NET, il metodo della classe code-behind offre maggiore flessibilità nell'uso dei dati recuperati, come si vedrà brevemente.

Recupero di tutte le informazioni sul prodotto contemporaneamente

Le due tecniche pervie che abbiamo esaminato afferrano i prodotti per la categoria corrente effettuando una chiamata al ProductsBLL metodo della GetProductsByCategoryID(categoryID) classe (il primo approccio è stato eseguito tramite ObjectDataSource, il secondo tramite il GetProductsInCategory(categoryID) metodo nella classe code-behind). Ogni volta che questo metodo viene richiamato, il livello della logica di business viene richiamato al livello di accesso ai dati, che esegue una query sul database con un'istruzione SQL che restituisce righe dalla tabella il Products cui CategoryID campo corrisponde al parametro di input fornito.

Date N categorie nel sistema, questo approccio nets N + 1 chiama al database una query di database per ottenere tutte le categorie e quindi N chiamate per ottenere i prodotti specifici per ogni categoria. Tuttavia, è possibile recuperare tutti i dati necessari in due chiamate di database una sola chiamata per ottenere tutte le categorie e un'altra per ottenere tutti i prodotti. Una volta che tutti i prodotti sono disponibili, è possibile filtrare tali prodotti in modo che solo i prodotti corrispondenti all'attuale CategoryID siano associati al ripetitore interno della categoria.

Per fornire questa funzionalità, è sufficiente apportare una leggera modifica al GetProductsInCategory(categoryID) metodo nella classe code-behind della pagina ASP.NET. Invece di restituire in modo cieco i risultati del ProductsBLL metodo della GetProductsByCategoryID(categoryID) classe, è possibile accedere prima a tutti i prodotti (se non sono già stati accessibili) e quindi restituire solo la visualizzazione filtrata dei prodotti in base all'oggetto passato CategoryID.

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

Si noti l'aggiunta della variabile a livello di pagina, allProducts. Contiene informazioni su tutti i prodotti e viene popolato la prima volta che viene richiamato il GetProductsInCategory(categoryID) metodo. Dopo aver verificato che l'oggetto allProducts sia stato creato e popolato, il metodo filtra i risultati di DataTable in modo che siano accessibili solo le righe corrispondenti CategoryID all'oggetto specificato CategoryID . Questo approccio riduce il numero di volte in cui si accede al database da N + 1 a due.

Questo miglioramento non introduce alcuna modifica al markup sottoposto a rendering della pagina, né restituisce meno record rispetto all'altro approccio. Riduce semplicemente il numero di chiamate al database.

Nota

È possibile che la riduzione del numero di accessi al database possa migliorare in modo intuitivo le prestazioni. Tuttavia, questo potrebbe non essere il caso. Se si dispone di un numero elevato di prodotti il cui CategoryID valore è NULL, ad esempio, la chiamata al GetProducts metodo restituisce un numero di prodotti che non vengono mai visualizzati. Inoltre, la restituzione di tutti i prodotti può essere sprecata se si mostra solo un sottoinsieme delle categorie, che potrebbe essere il caso se è stato implementato il paging.

Come sempre, quando si tratta di analizzare le prestazioni di due tecniche, l'unica misura surefire consiste nell'eseguire test controllati personalizzati per gli scenari comuni del caso dell'applicazione.

Riepilogo

In questa esercitazione è stato illustrato come annidare un controllo Web di dati all'interno di un altro, esaminando in particolare come un ripetitore esterno visualizzi un elemento per ogni categoria con un ripetitore interno che elenca i prodotti per ogni categoria in un elenco puntato. La sfida principale nella creazione di un'interfaccia utente annidata consiste nell'accedere ai dati corretti e associarli al controllo Web dei dati interni. Sono disponibili diverse tecniche, due delle quali sono state esaminate in questa esercitazione. Il primo approccio esaminato ha utilizzato un ObjectDataSource nel controllo ItemTemplate Web dei dati esterni associato al controllo Web dei dati interni tramite la relativa DataSourceID proprietà. La seconda tecnica ha eseguito l'accesso ai dati tramite un metodo nella classe code-behind della pagina ASP.NET. Questo metodo può quindi essere associato alla proprietà del DataSource controllo Web dei dati interni tramite la sintassi di associazione dati.

Mentre l'interfaccia utente nidificata esaminata in questa esercitazione usava un ripetitore annidato all'interno di un repeater, queste tecniche possono essere estese agli altri controlli Web dati. È possibile annidare un repeater all'interno di un controllo GridView o gridView all'interno di un oggetto DataList e così via.

Buon programmatori!

Informazioni sull'autore

Scott Mitchell, autore di sette libri ASP/ASP.NET e fondatore di 4GuysFromRolla.com, lavora con le tecnologie Web Microsoft dal 1998. Scott lavora come consulente indipendente, formatore e scrittore. Il suo ultimo libro è Sams Teach Yourself ASP.NET 2.0 in 24 ore. Può essere raggiunto all'indirizzo mitchell@4GuysFromRolla.com. o tramite il suo blog, disponibile all'indirizzo http://ScottOnWriting.NET.

Grazie speciale a

Questa serie di esercitazioni è stata esaminata da molti revisori utili. I revisori principali di questa esercitazione erano Zack Jones e Liz Shulok. Si è interessati a esaminare i prossimi articoli MSDN? In tal caso, rilasciami una riga in mitchell@4GuysFromRolla.com.