Udostępnij za pośrednictwem


Zagnieżdżone kontrolki internetowe danych (VB)

Autor: Scott Mitchell

Pobierz plik PDF

W tym samouczku dowiesz się, jak używać repeatera zagnieżdżonego wewnątrz innego repeatera. Przykłady ilustrują sposób wypełniania wewnętrznego repeatera zarówno deklaratywnie, jak i programowo.

Wprowadzenie

Oprócz statycznej składni HTML i składni powiązania danych szablony mogą również zawierać kontrolki sieci Web i kontrolki użytkownika. Te kontrolki sieci Web mogą mieć przypisane ich właściwości za pośrednictwem składni deklaratywnej, składni powiązania danych lub można uzyskać do nich dostęp programowo w odpowiednich programowych programowych programach obsługi zdarzeń po stronie serwera.

Osadzając kontrolki w szablonie, wygląd i środowisko użytkownika można dostosować i ulepszyć. Na przykład w samouczku Using TemplateFields in the GridView Control (Używanie pól szablonów w kontrolce GridView ) pokazano, jak dostosować wyświetlanie kontrolki GridView przez dodanie kontrolki Kalendarz w elemecie TemplateField w celu wyświetlenia daty zatrudnienia pracownika; w samouczkach Dodawanie kontrolek walidacji do interfejsów edycji i wstawiania oraz dostosowywanie interfejsu modyfikacji danych widzieliśmy, jak dostosować interfejsy edycji i wstawiania, dodając kontrolki walidacji, TextBoxes, DropDownLists i inne kontrolki sieci Web.

Szablony mogą również zawierać inne kontrolki sieci Web danych. Oznacza to, że możemy mieć listę danych zawierającą inną listę danych (lub repeater lub GridView lub DetailsView itd.) w swoich szablonach. Wyzwanie związane z takim interfejsem polega na powiązaniu odpowiednich danych z wewnętrzną kontrolą sieci Web danych. Dostępnych jest kilka różnych metod, od opcji deklaratywnych przy użyciu obiektu ObjectDataSource po metody programowe.

W tym samouczku dowiesz się, jak używać repeatera zagnieżdżonego wewnątrz innego repeatera. Zewnętrzny repeater będzie zawierać element dla każdej kategorii w bazie danych, wyświetlając nazwę i opis kategorii. Każdy wewnętrzny repeater elementu kategorii będzie wyświetlać informacje o każdym produkcie należącym do tej kategorii (zobacz Rysunek 1) na liście punktowanej. Nasze przykłady ilustrują sposób wypełniania wewnętrznego repeatera zarówno deklaratywnie, jak i programowo.

Każda kategoria wraz z produktami znajduje się na liście

Rysunek 1. Każda kategoria wraz z produktami znajduje się na liście (kliknij, aby wyświetlić obraz pełnowymiarowy)

Krok 1. Tworzenie listy kategorii

Podczas tworzenia strony korzystającej z zagnieżdżonych kontrolek sieci Web uważam, że warto zaprojektować, utworzyć i przetestować najbardziej zewnętrzną kontrolkę sieci Web danych, nie martwiąc się nawet o wewnętrzną zagnieżdżonym kontrolce. Dlatego zacznijmy od przejścia przez kroki niezbędne do dodania repeatera do strony zawierającej nazwę i opis każdej kategorii.

Zacznij od otwarcia strony w DataListRepeaterBasics folderze i dodania kontrolki Repeater do strony, ustawiając jej ID właściwość na CategoryList.NestedControls.aspx Z inteligentnego tagu Repeater wybierz opcję utworzenia nowego obiektu ObjectDataSource o nazwie CategoriesDataSource.

Nazwij nową kategorię ObjectDataSourceDataSource

Rysunek 2. Nadaj nazwę nowemu obiektowiDataSource CategoriesDataSource (kliknij, aby wyświetlić obraz o pełnym rozmiarze)

Skonfiguruj obiekt ObjectDataSource, aby pobierał dane z CategoriesBLL metody klasy s GetCategories .

Konfigurowanie obiektu ObjectDataSource do używania metody GetCategories klasy CategoryBLL

Rysunek 3. Konfigurowanie obiektu ObjectDataSource do używania CategoriesBLL metody Klasy GetCategories (kliknij, aby wyświetlić obraz o pełnym rozmiarze)

