Вложенные веб-элементы управления данными (C#)
В этом руководстве мы рассмотрим, как использовать 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
.
Рис. 2. Присвоение имени новому объекту ObjectDataSource CategoriesDataSource
(щелкните для просмотра полноразмерного изображения)
Настройте ObjectDataSource таким образом, чтобы он извлекает свои данные из CategoriesBLL
метода класса GetCategories
.
Рис. 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 CategoryList
ItemTemplate
, который отображает сведения о продуктах, относящихся к соответствующей категории. Существует несколько способов получения данных для этого внутреннего ретранслятора, два из которых мы рассмотрим в ближайшее время. Пока просто создадим продукт 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 ItemTemplate
s , или программно со страницы кода программной части 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 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();
}
}
Этот обработчик событий начинается с обеспечения того, что мы имеем дело с элементом данных, а не с элементом верхнего, нижнего колонтитула или разделителя. Далее мы ссылаемся на фактический CategoriesRow
экземпляр, который только что был привязан к текущему RepeaterItem
объекту . Наконец, мы ссылаемся на ObjectDataSource в ItemTemplate
и присваиваем его CategoryID
значение параметра текущему CategoryID
RepeaterItem
объекту .
С помощью этого обработчика 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((int)(Eval("CategoryID"))) %>'>
...
</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 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);
}
Этот метод просто создает экземпляр ProductsBLL
метода и возвращает результаты GetProductsByCategoryID(categoryID)
метода . Обратите внимание, что метод должен быть помечен Public
или Protected
; если метод помечен Private
, он не будет доступен из декларативной разметки страницы ASP.NET.
После внесения этих изменений для использования этого нового метода найдите время, чтобы просмотреть страницу в браузере. Выходные данные должны совпадать с выходными данными при использовании подхода ObjectDataSource и ItemDataBound
обработчика событий (см. снимок экрана на рисунке 5).
Примечание
Создание метода в классе кода программной части ASP.NET страницы может показаться занятой работой GetProductsInCategory(categoryID)
. В конце концов, этот метод просто создает экземпляр ProductsBLL
класса и возвращает результаты его GetProductsByCategoryID(categoryID)
метода. Почему бы просто не вызвать этот метод непосредственно из синтаксиса привязки данных во внутреннем repeater, например: DataSource='<%# ProductsBLL.GetProductsByCategoryID((int)(Eval("CategoryID"))) %>'
? Хотя этот синтаксис не будет работать с текущей 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 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;
}
Обратите внимание на добавление переменной уровня страницы . allProducts
Он содержит сведения обо всех продуктах и заполняется при первом вызове GetProductsInCategory(categoryID)
метода. Убедившись, что allProducts
объект создан и заполнен, метод фильтрует результаты DataTable таким образом, чтобы были доступны только те строки, для которых CategoryID
задано соответствие.CategoryID
Такой подход сокращает количество обращений к базе данных с N + 1 до двух.
Это улучшение не вносит никаких изменений в отображаемую разметку страницы и не возвращает меньше записей, чем в другом подходе. Это просто сокращает количество вызовов к базе данных.
Примечание
Можно интуитивно предположить, что сокращение числа обращений к базе данных, несомненно, повысит производительность. Однако это может быть не так. Если имеется большое количество продуктов CategoryID
NULL
, например 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.