Model Binding and Modern Web Forms
|
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. |
ASP.NET 4.5 (2012) added a set of features that brought Web Forms conceptually closer to MVC without changing
its underlying page/control model — strongly typed, method-based model binding in place of
SqlDataSource/ObjectDataSource markup, DataAnnotations validation, cleaner URLs, and async page
processing. These are the last substantial additions Web Forms received.
ItemType and strongly typed data controls
Setting ItemType on a data-bound control gives its templates a strongly typed Item instead of an untyped
Eval("Name") lookup, with full IntelliSense and compile-time checking:
<asp:ListView ID="ProductsList" runat="server" ItemType="WebFormsApp.Models.Product"
SelectMethod="GetProducts">
<ItemTemplate>
<%-- Item is a Product, not object -- Item.Name is checked at compile time --%>
<li><%#: Item.Name %> -- <%#: Item.Price.ToString("C") %></li>
</ItemTemplate>
</asp:ListView>
<%#: %> combines strongly typed binding with automatic HTML encoding, the data-binding analogue of <%: %>.
Model binding: SelectMethod/UpdateMethod/InsertMethod/DeleteMethod
Instead of a SqlDataSource/ObjectDataSource control, a data-bound control can point directly at plain
methods on the page (or a referenced class), each following simple parameter-binding conventions:
<asp:GridView ID="ProductsGrid" runat="server" ItemType="WebFormsApp.Models.Product"
SelectMethod="GetProducts" UpdateMethod="UpdateProduct" DeleteMethod="DeleteProduct"
DataKeyNames="Id" AllowPaging="true" PageSize="20">
<Columns>
<asp:BoundField DataField="Name" HeaderText="Name" />
<asp:BoundField DataField="Price" HeaderText="Price" />
<asp:CommandField ShowEditButton="true" ShowDeleteButton="true" />
</Columns>
</asp:GridView>
public partial class Products : System.Web.UI.Page
{
private readonly ProductRepository _repository = new ProductRepository();
public IQueryable<Product> GetProducts()
{
return _repository.All(); // paging/sorting are applied by the framework against the IQueryable
}
public void UpdateProduct(int id, Product product)
{
_repository.Update(id, product);
}
public void DeleteProduct(int id) => _repository.Delete(id);
}
This is a direct, no-markup-configuration substitute for `ObjectDataSource’s method-name conventions — the method itself is the data source, resolved and invoked by the model-binding infrastructure per request.
Value providers
Method parameters are populated from named sources using attributes, mirroring MVC’s model-binding attributes of the same era:
| Attribute | Binds from |
|---|---|
|
|
|
Another control’s value on the page (e.g. a filter |
|
|
|
|
|
|
|
The current user’s |
|
Values from a matched route (see "Friendly URLs" below) |
public IQueryable<Product> GetProducts([QueryString("cat")] int? categoryId,
[Control("MinPriceBox")] decimal? minPrice)
{
var query = _repository.All();
if (categoryId.HasValue) query = query.Where(p => p.CategoryId == categoryId);
if (minPrice.HasValue) query = query.Where(p => p.Price >= minPrice);
return query;
}
DataAnnotations validation on Web Forms
Model classes decorated with System.ComponentModel.DataAnnotations attributes are validated automatically
when bound through UpdateMethod/InsertMethod, surfaced via Page.ModelState (Web Forms' equivalent of
MVC’s ModelState) alongside, not instead of, the
validator controls:
public class Product
{
[Required, StringLength(100)]
public string Name { get; set; }
[Range(0, 100000)]
public decimal Price { get; set; }
}
public void UpdateProduct(int id, Product product)
{
if (!ModelState.IsValid) { return; } // DataAnnotations failures land here
_repository.Update(id, product);
}
Friendly URLs
The Microsoft.AspNet.FriendlyUrls NuGet package (installed by default in the ASP.NET 4.5 Web Forms project
template) layers extension-less, parameterized URLs on top of System.Web.Routing with almost no manual route
registration:
// RouteConfig.cs
public static class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
var settings = new FriendlyUrlSettings { AutoRedirectMode = RedirectMode.Permanent };
routes.EnableFriendlyUrls(settings);
}
}
/Products.aspx becomes reachable as /Products; segment-style parameters (/Products/5) map onto
PageRouteData on the target page, read the same way as the RouteTable.Routes.MapPageRoute approach in
Master Pages, Themes, and Localization,
but without hand-writing each route.
Bundling and minification
System.Web.Optimization (the Microsoft.AspNet.Web.Optimization NuGet package) combines and minifies
script/CSS files into a small number of cacheable requests:
// BundleConfig.cs
public static class BundleConfig
{
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/jquery").Include("~/Scripts/jquery-{version}.js"));
bundles.Add(new StyleBundle("~/Content/css").Include("~/Content/site.css"));
}
}
<%: Scripts.Render("~/bundles/jquery") %>
<%: Styles.Render("~/Content/css") %>
This is the same bundling infrastructure ASP.NET MVC 5 uses, since System.Web.Optimization is a
System.Web-level library shared by both.
Async page methods
RegisterAsyncTask lets a page await I/O-bound work without blocking a thread-pool thread for the whole
request, provided the page opts in with Async="true":
<%@ Page Async="true" Language="C#" CodeBehind="ProductReport.aspx.cs" Inherits="WebFormsApp.ProductReport" %>
protected void Page_Load(object sender, EventArgs e)
{
RegisterAsyncTask(new PageAsyncTask(LoadReportAsync));
}
private async Task LoadReportAsync()
{
ReportData = await _reportService.GenerateAsync(); // runs during the page's async point, before PreRender
}
PageAsyncTask also accepts the older Begin/End APM delegate pair for legacy async patterns; the Task-based
overload above is the practical choice for any code written against modern async/await. See
ASP.NET 4.5 Model Binding for
Web Forms and Async
support for Web Forms.