Aby określić zawartość szablonu Repeater, musimy przejść do widoku Źródło i ręcznie wprowadzić składnię deklaratywną. Dodaj element ItemTemplate wyświetlający nazwę kategorii w <h4> elemecie i opis kategorii w elemecie akapitu (<p>). Ponadto należy oddzielić każdą kategorię za pomocą reguły poziomej (<hr>). Po wprowadzeniu tych zmian strona powinna zawierać składnię deklaratywną dla elementu Repeater i ObjectDataSource, który jest podobny do następującego:

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

Rysunek 4 przedstawia postęp podczas wyświetlania za pośrednictwem przeglądarki.

Każda nazwa i opis kategorii jest wymieniona, oddzielona regułą poziomą

Rysunek 4. Każda nazwa i opis kategorii jest wymieniona, oddzielona przez regułę poziomą (kliknij, aby wyświetlić obraz pełnowymiarowy)

Krok 2. Dodawanie zagnieżdżonego modułu powtarzającego produkt

Po zakończeniu listy kategorii następnym zadaniem jest dodanie repeatera do CategoryList s ItemTemplate , który wyświetla informacje o tych produktach należących do odpowiedniej kategorii. Istnieje wiele sposobów, na które możemy pobrać dane dla tego wewnętrznego repeatera, z których dwa omówimy wkrótce. Na razie po prostu utwórzmy produkty Repeater w elemencie CategoryList Repeater s ItemTemplate. W szczególności załóżmy, że produkt Repeater wyświetla każdy produkt na liście punktowanej z każdym elementem listy, w tym nazwą produktu i ceną.

Aby utworzyć ten repeater, musimy ręcznie wprowadzić wewnętrzną składnię deklaratywną repeatera i szablony do CategoryList elementu s ItemTemplate. Dodaj następujące znaczniki w elemencie 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>

Krok 3. Powiązanie produktów Category-Specific z elementem ProductsByCategoryList Repeater

Jeśli odwiedzisz stronę za pośrednictwem przeglądarki w tym momencie, ekran będzie wyglądać tak samo jak na rysunku 4, ponieważ nie powiązaliśmy jeszcze żadnych danych z repeaterem. Istnieje kilka sposobów, na które możemy pobrać odpowiednie rekordy produktu i powiązać je z repeater, niektóre bardziej wydajne niż inne. Głównym wyzwaniem jest powrót odpowiednich produktów dla określonej kategorii.

Dane powiązane z wewnętrzną kontrolką repeatera mogą być dostępne deklaratywne za pośrednictwem obiektu ObjectDataSource w CategoryList elemencie Repeater s ItemTemplatelub programowo ze strony ASP.NET strony kodowej. Podobnie te dane mogą być powiązane z wewnętrznym repeaterem deklaratywnie — za pośrednictwem właściwości wewnętrznego repeatera DataSourceID lub deklaratywnej składnibinowania danych lub programowo, odwołując się do wewnętrznego repeatera w CategoryList procedurze obsługi zdarzeń Repeatera ItemDataBound , programowo ustawiając jej właściwość i wywołując jej DataSourceDataBind() metodę. Przyjrzyjmy się każdemu z tych podejść.

Uzyskiwanie dostępu do danych deklaratywnie za pomocą kontrolki ObjectDataSource i programu obsługi zdarzeńItemDataBound

Ponieważ w tej serii samouczków korzystaliśmy z obiektu ObjectDataSource, najbardziej naturalnym wyborem do uzyskiwania dostępu do danych w tym przykładzie jest trzymanie się obiektu ObjectDataSource. Klasa ProductsBLL ma metodę zwracającą GetProductsByCategoryID(categoryID) informacje o tych produktach należących do określonego categoryIDelementu . W związku z tym możemy dodać obiekt ObjectDataSource do CategoryList elementu Repeater s ItemTemplate i skonfigurować go w celu uzyskania dostępu do danych z tej metody klasy.

Niestety, repeater nie zezwala na edytowanie szablonów za pomocą widoku projektu, dlatego musimy ręcznie dodać składnię deklaratywną dla tej kontrolki ObjectDataSource. Poniższa składnia przedstawia CategoryList funkcję Repeater s ItemTemplate po dodaniu nowego obiektu 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>

