ASP.NET Repeater и DataBind: примеры на Visual Basic

РазделДанные и привязкаУровеньНовичок Чтение8 минОпубликовано ТегиASP.NETRepeaterVB.NETData Binding

Repeater самый лёгкий из списковых контролов ASP.NET: он не генерирует ни одного лишнего тега и выводит ровно ту разметку, которую вы описали в шаблонах. Разбираем привязку и события на примерах VB.NET.

Главное. Repeater даёт полный контроль над разметкой: данные привязываются из кода при первой загрузке, простой вывод делается выражениями в шаблоне, а условная логика и команды обрабатываются в ItemDataBound и ItemCommand.

Чем Repeater отличается от GridView

GridView рисует таблицу, умеет сортировать, листать и редактировать. За это приходится платить: разметка получается тяжёлой, а стилизовать её под дизайн сайта неудобно. Repeater устроен противоположно. Он просто повторяет шаблон для каждой записи источника и больше ничего не делает: ни таблиц, ни span, ни встроенного оформления.

Поэтому Repeater выбирают, когда нужна полностью своя разметка: список новостей в виде ul, карточки товаров на div с CSS-классами, меню, лента комментариев. Всё, что сверх простого вывода, например постраничность или редактирование, придётся писать самому, но для витринных списков это обычно и не нужно.

Шаблоны Repeater

Разметка задаётся набором шаблонов. ItemTemplate обязателен и описывает одну запись. AlternatingItemTemplate, если задан, используется для каждой второй записи. HeaderTemplate и FooterTemplate выводятся один раз до и после списка, а SeparatorTemplate между элементами.

Внутри шаблонов значения полей вставляются выражениями привязки. Для вывода текста используйте форму с двоеточием, которая кодирует HTML: так название товара с символом «<» не сломает страницу.

News.aspxASPX
<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>
Синтаксис <%#: появился в ASP.NET 4. В более старых версиях оборачивайте значение в Server.HtmlEncode.

Привязка данных из кода

Repeater умеет брать данные из контрола источника через DataSourceID, но чаще его наполняют из code-behind. В DataSource можно передать почти что угодно перечислимое: DataTable, DataView, SqlDataReader, List(Of T) или массив. Затем вызывается DataBind, и шаблоны раскрываются.

Привязку выполняйте только при первой загрузке страницы. При postback Repeater восстановит элементы из ViewState сам, а повторный DataBind сбросит состояние вложенных контролов, например отмеченные флажки.

News.aspx.vbVB.NET
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 Class

Eval или 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 пустой.

News.aspx.vbVB.NET
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 и остаётся читаемым. Если вложенных уровней больше двух, задумайтесь о том, чтобы собрать данные в иерархию объектов заранее и привязывать уже её.

Как это делают сегодня

В Razor Pages и Blazor отдельный контрол для этого не нужен: обычный цикл @foreach по коллекции даёт ту же свободу разметки, а строгая типизация модели проверяет имена полей при компиляции. Вложенные списки строятся вложенным циклом, а данные заранее собираются в иерархию через EF Core с Include, без отдельного запроса на каждую группу.

Вопросы и ответы

Можно ли сделать постраничный вывод в Repeater?

Встроенного нет. Используйте PagedDataSource как прослойку между данными и Repeater или выбирайте нужную страницу прямо в SQL через OFFSET и FETCH.

Почему FindControl в ItemDataBound возвращает Nothing?

Скорее всего, событие пришло для HeaderTemplate или FooterTemplate. Проверяйте ItemType перед поиском контролов.

Что выбрать: Repeater или ListView?

ListView умеет то же, плюс редактирование, вставку, группировку и DataPager. Если нужна только своя разметка для вывода, Repeater проще и легче.

Читайте также