Controlli Web dei dati annidati (C#)
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.
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
.
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.
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.
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 ItemTemplate
a 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 ilItemDataBound
gestore 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 DataSourceID
ID
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 CategoryList
ItemDataBound
Repeater 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. 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.
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 CategoryID
oggetto . 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((int)(Eval("CategoryID"))) %>'>
...
</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 DBNull
oggetto .
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 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 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 Public
Private
, 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((int)(Eval("CategoryID"))) %>'
? 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 eseguendo 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 viene richiamato questo metodo, 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 Products
tabella il 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 di ogni categoria. È tuttavia possibile recuperare tutti i dati necessari in solo due chiamate di database una 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. Anziché restituire in modo cieco i risultati del metodo della ProductsBLL
GetProductsByCategoryID(categoryID)
classe, è possibile prima accedere a tutti i prodotti (se non sono già stati accessibili) e quindi restituire solo la visualizzazione filtrata dei prodotti in base al passaggio CategoryID
.
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 popolata la prima volta che viene richiamato il GetProductsInCategory(categoryID)
metodo. Dopo aver verificato che l'oggetto sia stato creato e popolato, il metodo filtra i risultati di DataTable in modo che solo le righe corrispondenti CategoryID
all'oggetto allProducts
specificato CategoryID
siano accessibili. 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.
Nota
Uno potrebbe essere un motivo intuitivo per cui ridurre il numero di accessi al database potrebbe migliorare in modo sicuro le prestazioni. Tuttavia, questo potrebbe non essere il caso. Se si ha un numero elevato di prodotti il cui CategoryID
è 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 sprecato se si mostra solo un subset delle categorie, che potrebbe essere il caso se si è 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 dell'applicazione.
Riepilogo
In questa esercitazione è stato illustrato come annidare un controllo Web dati all'interno di un altro, esaminando in particolare come avere un ripetitore esterno visualizza 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 è l'accesso e l'associazione dei dati corretti 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 un oggetto ObjectDataSource nel controllo Web dei dati esterni associato al controllo ItemTemplate
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 databinding.
Anche se l'interfaccia utente annidata esaminata in questa esercitazione ha usato un ripetitore annidato all'interno di un ripetitore, queste tecniche possono essere estese agli altri controlli Web dati. È possibile annidare un ripetitore all'interno di gridView o gridView all'interno di un oggetto DataList e così via.
Programmazione felice!
Informazioni sull'autore
Scott Mitchell, autore di sette libri ASP/ASP.NET e fondatore di 4GuysFromRolla.com, ha lavorato con le tecnologie Microsoft Web dal 1998. Scott lavora come consulente indipendente, allenatore e scrittore. Il suo ultimo libro è Sams Teach Yourself ASP.NET 2,0 in 24 Ore. Può essere raggiunto a mitchell@4GuysFromRolla.com. o tramite il suo blog, che può essere trovato in http://ScottOnWriting.NET.
Grazie speciali
Questa serie di esercitazioni è stata esaminata da molti revisori utili. I revisori principali per questa esercitazione erano Zack Jones e Liz Shulok. Interessati a esaminare i prossimi articoli MSDN? In tal caso, lasciami una riga in mitchell@4GuysFromRolla.com.