Udostępnij za pomocą


Zagnieżdżone kontrolki sieciowe danych (C#)

Autor : Scott Mitchell

Pobierz plik PDF

W tym samouczku zbadamy, jak używać repeatera zagnieżdżonego wewnątrz innego repeatera. W przykładach pokazano, jak wypełnić wewnętrzny repeater zarówno deklaratywnie, jak i programowo.

Wprowadzenie

Oprócz statycznej składni HTML i składni powiązania danych szablony mogą również obejmować kontrolki sieci Web i kontrolki użytkownika. Te kontrolki sieci Web mogą mieć przypisane właściwości za pośrednictwem deklaratywnej składni powiązania danych lub można uzyskać do nich dostęp programowo w odpowiednich procedurach 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 Używanie pól szablonu w kontrolce GridView pokazano, jak dostosować wyświetlanie kontrolki GridView przez dodanie kontrolki Kalendarz w polu TemplateField w celu wyświetlenia daty zatrudnienia pracownika; w samouczkach Dodawanie kontrolek walidacji do interfejsów edycji i wstawiania oraz Dostosowywanie interfejsu modyfikacji danych zobaczyliśmy, jak dostosować interfejsy edycji i wstawiania, dodając kontrolki walidacji, pola tekstowe, listy rozwijane i inne kontrolki sieci Web.

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

W tym samouczku zbadamy, 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 element kategorii będzie mieć wewnętrzny Repeater wyświetlający informacje o każdym produkcie należącym do tej kategorii (zobacz Rysunek 1) na liście wypunktowanej. Nasze przykłady ilustrują sposób wypełniania wewnętrznego repeatera zarówno deklaratywnie, jak i programowo.

Każda kategoria wraz z jej produktami jest wymieniona

Rysunek 1. Każda kategoria wraz z jej produktami jest wyświetlana (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 najpierw zaprojektować, utworzyć i przetestować najbardziej zewnętrzną kontrolkę danych sieci Web, nie martwiąc się nawet o wewnętrzną zagnieżdżoną kontrolkę. W związku z tym zacznijmy od wykonania kroków niezbędnych do dodania repeatera do strony zawierającej nazwę i opis każdej kategorii.

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

Podaj nazwę dla nowego ObjectDataSource o nazwie CategoriesDataSource

Rysunek 2. Nadaj nowej nazwie ObjectDataSource 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 CategoriesBLL

Rysunek 3: Skonfiguruj obiekt ObjectDataSource, aby używał metody klasy CategoriesBLLGetCategories (Kliknij, aby wyświetlić obraz o pełnym rozmiarze)

Aby określić zawartość szablonu repeatera, musimy przejść do widoku Źródło i ręcznie wprowadzić składnię deklaratywną. Dodaj element ItemTemplate , który wyświetla nazwę kategorii w elemecie <h4> i opis kategorii w elemecie akapitu (<p>). Ponadto rozdzielmy każdą kategorię regułą poziomą (<hr>). Po wprowadzeniu tych zmian strona powinna zawierać składnię deklaratywną dla repeater i ObjectDataSource, która jest podobna do następującej:

<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 nasz postęp widziany 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 powtarzacza zagnieżdżonego produktu

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 pobierania danych dla tego wewnętrznego repeatera, z których dwa zostaną wkrótce zbadane. Na razie 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 w pliku CategoryList s ItemTemplate. Dodaj następujący znacznik 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 repeaterem ProductsByCategoryList

Jeśli w tym momencie przejdziesz do strony za pośrednictwem przeglądarki, 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 produktów i powiązać je z Repeaterem, niektóre bardziej wydajne niż inne. Głównym wyzwaniem jest odzyskanie odpowiednich produktów dla określonej kategorii.

Dane do powiązania z wewnętrzną kontrolką Repeater można uzyskać deklaratywnie za pośrednictwem ObjectDataSource w kontrolce CategoryList Repeater ItemTemplate lub programowo z page code-behind strony ASP.NET. Podobnie te dane mogą być powiązane z wewnętrznym repeaterem, i to zarówno deklaratywnie — poprzez właściwość repeatera DataSourceID używając składni deklaratywnego wiązania danych, jak i programowo, odwołując się do wewnętrznego repeatera w module obsługi zdarzeń repeatera CategoryList, programowo ustawiając jego właściwość ItemDataBound i wywołując jego metodę DataSource. 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 w szerokim zakresie, najbardziej naturalnym wyborem do uzyskiwania dostępu do danych w tym przykładzie jest trzymanie się obiektu ObjectDataSource. Klasa ProductsBLL ma metodę GetProductsByCategoryID(categoryID) , która zwraca informacje o tych produktach należących do określonego categoryIDelementu . W związku z tym możemy dodać ObjectDataSource do CategoryList Repeater ItemTemplate i skonfigurować go w celu uzyskania dostępu do danych z metody tej klasy.

Niestety repeater nie zezwala na edycję szablonów za pomocą widoku projektu, dlatego musimy ręcznie dodać składnię deklaratywną dla tej kontrolki ObjectDataSource. Poniższa składnia CategoryList przedstawia element 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ć właściwość ProductsByCategoryList Repeater DataSourceID na ID ObjectDataSource (ProductsByCategoryDataSource). Zwróć również uwagę, że nasz element ObjectDataSource ma <asp:Parameter> element, określający wartość categoryID, która zostanie przekazana do metody GetProductsByCategoryID(categoryID). Ale jak określić tę wartość? Idealnie, moglibyśmy po prostu ustawić DefaultValue właściwość tego <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 wiązania danych jest prawidłowa tylko w kontrolach, które mają DataBinding zdarzenie. Klasa Parameter nie ma takiego zdarzenia, dlatego powyższa składnia jest nielegalna i spowoduje wystąpienie błędu w czasie wykonywania.

Aby ustawić tę wartość, musimy utworzyć procedurę obsługi zdarzeń dla CategoryList zdarzenia Repeater ItemDataBound. Pamiętaj, że zdarzenie ItemDataBound jest uruchamiane raz dla każdego elementu powiązanego z Repeaterem. 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 repeatera ItemDataBound za pomocą następującego kodu:

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

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 odnosimy się do rzeczywistego wystąpienia CategoriesRow, które zostało powiązane z bieżącym elementem RepeaterItem. Na koniec odwołujemy się do ObjectDataSource w elemencie ItemTemplate i przypisujemy jego wartość parametru CategoryID do CategoryID bieżącego RepeaterItem.

Ten obsługiwacz zdarzeń ProductsByCategoryList powoduje, że repeater w każdym RepeaterItem jest powiązany z tymi produktami w kategorii RepeaterItem. Rysunek 5 przedstawia zrzut ekranu wynikowych danych wyjściowych.

Zewnętrzny repeater wyświetla każdą kategorię; wewnętrzny repeater listuje produkty dla tej kategorii

Rysunek 5. Zewnętrzne moduły powtarzania wyświetlają każdą kategorię; Wewnętrzny jeden wyświetla listę produktów dla tej kategorii (kliknij, aby wyświetlić obraz o pełnym rozmiarze)

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

Zamiast używać ObjectDataSource do pobierania produktów dla bieżącej kategorii, możemy utworzyć metodę w klasie code-behind naszego projektu ASP.NET (lub w folderze App_Code lub w osobnym projekcie Biblioteki klas), która zwraca odpowiedni zestaw produktów po przekazaniu w niej argumentu CategoryID. Wyobraź sobie, że mieliśmy taką metodę w klasie code-behind strony ASP.NET, która została nazwana GetProductsInCategory(categoryID). Dzięki tej metodzie możemy powiązać produkty dla bieżącej kategorii z wewnętrznym Repeaterem przy użyciu deklaratywnej składni:

<asp:Repeater runat="server" ID="ProductsByCategoryList" EnableViewState="False"
      DataSource='<%# GetProductsInCategory((int)(Eval("CategoryID"))) %>'>
  ...
</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 Integer przed przekazaniem go do metody GetProductsInCategory(categoryID). Zwróć uwagę, że CategoryID element, do którego uzyskuje się dostęp tutaj za pośrednictwem składni powiązania danych, jest CategoryID w zewnętrznym Repeater (CategoryList), tym, który jest powiązany z rekordami w tabeli Categories. 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 DBNull.

W przypadku tego podejścia 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 wartość zwróconą przez metodę ProductsDataTable klasy ProductsBLL. Utwórzmy metodę GetProductsInCategory(categoryID) w klasie code-behind dla naszej NestedControls.aspx strony. W tym celu należy użyć następującego kodu:

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

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 deklaratywnego znacznika strony ASP.NET.

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

Uwaga / Notatka

Może się wydawać pracą bez większego sensu tworzenie metody GetProductsInCategory(categoryID) w klasie code-behind strony ASP.NET. W końcu ta metoda po prostu tworzy wystąpienie ProductsBLL klasy i zwraca wyniki jej GetProductsByCategoryID(categoryID) metody. Dlaczego nie wywołać tej metody bezpośrednio z wewnętrznego repeatera w składni powiązania danych, na przykład: DataSource='<%# ProductsBLL.GetProductsByCategoryID((int)(Eval("CategoryID"))) %>'? Mimo że 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ą GetProductsByCategoryID(categoryID) lub klasa zawiera metodę statyczną Instance() , aby zwrócić nowe wystąpienie ProductsBLL klasy.

Chociaż takie modyfikacje wyeliminowałyby konieczność korzystania z metody w klasie kodu za ASP.NET strony, metoda w klasie kodu zapewnia nam większą elastyczność w pracy z pobranymi danymi, co zaraz zobaczymy.

Pobieranie wszystkich informacji o produkcie jednocześnie

Dwie poprzednie techniki, które przeanalizowaliśmy, pobierały te produkty dla bieżącej kategorii, wywołując metodę klasy ProductsBLLGetProductsByCategoryID(categoryID). Pierwsze podejście zrobiło to za pośrednictwem ObjectDataSource; drugie za pomocą metody GetProductsInCategory(categoryID) w klasie kodowej. 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 Products pole jest zgodne z CategoryID podanym parametrem wejściowym.

Biorąc pod uwagę N kategorii w systemie, to podejście skutkuje N + 1 połączeniami do bazy danych: jedno zapytanie w celu pobrania wszystkich kategorii, a następnie N połączeń w celu pobrania produktów specyficznych dla każdej kategorii. Możemy jednak pobrać wszystkie potrzebne dane w zaledwie dwóch wywołaniach do bazy danych: jedno wywołanie, aby pobrać wszystkie kategorie, a drugie, aby pobrać wszystkie produkty. Po uzyskaniu wszystkich produktów, możemy je filtrować tak, aby tylko te, które pasują do bieżącej CategoryID, były powiązane z wewnętrzną kontrolką Repeater tej kategorii.

Aby zapewnić tę funkcjonalność, musimy wprowadzić niewielką modyfikację w metodzie GetProductsInCategory(categoryID) klasy zaplecza kodu naszej strony ASP.NET. 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 już pobrane), a następnie zwrócić tylko przefiltrowany widok produktów na podstawie przekazanego elementu 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;
}

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 się, że obiekt allProducts został utworzony i wypełniony, metoda filtruje wyniki tabeli DataTable tak, aby były dostępne tylko te wiersze, których CategoryID odpowiadają określonemu CategoryID. 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 renderowanych znacznikach strony ani nie przywraca mniejszej liczby rekordów niż inne podejście. Po prostu zmniejsza liczbę wywołań do bazy danych.

