Udostępnij za pośrednictwem


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

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 właściwości za pośrednictwem deklaratywnej, składni powiązania danych lub mogą być dostępne programowo w odpowiednich programowych programach obsługi zdarzeń po stronie serwera.

Osadzając kontrolki w szablonie, wygląd i środowisko użytkownika można dostosowywać i ulepszać. 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 polu TemplateField w celu wyświetlenia daty zatrudnienia pracownika; W samouczkach Dodawanie kontrolek walidacji do interfejsów edycji i wstawiania oraz dostosowywania interfejsu modyfikacji danych zobaczyliś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ć element DataList, który zawiera inną listę danych (lub repeater, GridView lub DetailsView itd.) w swoich szablonach. Wyzwaniem za pomocą takiego interfejsu jest powiązanie 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 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 element kategorii wewnętrzny repeater będzie wyświetlać informacje dla każdego produktu należącego 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 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, która używa zagnieżdżonych kontrolek sieci Web danych, uważam, że warto zaprojektować, utworzyć i przetestować najbardziej zewnętrzną kontrolkę internetową danych, nie martwiąc się nawet o wewnętrzną zagnieżdżonym kontrolce. 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 s wybierz opcję utworzenia nowego obiektu ObjectDataSource o nazwie CategoriesDataSource.

Nadaj nowej nazwie ObjectDataSource CategoriesDataSource

Rysunek 2. Nadaj nowemu obiektowi nazwę 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. Konfigurowanie obiektu ObjectDataSource do używania CategoriesBLLGetCategories metody klasy (kliknij, aby wyświetlić obraz w 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 postęp wyświetlania za pośrednictwem przeglądarki.

Każda nazwa i opis kategorii są wyświetlane, oddzielone regułą poziomą

Rysunek 4. Każda nazwa i opis kategorii są wyświetlane, oddzielone regułą poziomą (kliknij, aby wyświetlić obraz pełnowymiarowy)

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

Po zakończeniu wyświetlania 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 po prostu utwórzmy produkty Repeater w CategoryList obiekcie Repeater s ItemTemplate. W szczególności, let s have the product Repeater display each product in a bulleted list with each list item including the product name and price.

Aby utworzyć ten repeater, musimy ręcznie wprowadzić wewnętrzną składnię deklaratywną repeatera i szablony do s CategoryListItemTemplate. 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 repeaterem ProductsByCategoryList

Jeśli na tym etapie odwiedzasz stronę 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 repeater, niektóre bardziej wydajne niż inne. Głównym wyzwaniem jest powrót odpowiednich produktów dla określonej kategorii.

Dane, które mają zostać powiązane z wewnętrzną kontrolką Repeater, mogą być dostępne deklaratywnie za pośrednictwem obiektu ObjectDataSource w CategoryList elemencie Repeater s ItemTemplatelub programowo ze strony kodowej strony ASP.NET. Podobnie te dane mogą być powiązane z wewnętrznym repeaterem deklaratywnie — za pośrednictwem wewnętrznej właściwości Repeater DataSourceID lub za pośrednictwem deklaratywnej składni powiązania danych lub programowo, odwołując się do wewnętrznego repeatera w CategoryList procedurze obsługi zdarzeń repeatera ItemDataBound , programowo ustawiając jego DataSource właściwość i wywołując jej DataBind() 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 w szerokim zakresie, najbardziej naturalnym wyborem na potrzeby 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ć obiekt ObjectDataSource do CategoryList obiektu Repeater s ItemTemplate i skonfigurować go w celu uzyskania dostępu do danych z tej metody klasy.

Niestety repeater nie zezwala na edycję jego szablonów za pomocą widoku Projektu, dlatego musimy ręcznie dodać składnię deklaratywną dla tej kontrolki ObjectDataSource. Następująca składnia pokazuje CategoryList repeater s ItemTemplate po dodaniu tego 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 metody ObjectDataSource należy ustawić ProductsByCategoryList właściwość Repeater na DataSourceID właściwość ID ObjectDataSource (ProductsByCategoryDataSource). Zwróć również uwagę, że element ObjectDataSource określa <asp:Parameter>categoryID wartość, która zostanie przekazana do GetProductsByCategoryID(categoryID) metody . Ale jak określić tę wartość? W idealnym przypadku możemy po prostu 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ższa składnia jest niedozwolona 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 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 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 odwołujemy się do rzeczywistego CategoriesRow wystąpienia, które zostało właśnie powiązane z bieżącym RepeaterItemelementem . Na koniec odwołujemy się do obiektu ObjectDataSource w obiekcie ItemTemplate i przypisujemy jego CategoryID wartość parametru do CategoryID wartości bieżącej RepeaterItem.

W przypadku tej procedury obsługi ProductsByCategoryList zdarzeń repeater 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.

Wzmacniak zewnętrzny Listy każdej kategorii; wewnętrzny Listy produkty dla tej kategorii

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

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

Zamiast używać obiektu ObjectDataSource do pobierania produktów dla bieżącej kategorii, możemy utworzyć metodę w naszej klasie ASP.NET s code-behind (lub w App_Code folderze lub w oddzielnym projekcie biblioteki klas), która zwraca odpowiedni zestaw produktów po przekazaniu w CategoryIDobiekcie . Załóżmy, że mamy taką metodę w naszej klasie ASP.NET s code-behind i że została ona nazwana GetProductsInCategory(categoryID). Dzięki tej metodzie możemy powiązać produkty dla bieżącej kategorii z wewnętrznym repeater przy użyciu następującej składni deklaratywnej:

<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 obiekt Integer przed przekazaniem go do GetProductsInCategory(categoryID) metody . Zwróć uwagę, że CategoryID dostęp w tym miejscu za pośrednictwem składni powiązania danych jest elementem CategoryID 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 DBNull.

W tym podejściu musimy utworzyć metodę GetProductsInCategory(categoryID) i pobrać odpowiedni zestaw produktów, biorąc pod uwagę podany element categoryID. Możemy to zrobić, zwracając po prostu zwrócony ProductsDataTable przez metodę ProductsBLL klasy s GetProductsByCategoryID(categoryID) . Utwórzmy metodę GetProductsInCategory(categoryID) w klasie kodu 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 ASP.NET strony znaczników deklaratywnych.

Po wprowadzeniu tych zmian w celu korzystania z 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 w przypadku korzystania z metody ObjectDataSource i ItemDataBound obsługi zdarzeń (wróć do rysunku 5, aby zobaczyć zrzut ekranu).

Uwaga

Tworzenie metody w klasie ASP.NET s code-behind może wydawać się zajęte GetProductsInCategory(categoryID) . 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((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() w celu zwrócenia nowego wystąpienia ProductsBLL klasy.

Chociaż takie modyfikacje wyeliminowałyby konieczność korzystania GetProductsInCategory(categoryID) z metody w klasie kodowej za ASP.NET strony, metoda klasy stojącej za kodem zapewnia nam większą elastyczność podczas pracy z pobranymi danymi, ponieważ 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 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 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.