Data Binding and Data Controls

This section documents ASP.NET Web Forms on .NET Framework 4.8.1, the last and permanent version of Web Forms — the page life cycle and postback model, ViewState and control state, server controls, validation controls, master pages and themes, data-bound controls, and the provider-based security model — as described by the official documentation at Microsoft Learn and the ASP.NET previous-versions archive, which are the reference these pages are written and verified against.

Web Forms receives security fixes only and has no forward path onto modern .NET (.NET Framework 4.8.1 is Microsoft’s last version of .NET Framework; Web Forms itself never shipped on .NET Core/.NET 5+). It remains supported for existing applications running on Windows but is not recommended for new development — see Choosing an ASP.NET Framework and Migrating to Modern ASP.NET for what that means in practice.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Data binding is Web Forms' declarative link between a data source and a repeating or detail UI, and it is the area where the framework’s "close to configuration, not code" promise is most visible — and where the binding-order pitfall covered at the end of this page is most commonly hit.

Data-binding expressions

<asp:Repeater ID="ProductsRepeater" runat="server">
    <ItemTemplate>
        <li>
            <%# Eval("Name") %> -- <%# Eval("Price", "{0:C}") %>
            <%-- Bind() is required (not Eval()) wherever the control writes the value back, e.g. FormView edit mode --%>
            <asp:TextBox runat="server" Text='<%# Bind("Quantity") %>' />
        </li>
    </ItemTemplate>
</asp:Repeater>

<asp:Repeater ID="CatalogRepeater" runat="server">
    <ItemTemplate>
        <%-- XPath() reads from an XmlDataSource-bound XPathNavigator item instead of a property bag --%>
        <span><%# XPath("@name") %></span>
    </ItemTemplate>
</asp:Repeater>

Eval is one-way (data → control); Bind is two-way and only valid inside a control that supports updating (a FormView/DetailsView edit template, or paired with a data source control’s Update command); XPath is Eval’s XML-specific sibling, valid when the bound item is an `XmlDataSource node. All three only evaluate when DataBind() actually runs on the containing control — they are not live bindings.

Data source controls

Data source controls are the declarative, no-code-behind way to wire a data-bound control to data:

Control Backs onto

SqlDataSource

A raw ADO.NET connection/command — SQL text or stored procedure, parameterized.

ObjectDataSource

An arbitrary business/data-access class following a method-name convention (SelectMethod, UpdateMethod, …​).

LinqDataSource

A LINQ to SQL DataContext.

EntityDataSource

An Entity Framework (EF6-era, ObjectContext/DbContext) model.

XmlDataSource

An XML document/file, queryable via XPath.

SiteMapDataSource

The site’s Web.sitemap hierarchy (see Master Pages, Themes, and Localization).

<asp:SqlDataSource ID="ProductsSource" runat="server"
    ConnectionString="<%$ ConnectionStrings:CatalogDb %>"
    SelectCommand="SELECT Id, Name, Price FROM Products WHERE CategoryId = @CategoryId"
    UpdateCommand="UPDATE Products SET Name=@Name, Price=@Price WHERE Id=@Id AND RowVersion=@original_RowVersion"
    ConflictDetection="CompareAllValues" OldValuesParameterFormatString="original_{0}">
    <SelectParameters>
        <asp:QueryStringParameter Name="CategoryId" QueryStringField="cat" Type="Int32" />
    </SelectParameters>
</asp:SqlDataSource>

<asp:ObjectDataSource ID="ProductsObjectSource" runat="server"
    TypeName="WebFormsApp.Data.ProductRepository"
    SelectMethod="GetByCategory" UpdateMethod="Update" InsertMethod="Insert" DeleteMethod="Delete" />

SqlDataSource embedding SQL directly in markup is functional but tightly couples presentation to persistence and is the pattern most Web Forms teams eventually replace with ObjectDataSource over a repository, or with model binding against a plain method.

Data-bound controls

Control Shape

GridView

Tabular, one row per record, built-in paging/sorting/editing/deleting via declarative fields.

DetailsView

A single record, field-per-row, with built-in paging between records and insert/edit/delete.

FormView

A single record, fully templated (no default rendering) — the templated counterpart to DetailsView.

Repeater

Fully templated, minimal built-in behavior — no default styling, paging, or editing; the most "just render this" of the group.

DataList

Templated, like Repeater, but supports a repeating flow/table layout (RepeatColumns, RepeatDirection) and basic edit-mode templates.

ListView / DataPager

Templated like Repeater/DataList but with full built-in paging (via a separate DataPager), sorting, insert/edit/delete — effectively `GridView’s flexibility without its fixed tabular rendering.

