Вложенные веб-элементы управления данными (VB)

Скотт Митчелл

Загрузить PDF-файл

В этом руководстве мы рассмотрим, как использовать repeater, вложенный в другой repeater. В примерах показано, как заполнить внутренний повторитель декларативно и программным способом.

Введение

Помимо статического синтаксиса HTML и привязки данных, шаблоны могут также включать веб-элементы управления и пользовательские элементы управления. Эти веб-элементы управления могут иметь свои свойства, назначенные с помощью декларативного синтаксиса привязки данных, или могут быть доступны программным способом в соответствующих обработчиках событий на стороне сервера.

Путем внедрения элементов управления в шаблон можно настроить и улучшить внешний вид и взаимодействие с пользователем. Например, в руководстве Использование TemplateFields в элементе управления GridView мы узнали, как настроить отображение GridView, добавив элемент управления Calendar в TemplateField для отображения даты найма сотрудника. В учебниках Добавление элементов управления проверкой в редактор и вставку интерфейсов и Настройка интерфейса изменения данных мы узнали, как настроить интерфейсы редактирования и вставки, добавив элементы управления проверкой, TextBoxes, DropDownLists и другие веб-элементы управления.

Шаблоны также могут содержать другие веб-элементы управления данными. То есть у нас может быть DataList, который содержит другой список данных (или Repeater, GridView, DetailsView и т. д.) в своих шаблонах. Проблема с таким интерфейсом заключается в привязке соответствующих данных к внутреннему веб-элементу управления данными. Существует несколько различных подходов: от декларативных параметров с использованием ObjectDataSource до программных.

В этом руководстве мы рассмотрим, как использовать repeater, вложенный в другой repeater. Внешний repeater будет содержать элемент для каждой категории в базе данных, отображающий имя и описание категории. Внутренний ретранслятор каждого элемента категории будет отображать сведения о каждом продукте, относящемся к этой категории (см. рис. 1) в маркированном списке. В наших примерах показано, как заполнить внутренний повторитель декларативно и программным способом.

Каждая категория, вместе с ее продуктами, перечислены

Рис. 1. Каждая категория вместе с продуктами отображается в списке (щелкните, чтобы просмотреть полноразмерное изображение)

Шаг 1. Создание списка категорий

При создании страницы, которая использует вложенные веб-элементы управления данными, я считаю полезным сначала проектировать, создавать и тестировать внешний веб-элемент управления данными, даже не беспокоясь о внутреннем вложенном элементе управления. Поэтому начнем с шагов, необходимых для добавления ретранслятора на страницу со списком имен и описаний для каждой категории.

Начните с открытия NestedControls.aspx страницы в папке DataListRepeaterBasics и добавьте на страницу элемент управления Repeater, задав для его ID свойства значение CategoryList. В смарт-теге Repeater выберите создать объект ObjectDataSource с именем CategoriesDataSource.

Присвойте имя новому объекту ObjectDataSource CategoriesDataSource

Рис. 2. Присвоение имени новому объекту ObjectDataSource CategoriesDataSource (щелкните для просмотра полноразмерного изображения)

Настройте ObjectDataSource таким образом, чтобы он извлекает свои данные из CategoriesBLL метода класса GetCategories .

Настройка ObjectDataSource для использования метода GetCategories класса CategoriesBLL

Рис. 3. Настройка ObjectDataSource для использования CategoriesBLL метода Classs GetCategories (щелкните для просмотра полноразмерного изображения)

Чтобы указать содержимое шаблона Repeater, необходимо перейти в представление Источника и вручную ввести декларативный синтаксис. Добавьте объект , отображающий ItemTemplate имя категории в элементе <h4> и описание категории в элементе абзаца (<p>). Кроме того, давайте разделим каждую категорию горизонтальным правилом (<hr>). После внесения этих изменений страница должна содержать декларативный синтаксис для Repeater и ObjectDataSource, который выглядит примерно так:

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

На рисунке 4 показан наш прогресс при просмотре в браузере.

Имя и описание каждой категории перечислены, разделенные горизонтальным правилом

Рис. 4. Имя и описание каждой категории отображаются, разделенные горизонтальным правилом (щелкните, чтобы просмотреть полноразмерное изображение)

Шаг 2. Добавление вложенного повторителя продукта

