Condividi tramite


Controlli Web dei dati annidati (C#)

di Scott Mitchell

Scaricare il PDF

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

Introduzione

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

Incorporando controlli all'interno di un modello, l'aspetto e l'esperienza utente possono essere personalizzati e migliorati. Ad esempio, nell'esercitazione Uso di TemplateFields nel controllo GridView, abbiamo visto come personalizzare la visualizzazione di GridView aggiungendo un controllo Calendario in un TemplateField per mostrare la data di assunzione di un dipendente; nelle esercitazioni Aggiunta di controlli di convalida alle interfacce di modifica e inserimento e Personalizzazione dell'interfaccia di modifica dei dati, abbiamo visto come personalizzare le interfacce di modifica e inserimento aggiungendo controlli di convalida, caselle di testo, elenchi a discesa e altri controlli Web.

I modelli possono contenere anche altri controlli Web dati. Ovvero, è possibile avere un DataList che contiene un altro DataList (o Repeater o GridView o DetailsView e così via) all'interno dei relativi modelli. La sfida con un'interfaccia di questo tipo è associare i dati appropriati al controllo Web dei dati interni. Sono disponibili alcuni approcci diversi, dalle opzioni dichiarative che usano ObjectDataSource a quelle a livello di codice.

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

Ogni categoria, insieme ai relativi prodotti, è elencata

Figura 1: Ogni categoria, insieme ai relativi prodotti, è elencata (fare clic per visualizzare l'immagine a dimensione intera)

Passaggio 1: Creazione dell'elenco di categorie

Quando si compila una pagina che usa controlli Web dati annidati, trovo utile progettare, creare e testare prima il controllo Web dei dati più esterno, senza doversi preoccupare del controllo annidato interno. Di conseguenza, è possibile iniziare esaminando i passaggi necessari per aggiungere un Repeater alla pagina che elenca il nome e la descrizione per ogni categoria.

Per iniziare, aprire la NestedControls.aspx pagina nella DataListRepeaterBasics cartella e aggiungere un controllo Repeater alla pagina, impostandone la ID proprietà su CategoryList. Dallo smart tag Repeater scegliere di creare un nuovo ObjectDataSource denominato CategoriesDataSource.

Assegnare un nome a New ObjectDataSource CategoriesDataSource

Figura 2: Assegnare un nome a New ObjectDataSource CategoriesDataSource (Fare clic per visualizzare l'immagine a dimensione intera)

Configurare ObjectDataSource in modo che estrae i dati dal CategoriesBLL metodo della GetCategories classe .

Configurare ObjectDataSource per l'uso del metodo GetCategories della classe CategoriesBLL

Figura 3: Configurare ObjectDataSource per l'uso del metodo della CategoriesBLL classe (GetCategories a dimensione intera)

Per specificare il contenuto del modello di Repeater, è 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, è possibile separare ogni categoria con una regola orizzontale (<hr>). Dopo aver apportato queste modifiche, la pagina deve contenere una sintassi dichiarativa per Repeater e ObjectDataSource simili alle seguenti:

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

Il nome e la descrizione di ciascuna categoria sono elencati, separati da una linea orizzontale

Figura 4: Il nome e la descrizione di ogni categoria sono elencati, separati da una regola orizzontale (fare clic per visualizzare un'immagine a dimensione intera)

Passaggio 2: Aggiunta del ripetitore di prodotti annidati

Al termine dell'elenco di categorie, l'attività successiva consiste nell'aggiungere CategoryList a s ItemTemplate un Repeater che visualizza informazioni su tali prodotti appartenenti alla categoria appropriata. Esistono diversi modi per recuperare i dati per questo repeater interno, due dei quali verranno esaminati a breve. Per il momento, è sufficiente creare i prodotti Repeater all'interno di CategoryList Repeater s ItemTemplate. Fare in modo che il prodotto Repeater visualizzi ogni prodotto in un elenco puntato, con ogni voce di elenco inclusa il nome e il prezzo del prodotto.

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

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

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

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

Accesso dichiarativo ai dati con un controllo ObjectDataSource e ilItemDataBoundgestore eventi

Poiché in questa serie di esercitazioni è stata usata ampiamente ObjectDataSource, la scelta più naturale per accedere ai dati per questo esempio consiste nel mantenere l'oggetto ObjectDataSource. La ProductsBLL classe dispone di un GetProductsByCategoryID(categoryID) metodo che restituisce informazioni sui prodotti che appartengono all'oggetto specificato categoryID. Pertanto, è possibile aggiungere un ObjectDataSource al CategoryList del Repeater ItemTemplate e configurarlo per accedere ai dati attraverso il metodo di questa classe.

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

Quando si usa l'approccio ObjectDataSource, è necessario impostare la ProductsByCategoryList proprietà DataSourceID Repeater su ID di ObjectDataSource (ProductsByCategoryDataSource). Si noti inoltre che ObjectDataSource dispone di un <asp:Parameter> elemento che specifica il categoryID valore che verrà passato al GetProductsByCategoryID(categoryID) metodo . Ma come si specifica questo valore? Idealmente, saremmo in grado di impostare solo la proprietà dell'elemento usando la DefaultValue<asp:Parameter> sintassi di associazione dati, come illustrato di seguito:

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

Sfortunatamente, la sintassi di associazione dati è valida solo nei controlli con un DataBinding evento. La Parameter classe non dispone di un evento di questo tipo e pertanto la sintassi precedente non è valida e genererà un errore di runtime.

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

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

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();
    }
}

Questo gestore eventi inizia assicurandosi di gestire un elemento di dati anziché l'intestazione, il piè di pagina o l'elemento separatore. Successivamente, facciamo riferimento all'istanza effettiva CategoriesRow appena associata al RepeaterItem corrente. Infine, si fa riferimento a ObjectDataSource in ItemTemplate e si assegna il valore del parametro CategoryID al CategoryID dell'attuale RepeaterItem.

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

Il ripetitore esterno elenca ogni categoria; Inner One Elenca i prodotti per tale categoria

Figura 5: Il ripetitore esterno elenca ogni categoria; Inner One Elenca i prodotti per la categoria (fare clic per visualizzare l'immagine a dimensione intera)

Accesso ai prodotti per categoria dati a livello di codice

Anziché usare objectDataSource per recuperare i prodotti per la categoria corrente, è possibile creare un metodo nella classe code-behind della pagina ASP.NET (o nella App_Code cartella o in un progetto libreria di classi separato) che restituisce il set appropriato di prodotti quando viene passato un oggetto CategoryID. Si supponga di avere un metodo di questo tipo 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 repeater interno usando la sintassi dichiarativa seguente:

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

La proprietà Repeater DataSource utilizza la sintassi di associazione dati 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 a un Integer oggetto prima di passarlo al GetProductsInCategory(categoryID) metodo . Si noti che l'oggetto CategoryID a cui si accede qui tramite la sintassi di associazione dati è nel CategoryID repeater esterno (CategoryList), quello associato ai record nella Categories tabella. Di conseguenza, si sa che CategoryID non può essere un valore di database NULL , motivo per cui è possibile eseguire il cast cieco del Eval metodo senza controllare se si tratta di un oggetto DBNull.

Con questo approccio, è necessario creare il metodo GetProductsInCategory(categoryID) e recuperare l'insieme appropriato di prodotti in base ai criteri forniti categoryID. Possiamo farlo semplicemente restituendo l'ProductsDataTable restituito dal metodo della classe ProductsBLLGetProductsByCategoryID(categoryID). Creiamo il metodo GetProductsInCategory(categoryID) nella classe code-behind per la pagina NestedControls.aspx. A tale scopo, usare il codice seguente:

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);
}

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

Dopo aver apportato queste modifiche per usare questa nuova tecnica, dedicare qualche istante alla visualizzazione della pagina tramite un browser. L'output deve essere identico all'output quando si usa l'approccio objectDataSource e ItemDataBound gestore eventi (fare riferimento alla figura 5 per visualizzare una schermata).

Annotazioni

Potrebbe sembrare un lavoro inutile creare il metodo GetProductsInCategory(categoryID) nella code-behind della classe della pagina ASP.NET. Dopo tutto, questo metodo crea semplicemente un'istanza della ProductsBLL classe e restituisce i risultati del relativo GetProductsByCategoryID(categoryID) metodo. Perché non chiamare semplicemente questo metodo direttamente dalla sintassi di associazione dati nel Repeater interno, ad esempio: DataSource='<%# ProductsBLL.GetProductsByCategoryID((int)(Eval("CategoryID"))) %>'? Sebbene questa sintassi non funzioni 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 includere una classe statica GetProductsByCategoryID(categoryID)Instance() per restituire una nuova istanza della ProductsBLL classe.

Sebbene tali modifiche eliminino la necessità del GetProductsInCategory(categoryID) 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à a breve.

Recupero di tutte le informazioni sul prodotto contemporaneamente

Le due tecniche precedenti che abbiamo esaminato recuperano i prodotti per la categoria corrente effettuando una chiamata al metodo della classe ProductsBLL (il primo approccio è stato eseguito tramite ObjectDataSource, il secondo tramite il metodo GetProductsByCategoryID(categoryID) nella classe code-behind). Ogni volta che questo metodo viene richiamato, il livello di logica di business chiama il 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 risulta in N + 1 chiamate al database: una query per ottenere tutte le categorie e quindi N chiamate per ottenere i prodotti specifici di 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'oggetto corrente CategoryID siano associati al Repeater interno di tale categoria.

Per fornire questa funzionalità, è sufficiente apportare una leggera modifica al GetProductsInCategory(categoryID) metodo nella classe code-behind della pagina ASP.NET. Anziché restituire in modo cieco i risultati del metodo della classe ProductsBLL, possiamo invece accedere prima a GetProductsByCategoryID(categoryID) i prodotti (se non sono già stati acceduti) e poi restituire solo la visualizzazione filtrata dei prodotti in base al fornito.

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

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

Annotazioni

Si potrebbe intuire che la riduzione del numero di accessi al database migliorerebbe sicuramente 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 uno spreco se si mostra solo un sottoinsieme delle categorie, che potrebbe essere il caso se sia stata implementata la paginazione.

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.

Riassunto

In questa esercitazione è stato illustrato come annidare un controllo Web dati all'interno di un altro, esaminando in particolare come un repeater 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 usato objectDataSource nel controllo ItemTemplate Web di 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.

Sebbene l'interfaccia utente nidificata esaminata in questa esercitazione usasse 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 un 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, ha lavorato 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 a mitchell@4GuysFromRolla.com.

Grazie speciale a

Questa serie di esercitazioni è stata esaminata da molti revisori competenti. I revisori principali per questo tutorial erano Zack Jones e Liz Shulok. Si è interessati a esaminare i prossimi articoli MSDN? In tal caso, mandami un messaggio a mitchell@4GuysFromRolla.com.