<asp:GridView ID="ProductsGrid" runat="server" DataSourceID="ProductsSource"
    AutoGenerateColumns="false" DataKeyNames="Id"
    AllowPaging="true" PageSize="20" AllowSorting="true"
    OnRowEditing="ProductsGrid_RowEditing">
    <Columns>
        <asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name" />
        <asp:TemplateField HeaderText="Price">
            <ItemTemplate><%# Eval("Price", "{0:C}") %></ItemTemplate>
            <EditItemTemplate><asp:TextBox runat="server" Text='<%# Bind("Price") %>' /></EditItemTemplate>
        </asp:TemplateField>
        <asp:CommandField ShowEditButton="true" ShowDeleteButton="true" />
    </Columns>
</asp:GridView>

TemplateField (used for Price above) is how any column beyond simple text — a formatted value, a nested control, conditional markup — is expressed; BoundField covers the plain-text case with less markup.

Paging, sorting, editing, inserting, deleting

GridView/DetailsView/ListView implement these against the bound IDataSource’s own capabilities: paging re-queries (`SelectCommand/SelectMethod) with new offset/size parameters, sorting appends an ORDER BY (SQL) or reflection-based sort (Object/Linq data sources), and edit/insert/delete map directly onto the data source’s UpdateCommand/InsertMethod/DeleteCommand. With a SqlDataSource/ObjectDataSource wired up, none of this requires code-behind at all beyond handling result/exception events for user feedback:

protected void ProductsSource_Updated(object sender, SqlDataSourceStatusEventArgs e)
{
    if (e.Exception != null)
    {
        e.ExceptionHandled = true;
        StatusLabel.Text = "Save failed: " + e.Exception.Message;
    }
}

Optimistic concurrency (ConflictDetection)

SqlDataSource/ObjectDataSource support optimistic concurrency via ConflictDetection="CompareAllValues" (compare every original column against current database state before applying the update, as in the SqlDataSource example above) or "OverwriteChanges" (last write wins, the default). CompareAllValues requires the data-bound control to keep the original row values around (via OldValuesParameterFormatString and, for GridView, DataKeyNames plus the row’s originally bound values) to compare against at update time, surfacing a conflict as a failed UpdateCommand/zero-rows-affected rather than a thrown ADO.NET concurrency exception.

Nested data-bound controls and the binding-order pitfall

Nesting one data-bound control inside another’s ItemTemplate (a Repeater of categories, each containing a nested Repeater of products) is common, but the outer control’s items — and therefore the inner controls — are only created once the outer control’s DataBind()/ItemDataBound has run. The classic mistake is calling .DataBind() on the inner control too early (e.g. in Page_Load, before the outer control has even created it) or forgetting to bind it at all inside ItemDataBound:

protected void CategoriesRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    if (e.Item.ItemType != ListItemType.Item && e.Item.ItemType != ListItemType.AlternatingItem)
    {
        return;
    }

    var category = (Category)e.Item.DataItem;
    var innerRepeater = (Repeater)e.Item.FindControl("ProductsRepeater");
    innerRepeater.DataSource = category.Products;
    innerRepeater.DataBind(); // must happen here -- the inner control does not exist before this fires
}

The general rule: bind the innermost control from within the outer control’s ItemDataBound (or the equivalent RowDataBound for GridView), never from Page_Load, because the inner control instance is created fresh on every outer-row bind and does not persist identity the way a page-level control does.