W przypadku korzystania z podejścia ObjectDataSource należy ustawić ProductsByCategoryList właściwość Repeater na DataSourceIDID właściwość ObjectDataSource (ProductsByCategoryDataSource). Zwróć również uwagę, że nasza baza danych ObjectDataSource zawiera <asp:Parameter> element określający wartość, categoryID która zostanie przekazana do GetProductsByCategoryID(categoryID) metody. Ale jak określić tę wartość? W idealnym przypadku można ustawić DefaultValue właściwość <asp:Parameter> elementu przy użyciu składni powiązania danych, w następujący sposób:

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

Niestety składnia powiązania danych jest prawidłowa tylko w kontrolkach, które mają DataBinding zdarzenie. Klasa Parameter nie ma takiego zdarzenia, dlatego powyższej składni jest niedozwolona i spowoduje błąd środowiska uruchomieniowego.

Aby ustawić tę wartość, musimy utworzyć procedurę obsługi zdarzeń dla CategoryList zdarzenia repeatera ItemDataBound . Pamiętaj, że ItemDataBound zdarzenie jest uruchamiane raz dla każdego elementu powiązanego z repeater. W związku z tym za każdym razem, gdy to zdarzenie jest uruchamiane dla zewnętrznego repeatera, możemy przypisać bieżącą CategoryID wartość do parametru ProductsByCategoryDataSource ObjectDataSource CategoryID .

Utwórz procedurę obsługi zdarzeń dla CategoryList zdarzenia Repeater za ItemDataBound pomocą następującego kodu:

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

Ta procedura obsługi zdarzeń rozpoczyna się od upewnienia się, że mamy do czynienia z elementem danych, a nie nagłówkiem, stopką lub elementem separatora. Następnie odwołujemy się do rzeczywistego CategoriesRow wystąpienia, które właśnie zostało powiązane z bieżącym RepeaterItem. Na koniec odwołujemy się do obiektu ObjectDataSource w obiekcie ItemTemplate i przypisujemy jej CategoryID wartość parametru do CategoryID bieżącej RepeaterItemwartości .

W przypadku tej procedury obsługi zdarzeń repeater ProductsByCategoryList w każdym RepeaterItem z nich jest powiązany z tymi produktami w RepeaterItem kategorii s. Rysunek 5 przedstawia zrzut ekranu wynikowych danych wyjściowych.

Zewnętrzny powtarzacz Listy każdej kategorii; wewnętrzny Listy produktów dla tej kategorii

Rysunek 5. Zewnętrzny powtarzacz Listy każdej kategorii; wewnętrzna Listy produkty dla tej kategorii (kliknij, aby wyświetlić obraz pełnowymiarowy)

Programowe uzyskiwanie dostępu do produktów według danych kategorii

Zamiast używać obiektu ObjectDataSource do pobierania produktów dla bieżącej kategorii, możemy utworzyć metodę w naszej klasie ASP.NET stronicowania kodu (lub w App_Code folderze lub w osobnym projekcie biblioteki klas), który zwraca odpowiedni zestaw produktów po przekazaniu w CategoryIDobiekcie . Załóżmy, że mieliśmy taką metodę w naszej klasie ASP.NET page-behind i że została ona nazwana GetProductsInCategory(categoryID). Za pomocą tej metody możemy powiązać produkty dla bieżącej kategorii z wewnętrznym repeaterem przy użyciu następującej składni deklaratywnej:

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

Właściwość Repeater DataSource używa składni powiązania danych, aby wskazać, że jej dane pochodzą z GetProductsInCategory(categoryID) metody . Ponieważ Eval("CategoryID") zwraca wartość typu Object, rzutujemy obiekt na obiekt przed przekazaniem GetProductsInCategory(categoryID) go do Integer metody . Należy pamiętać, że dostęp CategoryID do tego miejsca za pośrednictwem składni powiązania danych jest CategoryID elementem w zewnętrznym repeaterze (CategoryList), tym, który jest powiązany z rekordami w Categories tabeli. W związku z tym wiemy, że CategoryID nie może być wartością bazy danych NULL , dlatego możemy ślepo rzutować Eval metodę bez sprawdzania, czy mamy do czynienia z wartością DBNull.