После завершения перечисления категорий наша следующая задача — добавить в элемент repeater CategoryListItemTemplate , который отображает сведения о продуктах, относящихся к соответствующей категории. Существует несколько способов получения данных для этого внутреннего ретранслятора, два из которых мы рассмотрим в ближайшее время. Пока просто создадим продукт Repeater в CategoryList файле Repeater s ItemTemplate. В частности, пусть повторитель продукта отображает каждый продукт в маркированный список с каждым элементом списка, включая название и цену продукта.

Чтобы создать этот repeater, необходимо вручную ввести декларативный синтаксис и шаблоны внутреннего repeater в CategoryList .ItemTemplate Добавьте следующую разметку в 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>

Шаг 3. Привязка Category-Specific Products к элементу Repeater ProductsByCategoryList

Если вы перейдете на страницу в браузере на этом этапе, ваш экран будет выглядеть так же, как на рис. 4, так как мы еще не привязали все данные к Repeater. Есть несколько способов, с помощью которых можно получить соответствующие записи о продукте и привязать их к Repeater, некоторые из которых более эффективны, чем другие. Задача main заключается в получении соответствующих продуктов для указанной категории.

Доступ к данным, которые необходимо привязать к внутреннему элементу управления Repeater, можно получить декларативно, через ObjectDataSource в CategoryList repeater ItemTemplates , или программно со страницы кода программной части ASP.NET страницы. Аналогичным образом эти данные могут быть привязаны к внутреннему repeater либо декларативно — с помощью свойства inner Repeater s DataSourceID или с помощью декларативного синтаксиса привязки данных, либо программно путем ссылки на внутренний repeater в CategoryList обработчике событий Repeater ItemDataBound , программной настройки его DataSource свойства и вызова его DataBind() метода. Рассмотрим каждый из этих подходов.

Декларативный доступ к данным с помощью элемента управления ObjectDataSource и обработчикаItemDataBoundсобытий

Так как мы широко использовали ObjectDataSource в этой серии руководств, наиболее естественным выбором для доступа к данным в этом примере является использование ObjectDataSource. Класс ProductsBLL имеет GetProductsByCategoryID(categoryID) метод , который возвращает сведения о продуктах, принадлежащих указанному categoryIDобъекту . Таким образом, мы можем добавить ObjectDataSource в CategoryList repeater ItemTemplate и настроить его для доступа к своим данным из этого метода класса .

К сожалению, repeater не позволяет редактировать свои шаблоны в режиме конструктора, поэтому нам нужно вручную добавить декларативный синтаксис для этого элемента управления ObjectDataSource. В следующем синтаксисе показаны значения CategoryList repeater ItemTemplate после добавления этого нового Объекта 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>

При использовании подхода ObjectDataSource необходимо задать ProductsByCategoryList для свойства Repeater s DataSourceID значение ID объекта ObjectDataSource (ProductsByCategoryDataSource). Кроме того, обратите внимание, что объект ObjectDataSource содержит <asp:Parameter> элемент , указывающий categoryID значение, которое будет передано в GetProductsByCategoryID(categoryID) метод . Но как указать это значение? В идеале мы могли бы просто задать DefaultValue свойство <asp:Parameter> элемента с помощью синтаксиса привязки данных, например:

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

К сожалению, синтаксис привязки данных допустим только в элементах управления с событием DataBinding . В Parameter классе отсутствует такое событие, поэтому приведенный выше синтаксис является недопустимым и приведет к ошибке среды выполнения.

Чтобы задать это значение, необходимо создать обработчик событий для CategoryList события Repeater.ItemDataBound Помните, что ItemDataBound событие срабатывает один раз для каждого элемента, привязанного к repeater. Поэтому каждый раз, когда это событие возникает для внешнего repeater, можно присвоить текущее CategoryID значение параметру ProductsByCategoryDataSource ObjectDataSource.CategoryID

Создайте обработчик событий для CategoryList события Repeater ItemDataBound с помощью следующего кода:

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

Этот обработчик событий начинается с обеспечения того, что мы имеем дело с элементом данных, а не с элементом верхнего, нижнего колонтитула или разделителя. Далее мы ссылаемся на фактический CategoriesRow экземпляр, который только что был привязан к текущему RepeaterItemобъекту . Наконец, мы ссылаемся на ObjectDataSource в ItemTemplate и присваиваем его CategoryID значение параметра текущему CategoryIDRepeaterItemобъекту .

