Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом руководстве мы рассмотрим, как использовать повторитель, вложенный в другой повторител. В примерах показано, как заполнить внутренний повторитель как декларативно, так и программно.
Введение
Помимо статического синтаксиса HTML и привязки данных, шаблоны также могут включать веб-элементы управления и элементы управления пользователем. Эти веб-элементы управления могут иметь свои свойства, назначенные с помощью декларативного синтаксиса, синтаксиса привязки данных или доступ к ним программным способом в соответствующих обработчиках событий на стороне сервера.
Внедрив элементы управления в шаблон, внешний вид и взаимодействие с пользователем можно настроить и улучшить. Например, в руководстве "Использование TemplateFields в элементе управления GridView" мы узнали, как настроить отображение GridView, добавив элемент управления Calendar в TemplateField для отображения даты найма сотрудника; в учебниках по добавлению элементов управления проверки в интерфейсы редактирования и вставки и настройке интерфейса модификации данных мы узнали, как настроить интерфейсы редактирования и вставки, добавив элементы управления проверки, TextBoxes, DropDownLists и другие веб-элементы управления.
Шаблоны также могут содержать другие веб-элементы управления данными. То есть у нас может быть DataList, содержащий другой объект DataList (или Repeater, или GridView, или DetailsView и т. д.) в своих шаблонах. Проблема с таким интерфейсом — привязка соответствующих данных к внутреннему веб-элементу управления данными. Существует несколько различных подходов, начиная от декларативных параметров с помощью ObjectDataSource до программных.
В этом руководстве мы рассмотрим, как использовать повторитель, вложенный в другой повторител. Внешний повторитель будет содержать элемент для каждой категории в базе данных, отображая имя и описание категории. Внутренний повторитель каждого элемента категории будет отображать сведения о каждом продукте, относящемся к этой категории (см. рис. 1), в виде маркированного списка. В наших примерах показано, как заполнять внутренний повторитель как декларативно, так и программно.
Рис. 1. Каждая категория, а также ее продукты, перечислены (щелкните, чтобы просмотреть изображение полного размера)
Шаг 1. Создание списка категорий
При создании страницы, использующую вложенные веб-элементы управления данными, я считаю, что полезно сначала разрабатывать, создавать и тестировать внешние веб-элементы управления данными, даже не беспокоясь о внутреннем вложенном элементе управления. Поэтому рассмотрим шаги, необходимые для добавления повторителя на страницу, в которую перечислены имя и описание каждой категории.
Начните с открытия страницы NestedControls.aspx
в папке DataListRepeaterBasics
и добавьте Repeater элемент управления на страницу, установив для свойства ID
значение CategoryList
. В смарт-теге Repeater выберите создать объект ObjectDataSource с именем CategoriesDataSource
.
Рис. 2. Назовите новый объект ObjectDataSource CategoriesDataSource
(щелкните, чтобы просмотреть изображение полного размера)
Настройте ObjectDataSource, чтобы он извлекал данные из CategoriesBLL
метода класса GetCategories
.
Рис. 3. Настройка ObjectDataSource для использования CategoriesBLL
метода класса 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. Добавление вложенного элемента повторителя продукта
После завершения перечисления категорий наша следующая задача — добавить повторитель CategoryList
в S ItemTemplate
, отображающий сведения об этих продуктах, принадлежащих соответствующей категории. Существует несколько способов получить данные для этого внутреннего повторителя, два из которых мы рассмотрим в ближайшее время. На данный момент давайте просто создадим продукты Repeater в CategoryList
Repeater'а ItemTemplate
. В частности, пусть компонент Repeater отобразит каждый продукт в виде маркированного списка, где каждый элемент списка включает название и цену продукта.
Чтобы создать этот повторитель, необходимо вручную ввести декларативный синтаксис и шаблоны внутреннего повторителя в CategoryList
ItemTemplate
. Добавьте следующую разметку в CategoryList
Repeater 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 к Repeater ProductsByCategoryList
Если вы посещаете страницу через браузер на этом этапе, экран будет выглядеть так же, как и на рис. 4, так как мы еще не привязали любые данные к повторителеру. Существует несколько способов получить соответствующие записи продуктов и привязать их к повторителю, причем одни из них более эффективны, чем другие. Основной проблемой здесь является возвращение соответствующих продуктов для указанной категории.
Данные, которые необходимо привязать к внутреннему элементу управления Repeater, доступны либо декларативно через ObjectDataSource в дочерних элементах Repeater, либо программно из файла программного кода страницы ASP.NET. Аналогичным образом, эти данные могут быть привязаны к внутреннему повторителю либо декларативно — через свойство внутреннего повторителя DataSourceID
или через декларативный синтаксис привязки данных, либо программным путем, ссылаясь на внутренний повторитель в обработчике события повторителя CategoryList
, программно устанавливая его свойство ItemDataBound
и вызывая его метод DataSource
DataBind()
. Рассмотрим каждый из этих подходов.
Доступ к данным декларативно с помощью элемента управления ObjectDataSource и обработчикаItemDataBound
событий
Так как мы широко использовали ObjectDataSource в этой серии учебников, самый естественный выбор для доступа к данным в этом примере заключается в том, чтобы придерживаться ObjectDataSource. Класс ProductsBLL
имеет метод GetProductsByCategoryID(categoryID)
, который возвращает сведения о тех продуктах, принадлежащих указанному categoryID
. Поэтому мы можем добавить ObjectDataSource в CategoryList
Repeater ItemTemplate
и настроить его для доступа к данным из метода этого класса.
К сожалению, повторитель не позволяет изменять шаблоны с помощью представления конструктора, поэтому необходимо добавить декларативный синтаксис для этого элемента управления 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 на DataSourceID
объекта ObjectDataSource (ID
). Обратите внимание, что наш ObjectDataSource имеет элемент <asp:Parameter>
, который указывает значение categoryID
, передаваемое в метод GetProductsByCategoryID(categoryID)
. Но как указать это значение? В идеале мы могли бы просто задать свойство DefaultValue
элемента <asp:Parameter>
используя синтаксис привязки данных, например:
<asp:Parameter Name="CategoryID" Type="Int32"
DefaultValue='<%# Eval("CategoryID")' />
К сожалению, синтаксис привязки данных действителен только в элементах управления с событием DataBinding
. Класс Parameter
не имеет такого события, поэтому приведенный выше синтаксис является незаконным и приведет к ошибке среды выполнения.
Чтобы задать это значение, необходимо создать обработчик события CategoryList
Repeater ItemDataBound
. Помните, что событие ItemDataBound
запускается один раз для каждого элемента, привязанного к повторителю. Таким образом, каждый раз, когда это событие запускается для внешнего повторителя, можно назначить текущее 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
текущее значение CategoryID
для RepeaterItem
.
С помощью этого обработчика ProductsByCategoryList
событий повторитель в каждом из них RepeaterItem
привязан к этим продуктам RepeaterItem
в категории s. На рисунке 5 показан снимок экрана результирующего вывода.
Рис. 5: Внешний повторитель перечисляет каждую категорию; внутренний повторитель перечисляет продукты для каждой категории (щелкните, чтобы просмотреть изображение в полном размере)
Доступ к продуктам по данным категории программным способом
Вместо того чтобы использовать ObjectDataSource для извлечения продуктов текущей категории, мы могли бы создать метод в классе кода страницы ASP.NET (или в App_Code
папке, или в отдельном проекте библиотеки классов), который возвращает соответствующий набор продуктов при передаче в CategoryID
. Представьте, что у нас был такой метод в классе кодовой части страницы ASP.NET и что он был назван GetProductsInCategory(categoryID)
. С помощью этого метода можно привязать продукты для текущей категории к внутреннему повторитею с помощью следующего декларативного синтаксиса:
<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
в внешнем Повторителе (CategoryList
), который связан с записями в таблице Categories
. Поэтому мы знаем, что CategoryID
не может быть значением базы данных NULL
, поэтому мы можем слепо привести Eval
метод, не проверяя, имеет ли мы дело с DBNull
.
При таком подходе необходимо создать метод GetProductsInCategory(categoryID)
и извлечь соответствующий набор продуктов на основе предоставленного categoryID
. Это можно сделать, просто возвращая ProductsDataTable
возвращаемый методом ProductsBLL
класса GetProductsByCategoryID(categoryID)
. Давайте создадим метод GetProductsInCategory(categoryID)
в классе code-behind для нашей страницы 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, чтобы увидеть снимок экрана).
Замечание
Это может показаться занятой работой, чтобы создать GetProductsInCategory(categoryID)
метод в классе кода ASP.NET страницы. В конце концов, этот метод просто создает экземпляр ProductsBLL
класса и возвращает результаты его GetProductsByCategoryID(categoryID)
метода. Почему этот метод не просто вызывается непосредственно из синтаксиса привязки данных во внутреннем повторитее, например: 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)
в классе code-behind). Каждый раз при вызове этого метода уровень бизнес-логики вызывает уровень доступа к данным, который запрашивает базу данных с инструкцией 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 до двух.
Это улучшение не вводит никаких изменений в отрисованную разметку страницы, а также не возвращает меньше записей, чем другой подход. Это просто сокращает количество вызовов к базе данных.
Замечание
Одна из интуитивно понятных причин заключается в том, что сокращение числа доступа к базе данных приведет к повышению производительности. Однако это может быть не так. Если у вас есть большое количество продуктов, у которых CategoryID
равно NULL
, например, вызов метода GetProducts
возвращает количество товаров, которые никогда не отображаются. Кроме того, возврат всех продуктов может быть расточительным, если отображается только подмножество категорий, что может быть так, если вы реализовали разбиение на страницы.
Как всегда, когда речь идет об анализе производительности двух методов, единственной мерой проверки является выполнение контролируемых тестов, адаптированных для распространенных сценариев вашего приложения.
Сводка
В этом руководстве мы узнали, как вложить один веб-элемент управления данными в другой, в частности, как внешний повторитель отображает элемент для каждой категории, а внутренний повторитель перечисляет продукты для каждой категории в списке с маркерами. Основная проблема в создании вложенного пользовательского интерфейса заключается в доступе и привязке правильных данных к внутреннему веб-элементу управления данными. Доступны различные методы, два из которых мы рассмотрели в этом руководстве. Первый рассмотренный подход использовал ObjectDataSource во внешнем элементе управления данными, который был привязан к внутреннему элементу управления данными через его свойство ItemTemplate
. Второй метод обращается к данным через метод в классе кода ASP.NET страницы. Затем этот метод можно привязать к свойству внутреннего веб-элемента управления DataSource
данными с помощью синтаксиса привязки данных.
В то время как вложенный пользовательский интерфейс, рассмотренный в этом руководстве, использовал повторитель, вложенный в повторитель, эти методы можно расширить на другие веб-элементы управления данными. Вы можете вложить повторитель в GridView или GridView в DataList и т. д.
Счастливое программирование!
Сведения о авторе
Скотт Митчелл, автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с технологиями Microsoft Web с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга — Sams Teach Yourself ASP.NET 2.0 за 24 часа. С ним можно связаться по адресу mitchell@4GuysFromRolla.com.
Особое спасибо кому
Эта серия учебников была проверена многими полезными рецензентами. Ведущие рецензенты этого обучающего материала были Зак Джонс и Лиз Шулок. Хотите просмотреть мои предстоящие статьи MSDN? Если да, напишите мне на mitchell@4GuysFromRolla.com.