Dzięki temu podejściu musimy utworzyć metodę GetProductsInCategory(categoryID) i pobrać odpowiedni zestaw produktów, biorąc pod uwagę podany element categoryID. Możemy to zrobić, po prostu zwracając zwrócony ProductsDataTable przez metodę ProductsBLL klasy s GetProductsByCategoryID(categoryID) . Utwórzmy metodę GetProductsInCategory(categoryID) w klasie code-behind dla naszej NestedControls.aspx strony. Wykonaj to przy użyciu następującego kodu:

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

Ta metoda po prostu tworzy wystąpienie ProductsBLL metody i zwraca wyniki GetProductsByCategoryID(categoryID) metody. Należy pamiętać, że metoda musi być oznaczona Public lub Protected; jeśli metoda jest oznaczona Private, nie będzie dostępna z ASP.NET strony deklaratywnej znaczników.

Po wprowadzeniu tych zmian, aby użyć tej nowej techniki, poświęć chwilę, aby wyświetlić stronę za pośrednictwem przeglądarki. Dane wyjściowe powinny być identyczne z danymi wyjściowymi podczas korzystania z metody ObjectDataSource i ItemDataBound procedury obsługi zdarzeń (zapoznaj się z rysunkiem 5, aby zobaczyć zrzut ekranu).

Uwaga

Może się wydawać, że zajęta praca nad utworzeniem GetProductsInCategory(categoryID) metody w klasie ASP.NET stronicowania kodu. W końcu ta metoda po prostu tworzy wystąpienie ProductsBLL klasy i zwraca wyniki jej GetProductsByCategoryID(categoryID) metody. Dlaczego nie tylko wywołaj tę metodę bezpośrednio ze składni powiązania danych w wewnętrznym repeaterze, na przykład: DataSource='<%# ProductsBLL.GetProductsByCategoryID(CType(Eval("CategoryID"), Integer)) %>'? Chociaż ta składnia nie będzie działać z naszą bieżącą implementacją ProductsBLL klasy (ponieważ GetProductsByCategoryID(categoryID) metoda jest metodą wystąpienia), można zmodyfikować ProductsBLL tak, aby zawierała metodę statyczną lub klasa zawiera metodę statyczną GetProductsByCategoryID(categoryID)Instance() , aby zwrócić nowe wystąpienie ProductsBLL klasy.

Chociaż takie modyfikacje wyeliminowałyby konieczność GetProductsInCategory(categoryID) korzystania z metody w klasie kodu za stroną ASP.NET, metoda klasy za pomocą kodu zapewnia nam większą elastyczność pracy z pobranymi danymi, jak wkrótce zobaczymy.

Pobieranie wszystkich informacji o produkcie jednocześnie

Dwie przekonujące techniki, które zbadaliśmy, pobierają te produkty dla bieżącej kategorii, wywołując metodę ProductsBLL klasy GetProductsByCategoryID(categoryID) (pierwsze podejście zrobiło to za pośrednictwem obiektu ObjectDataSource, drugiego za pomocą GetProductsInCategory(categoryID) metody w klasie code-behind). Za każdym razem, gdy ta metoda jest wywoływana, warstwa logiki biznesowej wywołuje warstwę dostępu do danych, która wysyła zapytanie do bazy danych za pomocą instrukcji SQL zwracającej wiersze z tabeli, których CategoryID pole jest zgodne z Products podanym parametrem wejściowym.

Biorąc pod uwagę N kategorii w systemie, to podejście nets N + 1 wywołuje do bazy danych jedno zapytanie bazy danych w celu pobrania wszystkich kategorii, a następnie N wywołań w celu uzyskania produktów specyficznych dla każdej kategorii. Możemy jednak pobrać wszystkie potrzebne dane w zaledwie dwóch bazach danych wywołuje jedno wywołanie, aby pobrać wszystkie kategorie i inne, aby pobrać wszystkie produkty. Gdy mamy wszystkie produkty, możemy filtrować te produkty, aby tylko produkty pasujące do bieżącej CategoryID były powiązane z wewnętrzną powtarzaną kategorią.