С помощью этого обработчика ProductsByCategoryList событий repeater в каждом из них RepeaterItem привязан к продуктам RepeaterItem в категории . На рисунке 5 показан снимок экрана с выходными данными.

Внешний ретранслятор Списки каждой категории; внутренний Списки продукты для этой категории

Рис. 5. Внешний повторитель Списки каждой категории; внутренний Списки продукты для этой категории (щелкните, чтобы просмотреть полноразмерное изображение)

Программный доступ к данным Products by Category

Вместо использования ObjectDataSource для получения продуктов для текущей категории можно создать метод в классе кода программной части страницы ASP.NET (либо в App_Code папке или в отдельном проекте библиотеки классов), который возвращает соответствующий набор продуктов при передаче в CategoryID. Представьте, что у нас есть такой метод в классе кода программной части страницы ASP.NET и что он называется GetProductsInCategory(categoryID). С помощью этого метода можно привязать продукты для текущей категории к внутреннему repeater, используя следующий декларативный синтаксис:

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

Свойство Repeater использует DataSource синтаксис привязки данных, чтобы указать, что его данные поступают из GetProductsInCategory(categoryID) метода . Так как Eval("CategoryID") возвращает значение типа Object, мы приведения объекта к перед передачей Integer его в GetProductsInCategory(categoryID) метод . Обратите внимание, что объект , доступный CategoryID здесь с помощью синтаксиса привязки данных, является элементом CategoryID во внешнем repeater (CategoryList), который привязан к записям в Categories таблице. Поэтому мы знаем, что CategoryID не может быть значением базы данных NULL , поэтому мы можем слепо привести Eval метод, не проверив, имеет ли мы дело с DBNull.

При таком подходе необходимо создать GetProductsInCategory(categoryID) метод и получить соответствующий набор продуктов с заданным categoryID. Это можно сделать, просто возвратив объект , ProductsDataTable возвращенный методом ProductsBLL класса s GetProductsByCategoryID(categoryID) . Давайте создадим GetProductsInCategory(categoryID) метод в классе кода программной части для нашей NestedControls.aspx страницы. Сделайте это с помощью следующего кода:

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

Этот метод просто создает экземпляр ProductsBLL метода и возвращает результаты GetProductsByCategoryID(categoryID) метода . Обратите внимание, что метод должен быть помечен Public или Protected; если метод помечен Private, он не будет доступен из декларативной разметки страницы ASP.NET.

После внесения этих изменений для использования этого нового метода найдите время, чтобы просмотреть страницу в браузере. Выходные данные должны совпадать с выходными данными при использовании подхода ObjectDataSource и ItemDataBound обработчика событий (см. снимок экрана на рисунке 5).

Примечание

Создание метода в классе кода программной части ASP.NET страницы может показаться занятой работой GetProductsInCategory(categoryID) . В конце концов, этот метод просто создает экземпляр ProductsBLL класса и возвращает результаты его GetProductsByCategoryID(categoryID) метода. Почему бы просто не вызвать этот метод непосредственно из синтаксиса привязки данных во внутреннем repeater, например: DataSource='<%# ProductsBLL.GetProductsByCategoryID(CType(Eval("CategoryID"), Integer)) %>'? Хотя этот синтаксис не будет работать с текущей ProductsBLL реализацией класса (так как GetProductsByCategoryID(categoryID) метод является методом экземпляра), вы можете включить ProductsBLL статический GetProductsByCategoryID(categoryID) метод или включить статический Instance() метод для возврата нового экземпляра ProductsBLL класса.

Хотя такие изменения устранили бы необходимость GetProductsInCategory(categoryID) в методе в классе кода программной части страницы ASP.NET, метод класса кода программной части обеспечивает большую гибкость в работе с извлеченными данными, как мы увидим вскоре.

Получение всех сведений о продукте одновременно

