Getting Started with ASP.NET 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. |
This page orients a developer inside an ASP.NET Web Forms codebase: how the project is organized, how a page is put together, and how a request becomes rendered HTML before the page life cycle itself (covered next) takes over.
Web Site Project vs. Web Application Project
Visual Studio has offered two different project models for Web Forms since .NET Framework 2.0, and both are still current on .NET Framework 4.8.1:
| Web Site Project (WSP) | Web Application Project (WAP) |
|---|---|
No |
Standard MSBuild |
Each page compiles individually, on first request or with |
The whole project compiles into one assembly before deployment. |
Easy to edit a single page directly on a production server. |
Cannot edit a single file in place without a full rebuild/redeploy. |
Weak refactoring support; partial classes are inferred at compile time. |
Full Visual Studio refactoring, since code-behind partial classes are explicit compiled members. |
Good fit for simple sites maintained by many small edits. |
Good fit for team development with source control, unit tests, and CI builds. |
New Web Forms development (to the extent there is any) should use the Web Application Project model — it is the one that behaves like every other MSBuild-based .NET project and is what the rest of this section assumes. See Web Application Projects versus Web Site Projects for the full comparison.
<!-- WebFormsApp.csproj (Web Application Project, .NET Framework 4.8.1) -->
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetFrameworkVersion>v4.8.1</TargetFrameworkVersion>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<UseIISExpress>true</UseIISExpress>
</PropertyGroup>
<ItemGroup>
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
</ItemGroup>
</Project>
The {349c5851-…} project-type GUID marks this as an ASP.NET Web Application in the solution; it is what
tells Visual Studio to compile the whole site into one assembly and enable the WAP-specific tooling.
.aspx / .aspx.cs anatomy
A Web Forms page is two files working together: the .aspx markup file and its code-behind class.
<%-- Default.aspx --%>
<%@ Page Title="Home" Language="C#" AutoEventWireup="true"
CodeBehind="Default.aspx.cs" Inherits="WebFormsApp.Default" %>
<!DOCTYPE html>
<html>
<head runat="server">
<title><%: Page.Title %></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label ID="GreetingLabel" runat="server" Text="Hello" />
<asp:Button ID="SayHiButton" runat="server" Text="Say hi"
OnClick="SayHiButton_Click" />
</div>
</form>
</body>
</html>
// Default.aspx.cs
namespace WebFormsApp
{
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
GreetingLabel.Text = "Hello, first visit!";
}
}
protected void SayHiButton_Click(object sender, EventArgs e)
{
GreetingLabel.Text = "Hello, " + DateTime.Now.ToLongTimeString();
}
}
}
The code-behind class is partial; Visual Studio generates a second partial class,
Default.aspx.designer.cs, containing a protected field per runat="server" control (GreetingLabel,
SayHiButton above) so they can be referenced by name from code-behind without manual declarations. Every
.aspx page ultimately derives from System.Web.UI.Page, itself a System.Web.UI.Control — the base of the
control-tree model covered in Server Controls.
Code-behind vs. inline code
Inline code — <% … %> render blocks and <%= … %> / <%: … %> expressions directly in the .aspx
markup — still works and predates code-behind:
<p>Server time: <%: DateTime.Now.ToString("T") %></p>
<% for (int i = 0; i < 3; i++) { %>
<li>Item <%: i %></li>
<% } %>
<%: expr %> (added in ASP.NET 4) HTML-encodes the result automatically; the older <%= expr %> does not and
is an XSS risk against untrusted data. Prefer <%: %> (or Server.HtmlEncode) whenever the value did not come
from a trusted, already-encoded source. Code-behind is preferred for anything beyond trivial formatting: it
gets full IntelliSense, step debugging, and unit-testable methods, none of which apply cleanly to inline code
blocks embedded in markup.
The @Page, @Control, @Register, and @Import directives
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs"
Inherits="WebFormsApp.Default" MasterPageFile="~/Site.master"
EnableViewState="true" %>
<%@ Import Namespace="System.Text" %>
<%@ Register TagPrefix="uc" TagName="Greeting" Src="~/Controls/Greeting.ascx" %>
<%@ Register TagPrefix="cc" Namespace="WebFormsApp.Controls" Assembly="WebFormsApp" %>
<uc:Greeting ID="Greeting1" runat="server" />
-
@Page— one per.aspxfile; declares the language, the code-behind link, the base class, the master page, and page-level defaults such asEnableViewStateandValidateRequest. -
@Control— the@Pageequivalent for a.ascxuser control (see User and Custom Controls). -
@Import— brings a namespace into scope for inline code, equivalent to a C#using. -
@Register— makes a user control (Src=) or a compiled custom-control assembly (Namespace=/Assembly=) available under a markup tag prefix.
See ASP.NET Web Forms Page Syntax for the full directive and syntax reference.
The App_* special folders
ASP.NET reserves several top-level folder names and compiles or serves their contents specially:
| Folder | Purpose |
|---|---|
|
Classes compiled automatically into a dynamic assembly, referenceable from any page without an
explicit project reference — helper classes, typed |
|
Application data files — |
|
Skin files ( |
|
|
|
Per-page |
|
|
AutoEventWireup
AutoEventWireup="true" (the WAP template default) lets ASP.NET bind well-known page-lifecycle event handler
methods — Page_Load, Page_Init, Page_PreRender, and so on — purely by method name, with no explicit
+= subscription required. Setting it to "false" requires wiring events explicitly, typically by overriding
OnLoad/OnInit or subscribing in the constructor:
// Equivalent to AutoEventWireup picking up Page_Load automatically:
public partial class Default : System.Web.UI.Page
{
public Default()
{
Load += Page_Load; // explicit wiring, needed when AutoEventWireup="false"
}
private void Page_Load(object sender, EventArgs e) { /* ... */ }
}
Explicit wiring is marginally faster (it skips a reflection-based lookup on the first page hit per type) and
is required if a base class also defines a Page_Load-named method that should not be auto-bound; almost all
Web Forms code leaves AutoEventWireup="true" and relies on the naming convention.
Visual Studio designer and tooling
Visual Studio’s Web Forms Designer offers a WYSIWYG/Split/Source view over .aspx markup, drag-and-drop from
the Toolbox (which inserts <asp:Control runat="server" …/> markup and a matching designer field), and the
Properties window bound to whichever control is selected. IntelliSense works over both markup (tag/attribute
completion for registered controls) and code-behind. Debugging a Web Forms page is ordinary .NET debugging — set a breakpoint in Page_Load or an event handler, run under IIS Express, and step through exactly as in any
other .NET Framework project; the debugger has no special awareness of the page life cycle beyond what a normal
call stack shows.
Targeting .NET Framework 4.8.1
Web Forms features) and is the effective ceiling for TargetFrameworkVersion in any Web Forms project today.
There is no reason to target an older 4.x version for new work — 4.8.1 is fully backward compatible with code
written against 4.0 through 4.8, and picking it gets the latest available runtime and JIT fixes with no source
changes required. See the ASP.NET Web Forms documentation
on Microsoft Learn and .NET
Framework versions and dependencies for compatibility details.