
ASP.NET Repeater и DataBind: примеры на Visual Basic
Repeater самый лёгкий из списковых контролов ASP.NET: он не генерирует ни одного лишнего тега и выводит ровно ту разметку, которую вы описали в шаблонах. Разбираем привязку и события на примерах VB.NET.
Чем Repeater отличается от GridView
GridView рисует таблицу, умеет сортировать, листать и редактировать. За это приходится платить: разметка получается тяжёлой, а стилизовать её под дизайн сайта неудобно. Repeater устроен противоположно. Он просто повторяет шаблон для каждой записи источника и больше ничего не делает: ни таблиц, ни span, ни встроенного оформления.
Поэтому Repeater выбирают, когда нужна полностью своя разметка: список новостей в виде ul, карточки товаров на div с CSS-классами, меню, лента комментариев. Всё, что сверх простого вывода, например постраничность или редактирование, придётся писать самому, но для витринных списков это обычно и не нужно.
Шаблоны Repeater
Разметка задаётся набором шаблонов. ItemTemplate обязателен и описывает одну запись. AlternatingItemTemplate, если задан, используется для каждой второй записи. HeaderTemplate и FooterTemplate выводятся один раз до и после списка, а SeparatorTemplate между элементами.
Внутри шаблонов значения полей вставляются выражениями привязки. Для вывода текста используйте форму с двоеточием, которая кодирует HTML: так название товара с символом «<» не сломает страницу.
<asp:Repeater ID="rptNews" runat="server">
<HeaderTemplate>
<ul class="news-list">
</HeaderTemplate>
<ItemTemplate>
<li class="news-item">
<time><%# Eval("PublishDate", "{0:dd.MM.yyyy}") %></time>
<a href='<%# "news.aspx?id=" & Eval("NewsID") %>'>
<%#: Eval("Title") %>
</a>
<asp:Label ID="lblNew" runat="server" Text="новое"
CssClass="badge" Visible="false" />
</li>
</ItemTemplate>
<FooterTemplate>
</ul>
</FooterTemplate>
</asp:Repeater>Привязка данных из кода
Repeater умеет брать данные из контрола источника через DataSourceID, но чаще его наполняют из code-behind. В DataSource можно передать почти что угодно перечислимое: DataTable, DataView, SqlDataReader, List(Of T) или массив. Затем вызывается DataBind, и шаблоны раскрываются.
Привязку выполняйте только при первой загрузке страницы. При postback Repeater восстановит элементы из ViewState сам, а повторный DataBind сбросит состояние вложенных контролов, например отмеченные флажки.
Imports System.Data.SqlClient
Partial Class News
Inherits System.Web.UI.Page
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
If Not IsPostBack Then BindNews()
End Sub
Private Sub BindNews()
Dim cs = ConfigurationManager.ConnectionStrings("Main").ConnectionString
Using cn As New SqlConnection(cs), _
cmd As New SqlCommand("SELECT TOP 20 NewsID, Title, PublishDate " & _
"FROM News ORDER BY PublishDate DESC", cn)
cn.Open()
' Reader закроется вместе с соединением после DataBind
rptNews.DataSource = cmd.ExecuteReader()
rptNews.DataBind()
End Using
End Sub
End ClassEval или Container.DataItem
Eval удобен и универсален: он находит поле по имени через отражение и работает с любым источником. Цена универсальности в скорости и в отсутствии проверки на этапе компиляции: опечатка в имени поля обнаружится только при открытии страницы.
Когда источник известен, можно обращаться к записи напрямую через Container.DataItem и приводить её к нужному типу. Для DataTable это DataRowView, для SqlDataReader это IDataRecord, для списка объектов сам класс. Начиная с ASP.NET 4.5 есть ещё более удобный вариант: свойство ItemType и выражение Item.Title с подсказками IntelliSense.
- Eval(«Title»): просто, но медленнее и без проверки имени;
- CType(Container.DataItem, DataRowView)(«Title»): быстро, тип источника фиксирован;
- ItemType=«NewsItem» и Item.Title: строгая типизация, ASP.NET 4.5 и новее.
ItemDataBound: логика для каждой записи
Выражения в шаблоне хороши для простого вывода. Как только появляется условие, например показать значок «новое» для записей младше трёх дней, лучше перенести логику в событие ItemDataBound. Оно вызывается для каждого элемента после привязки, и в нём доступны и данные записи, и контролы шаблона.
Важно проверять тип элемента: событие приходит и для заголовка, и для подвала, где DataItem пустой.
Protected Sub rptNews_ItemDataBound(sender As Object, _
e As RepeaterItemEventArgs) Handles rptNews.ItemDataBound
If e.Item.ItemType <> ListItemType.Item AndAlso _
e.Item.ItemType <> ListItemType.AlternatingItem Then Return
Dim rec = DirectCast(e.Item.DataItem, IDataRecord)
Dim published = CDate(rec("PublishDate"))
Dim badge = DirectCast(e.Item.FindControl("lblNew"), Label)
badge.Visible = (Date.Now - published).TotalDays < 3
End SubКнопки в строках и ItemCommand
Если в шаблон поместить Button или LinkButton с CommandName и CommandArgument, то при нажатии Repeater поднимет событие ItemCommand. Это удобнее, чем вешать обработчик на каждую кнопку: один метод разбирает все команды по имени, а аргумент несёт идентификатор записи, например CommandArgument='<%# Eval(«NewsID») %>'.
В обработчике проверьте e.CommandName, выполните действие и перепривяжите данные, чтобы список отразил изменения. Помните, что CommandArgument хранится в ViewState и приходит от клиента: права на удаление или изменение записи проверяйте на сервере, а не полагайтесь на то, что кнопка была видна.
Вложенные Repeater
Частая задача: категории, а под каждой список товаров. Вкладываем второй Repeater в ItemTemplate первого и наполняем его в ItemDataBound внешнего. Чтобы не делать запрос к базе на каждую категорию, загрузите обе таблицы в один DataSet и свяжите их через DataRelation. Тогда в ItemDataBound достаточно вызвать у DataRowView метод CreateChildView с именем связи и отдать результат во вложенный Repeater.
Такой подход выполняет два запроса вместо N+1 и остаётся читаемым. Если вложенных уровней больше двух, задумайтесь о том, чтобы собрать данные в иерархию объектов заранее и привязывать уже её.
Как это делают сегодня
Вопросы и ответы
Можно ли сделать постраничный вывод в Repeater?
Встроенного нет. Используйте PagedDataSource как прослойку между данными и Repeater или выбирайте нужную страницу прямо в SQL через OFFSET и FETCH.
Почему FindControl в ItemDataBound возвращает Nothing?
Скорее всего, событие пришло для HeaderTemplate или FooterTemplate. Проверяйте ItemType перед поиском контролов.
Что выбрать: Repeater или ListView?
ListView умеет то же, плюс редактирование, вставку, группировку и DataPager. Если нужна только своя разметка для вывода, Repeater проще и легче.