Repeater コントロールの基本概念
ASP.NET Web Formsのサーバーコントロールの中でも、Repeaterはデータバインドコンテナとして設計された軽量な要素です。その主な目的は、データソースから取得したレコード集合を基に、開発者が定義したHTML構造を動的に展開することです。テーブルやグリッドのようなデフォルトのレンダリング機能を持たないため、完全にカスタマイズされたマークアップを記述可能ですが、その分テンプレートの明示的な定義が必須となります。
利用可能なテンプレート構造
レイアウトの制御には、以下の5つのテンプレートプロパティが用いられます。
ItemTemplate:データソースの各行に対して1回ずつレンダリングされる基本構造。主にレコードごとの内容を定義します。AlternatingItemTemplate:交互に表示されるデータ項目に適用される構造。偶数行と奇数行で背景色やスタイルを分けたい場合に利用されます。HeaderTemplateおよびFooterTemplate:リストの先頭と末尾に1回だけ出力される共通要素。リストを囲む<ul>タグや見出しなどを配置します。SeparatorTemplate:各項目の間に挿入される区切り要素。水平線や余白の制御に使用されます。
データバインドの実装パターン
Repeater にデータを紐付ける際、マークアップ側での宣言的設定と、コードビハインドでのプログラム的実装の2つのアプローチが一般的です。テンプレート内でカラムの値にアクセスするには、データバインド式 <%# Eval("ColumnName") %> を利用します。
<asp:Repeater ID="rptDeviceLog" runat="server">
<HeaderTemplate>
<table class="log-table">
<thead><tr><th>デバイス名</th><th>ステータス</th></tr></thead>
<tbody>
</HeaderTemplate>
<ItemTemplate>
<tr>
<td><%# Eval("DeviceName") %></td>
<td><%# Eval("Status") %></td>
</tr>
</ItemTemplate>
<FooterTemplate>
</tbody>
</table>
</FooterTemplate>
</asp:Repeater>
コードビハインド側では、任意のデータ型を割り当て後、DataBind() メソッドを実行してレンダリングをトリガーします。
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
var monitoringData = LoadSystemMetrics();
rptDeviceLog.DataSource = monitoringData;
rptDeviceLog.DataBind();
}
}
private List<dynamic> LoadSystemMetrics()
{
return new List<dynamic>
{
new { DeviceName = "Gateway-Alpha", Status = "Online" },
new { DeviceName = "Node-Beta", Status = "Standby" }
};
}
列挙処理の内部仕様 (IEnumerable / IEnumerator)
Repeater がデータソースを順次読み込む動作は、内部で IEnumerable および IEnumerator インターフェースの契約に依存しています。これらのインターフェースは、foreach 構文やデータバインドエンジンがコレクションを走査するための標準的なプロトコルを提供します。
IEnumerable は GetEnumerator() メソッドを定義しており、これを実装したクラスは列挙子を生成できます。返却される IEnumerator は、MoveNext()(次の要素へ移動)、Current(現在の要素の取得)、Reset()(先頭への巻き戻し)の3つのメンバーで構成されます。
以下に、これらインターフェースを実装してカスタムコレクションを定義する例を示します。
using System;
using System.Collections;
public class TelemetryRecord
{
public string StationId { get; }
public double Temperature { get; }
public TelemetryRecord(string id, double temp)
{
StationId = id;
Temperature = temp;
}
}
public class TelemetryRegistry : IEnumerable
{
private readonly TelemetryRecord[] _records;
public TelemetryRegistry(TelemetryRecord[] data)
{
_records = data;
}
public IEnumerator GetEnumerator()
{
return new TelemetryIterator(_records);
}
}
public class TelemetryIterator : IEnumerator
{
private readonly TelemetryRecord[] _source;
private int _index = -1;
public TelemetryIterator(TelemetryRecord[] source)
{
_source = source;
}
public bool MoveNext()
{
_index++;
return _index < _source.Length;
}
public void Reset()
{
_index = -1;
}
public object Current
{
get
{
if (_index < 0 || _index >= _source.Length)
throw new InvalidOperationException("列挙位置が無効です。");
return _source[_index];
}
}
}
public class ExecutionHost
{
public static void Main()
{
var rawData = new TelemetryRecord[]
{
new TelemetryRecord("S01", 22.4),
new TelemetryRecord("S02", 19.8),
new TelemetryRecord("S03", 25.1)
};
var registry = new TelemetryRegistry(rawData);
foreach (TelemetryRecord record in registry)
{
Console.WriteLine($"測定点 [{record.StationId}]: {record.Temperature}°C");
}
}
}
このようにインターフェースの契約を満たす実装を用意しておくことで、Repeater などのデータバインドコントロールや標準の走査構文が、データ型に依存せずに一貫して処理を実行できるようになります。