Два основных метода, которые мы рассмотрели, захватывают эти продукты для текущей категории путем вызова ProductsBLL метода класса GetProductsByCategoryID(categoryID) (первый подход делал это через ObjectDataSource, второй — через GetProductsInCategory(categoryID) метод в классе кода программной части). При каждом вызове этого метода уровень бизнес-логики вызывает уровень доступа к данным, который запрашивает базу данных с помощью инструкции SQL, возвращающей строки из Products таблицы, поле которой CategoryID соответствует указанному входным параметру.

Учитывая N категорий в системе, этот подход не содержит N + 1 вызовов к базе данных один запрос базы данных для получения всех категорий, а затем N вызовов для получения продуктов, относящихся к каждой категории. Однако мы можем получить все необходимые данные только в двух вызовах базы данных: один вызов для получения всех категорий, а другой — для получения всех продуктов. Получив все продукты, мы можем отфильтровать эти продукты, чтобы только те продукты, которые соответствуют текущему CategoryID , были привязаны к внутреннему ретранслятору этой категории.

Чтобы обеспечить эту функциональность, нам нужно лишь внести небольшое изменение GetProductsInCategory(categoryID) в метод в классе кода программной части страницы ASP.NET. Вместо того чтобы слепо возвращать результаты ProductsBLL метода класса GetProductsByCategoryID(categoryID) , мы можем сначала получить доступ ко всем продуктам (если они еще не были доступны), а затем вернуть только отфильтрованное представление продуктов на основе переданного в 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

Обратите внимание на добавление переменной уровня страницы . allProducts Он содержит сведения обо всех продуктах и заполняется при первом вызове GetProductsInCategory(categoryID) метода. Убедившись, что allProducts объект создан и заполнен, метод фильтрует результаты DataTable таким образом, чтобы были доступны только те строки, для которых CategoryID задано соответствие.CategoryID Такой подход сокращает количество обращений к базе данных с N + 1 до двух.

Это улучшение не вносит никаких изменений в отображаемую разметку страницы и не возвращает меньше записей, чем в другом подходе. Это просто сокращает количество вызовов к базе данных.

Примечание

Можно интуитивно предположить, что сокращение числа обращений к базе данных, несомненно, повысит производительность. Однако это может быть не так. Если имеется большое количество продуктов CategoryIDNULL, например GetProducts , вызов метода возвращает количество продуктов, которые никогда не отображаются. Кроме того, возврат всех продуктов может быть расточительным, если отображается только подмножество категорий, что может быть в случае реализации разбиения по страницам.

Как всегда, когда дело доходит до анализа производительности двух методов, единственной верной мерой является выполнение управляемых тестов, адаптированных для распространенных сценариев вашего приложения.

Сводка

В этом руководстве мы узнали, как вложить один веб-элемент управления данными в другой, в частности, чтобы внешний repeater отображал элемент для каждой категории с внутренним повторителем, перечисляющим продукты для каждой категории в маркированный список. Задача main при создании вложенного пользовательского интерфейса заключается в доступе к правильным данным и привязке к веб-элементу управления внутренними данными. Существует множество доступных методов, два из которых мы рассмотрели в этом руководстве. Первый рассмотренный подход использовал ObjectDataSource в веб-элементе управления ItemTemplate внешних данных, который был привязан к внутреннему веб-элементу управления данных через его DataSourceID свойство. Второй метод обращается к данным с помощью метода в классе кода программной части страницы ASP.NET. Затем этот метод можно привязать к свойству внутренних веб-элементов управления DataSource данными с помощью синтаксиса привязки данных.

Хотя вложенный пользовательский интерфейс, рассмотренный в этом руководстве, использовал ретранслятор, вложенный в repeater, эти методы можно расширить на другие веб-элементы управления данными. Вы можете вложить repeater в GridView или GridView в DataList и т. д.

Счастливого программирования!

Об авторе

Скотт Митчелл( Scott Mitchell), автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с веб-технологиями Майкрософт с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Sams Teach Yourself ASP.NET 2.0 в 24 часах. Он может быть доступен в mitchell@4GuysFromRolla.com. или через его блог, который можно найти по адресу http://ScottOnWriting.NET.

Особая благодарность

Эта серия учебников была рассмотрена многими полезными рецензентами. Ведущими рецензентами этого руководства были Зак Джонс и Лиз Шулок. Хотите просмотреть предстоящие статьи MSDN? Если да, опустите мне строку на mitchell@4GuysFromRolla.com.