Aby zapewnić tę funkcję, musimy wprowadzić niewielką modyfikację GetProductsInCategory(categoryID) metody w naszej klasie ASP.NET stronicowej klasy. Zamiast ślepo zwracać wyniki ProductsBLL metody klasy GetProductsByCategoryID(categoryID) , zamiast tego możemy najpierw uzyskać dostęp do wszystkich produktów (jeśli nie zostały one jeszcze pobrane), a następnie zwrócić tylko filtrowany widok produktów na podstawie przekazanego elementu 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

Zwróć uwagę na dodanie zmiennej na poziomie strony. allProducts Zawiera on informacje o wszystkich produktach i jest wypełniany przy pierwszym GetProductsInCategory(categoryID) wywołaniu metody. Po upewnieniu allProducts się, że obiekt został utworzony i wypełniony, metoda filtruje wyniki tabeli Danych, tak aby były dostępne tylko te wiersze, których CategoryID dopasowania CategoryID są dostępne. Takie podejście zmniejsza liczbę prób uzyskania dostępu do bazy danych z N + 1 do dwóch.

To ulepszenie nie wprowadza żadnych zmian w renderowanej adiustacji strony ani nie przywraca mniejszej liczby rekordów niż inne podejście. Po prostu zmniejsza liczbę wywołań do bazy danych.

Uwaga

Można intuicyjnie z tego powodu, że zmniejszenie liczby dostępu do bazy danych z pewnością poprawi wydajność. Jednak może to nie być przypadek. Jeśli masz dużą liczbę produktów, których CategoryID na przykład jest NULL, wywołanie GetProducts metody zwraca liczbę produktów, które nigdy nie są wyświetlane. Ponadto zwracanie wszystkich produktów może być marnotrawne, jeśli wyświetlasz tylko podzestaw kategorii, co może być przypadkiem, jeśli zaimplementowano stronicowanie.

Jak zawsze, jeśli chodzi o analizowanie wydajności dwóch technik, jedyną miarą surefire jest uruchamianie kontrolowanych testów dostosowanych do typowych scenariuszy przypadków aplikacji.

Podsumowanie

W tym samouczku pokazano, jak zagnieżdżać jedną kontrolkę sieci Web danych w innej, w szczególności sprawdzając sposób wyświetlania zewnętrznego repeatera elementu dla każdej kategorii z wewnętrznym powtarzaczem zawierającym listę produktów dla każdej kategorii na liście punktowanej. Głównym wyzwaniem w tworzeniu zagnieżdżonego interfejsu użytkownika jest dostęp i powiązanie prawidłowych danych z wewnętrzną kontrolą sieci Web danych. Dostępnych jest wiele technik, z których dwa zostały zbadane w tym samouczku. Pierwsze podejście zbadane używało obiektu ObjectDataSource w kontrolce sieci Web danych zewnętrznych powiązanych z wewnętrzną kontrolką sieci Web ItemTemplate danych za pośrednictwem jej DataSourceID właściwości. Druga technika uzyskiwała dostęp do danych za pośrednictwem metody w klasie ASP.NET s code-behind. Następnie można powiązać tę metodę z właściwością wewnętrznej kontrolki DataSource sieci Web danych za pomocą składni łączenia danych.

Podczas gdy zagnieżdżony interfejs użytkownika zbadany w tym samouczku używał zagnieżdżonego modułu powtarzającego, te techniki można rozszerzyć na inne kontrolki sieci Web danych. Można zagnieżdżać repeater w elemencie GridView lub GridView w obrębie listy danych itd.

Szczęśliwe programowanie!

Informacje o autorze

Scott Mitchell, autor siedmiu książek ASP/ASP.NET i założyciel 4GuysFromRolla.com, współpracuje z technologiami internetowymi firmy Microsoft od 1998 roku. Scott pracuje jako niezależny konsultant, trener i pisarz. Jego najnowsza książka to Sams Teach Yourself ASP.NET 2.0 w ciągu 24 godzin. Można do niego dotrzeć pod adresem mitchell@4GuysFromRolla.com. Lub za pośrednictwem swojego bloga, który można znaleźć na stronie http://ScottOnWriting.NET.

Specjalne podziękowania

Ta seria samouczków została sprawdzona przez wielu pomocnych recenzentów. Recenzenci w tym samouczku to Zack Jones i Liz Shulok. Chcesz przejrzeć nadchodzące artykuły MSDN? Jeśli tak, upuść mi wiersz pod adresem mitchell@4GuysFromRolla.com.