Uwaga / Notatka

Można intuicyjnie uzasadnić, że zmniejszenie liczby dostępu do bazy danych z pewnością poprawi wydajność. Jednak może to nie być możliwe. 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 podzbiór kategorii, co może być możliwe w przypadku zaimplementowania stronicowania.

Jak zawsze, jeśli chodzi o analizę wydajności dwóch technik, jedyną niezawodną miarą jest uruchamianie kontrolowanych testów dostosowanych do typowych scenariuszy użycia aplikacji.

Podsumowanie

W tym samouczku pokazano, jak zagnieżdżać jedną kontrolkę danych Web w innej kontrolce, w szczególności sprawdzając, jak zewnętrzny Repeater wyświetla element dla każdej kategorii, z wewnętrznym Repeaterem zawierającym produkty dla każdej kategorii w wypunktowanej liście. Głównym wyzwaniem w tworzeniu zagnieżdżonego interfejsu użytkownika jest uzyskiwanie dostępu do odpowiednich danych i powiązanie ich z wewnętrzną kontrolą sieci Web danych. Dostępnych jest wiele technik, z których dwie przeanalizowaliśmy w tym samouczku. Pierwsze zbadane podejście używało ObjectDataSource w zewnętrznej kontrolce danych sieci Web, która była powiązana z wewnętrzną kontrolką danych sieci Web za pośrednictwem jej właściwości ItemTemplate. Druga technika uzyskuje dostęp do danych za pośrednictwem metody w klasie code-behind strony ASP.NET. Ta metoda może być następnie powiązana z wewnętrzną właściwością kontrolki danych sieci Web DataSource za pomocą składni wiązania danych.

Podczas gdy zagnieżdżony interfejs użytkownika zbadany w tym samouczku używał kontrolki repeatera zagnieżdżonej w repeaterze, te techniki można rozszerzyć na inne kontrolki danych sieci Web. Możesz zagnieżdżać Repeater w GridView lub GridView w DataList i tak dalej.

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 go uzyskać pod adresem mitchell@4GuysFromRolla.com.

Specjalne podziękowania

Ta seria samouczków została omówiona przez wielu przydatnych recenzentów. Recenzenci z tego samouczka to Zack Jones i Liz Shulok. Chcesz przejrzeć nadchodzące artykuły MSDN? Jeśli tak, napisz do mnie na adres mitchell@4GuysFromRolla.com.