- Application name: [Your App Name]
- Page count: [N] .aspx pages, [N] .ascx user controls, [N] .master pages
- Primary controls used: [e.g., GridView, ListView, FormView, Login controls]
- Data access pattern: [e.g., SqlDataSource, EntityDataSource, ObjectDataSource, inline DbContext]
- Authentication: [e.g., ASP.NET Membership, ASP.NET Identity, Forms Auth, Windows Auth]
- Session state usage: [Heavy / Moderate / Light / None]
- Target framework: .NET [8/9/10]
When migrating Web Forms markup to Blazor using BWFC, apply these rules in order:
- Remove all
runat="server"attributes - Remove all
asp:tag prefixes (<asp:Button>→<Button>) - Remove
<%@ Page %>,<%@ Control %>,<%@ Master %>directives - Add
@page "/route"directive at the top of each page - Remove
<asp:Content>wrappers — the page body IS the content - Remove
<form runat="server">wrappers entirely
| Web Forms | Blazor |
|---|---|
<%: expression %> |
@(expression) |
<%= expression %> |
@(expression) |
<%# Item.Property %> |
@context.Property (in templates) |
<%# Eval("Property") %> |
@context.Property |
<%# Bind("Property") %> |
@bind-Value="context.Property" |
<%-- comment --%> |
@* comment *@ |
- Replace
ItemType="Namespace.Type"withTItem="Type" - When original used
SelectMethod: Preserve as delegate reference:SelectMethod="@service.GetItems"(BWFC'sDataBoundComponentauto-populatesItems) - When original used
DataSource/DataBind(): Replace withItems="propertyName"(collections) orDataItem="propertyName"(single record) - Load data in
OnInitializedAsync(only when usingItemsbinding):
@inject IYourService YourService
private List<YourType> items = new();
protected override async Task OnInitializedAsync()
{
items = await YourService.GetItemsAsync();
}
⚠️ Do NOT convertSelectMethodtoItems=binding — BWFC natively supportsSelectMethodas a delegate.
Add Context="Item" to all data templates:
<ItemTemplate Context="Item">
@Item.PropertyName
</ItemTemplate>| Web Forms | Blazor |
|---|---|
Page_Load(sender, e) |
OnInitializedAsync() |
if (!IsPostBack) |
Works AS-IS via WebFormsPageBase; optionally simplify |
Page_PreRender |
OnParametersSetAsync() |
Response.Redirect("~/path") |
Works AS-IS via ResponseShim (auto-strips ~/ and .aspx); or use NavigationManager.NavigateTo("/path") |
Page.Title = "X" |
Works AS-IS via WebFormsPageBase |
Session["key"] |
Works AS-IS via SessionShim; or use scoped service / ProtectedSessionStorage |
Request.Form["key"] |
Works AS-IS via FormShim + <WebFormsForm> component |
Request.QueryString["key"] |
Works AS-IS via RequestShim; or use [SupplyParameterFromQuery] |
Server.MapPath("~/path") |
Works AS-IS via ServerShim |
Cache["key"] |
Works AS-IS via CacheShim |
Page.ClientScript.RegisterStartupScript(...) |
Works AS-IS via ClientScriptShim |
ViewState["key"] |
Works AS-IS via ViewStateDictionary; or use component field |
// Web Forms
protected void Button_Click(object sender, EventArgs e) { }
// Blazor
private void Button_Click() { }- Replace
~/with/in all URL references - Replace
NavigateUrl="~/path"withNavigateUrl="/path"
<asp:ContentPlaceHolder ID="MainContent">→@Body- Add
@inherits LayoutComponentBaseto the layout file <asp:ScriptManager>→<ScriptManager />(renders nothing — correct)
| Control | Your Usage | Migration Notes |
|---|---|---|
| GridView | [e.g., ProductGrid on Products.aspx] | Items="products" + TItem="Product" |
| ListView | [e.g., CategoryList on Default.aspx] | Items="categories" + TItem="Category" |
| FormView | [e.g., ProductDetail on Detail.aspx] | DataItem="product" + TItem="Product" |
| TextBox | [e.g., Login form fields] | Add @bind-Text="model.Property" |
| DropDownList | [e.g., Category filter] | Bind Items + @bind-SelectedValue |
| Validators | [e.g., Login/Register forms] | Nearly 1:1 — wrap form in <EditForm> |
[Describe your data access approach]
[Describe your auth approach]
[Describe your session state approach]
[Describe your routing strategy]
| Original URL | Blazor Route | Page File |
|---|---|---|
/Default.aspx |
@page "/" |
Pages/Index.razor |
/Products.aspx |
@page "/Products" |
Pages/Products.razor |
/Products/Detail.aspx?id=5 |
@page "/Products/{ProductId:int}" |
Pages/ProductDetail.razor |
When Copilot encounters these attributes during migration, remove them without comment — they have no Blazor equivalent:
runat="server", AutoEventWireup, CodeBehind, CodeFile, Inherits (usually), EnableViewState, ViewStateMode, ValidateRequest, MaintainScrollPositionOnPostBack, ClientIDMode, EnableTheming, SkinID
- No ViewState — Blazor components maintain state in fields/properties.
ViewStateDictionaryshim available for compile-compat viaWebFormsPageBase.ViewState. - IsPostBack works AS-IS —
WebFormsPageBaseprovidesIsPostBack(SSR: checks HTTP method; Interactive: tracks render count).if (!IsPostBack)compiles and runs correctly. - No DataSource controls —
SqlDataSource,ObjectDataSource,EntityDataSourcemust be replaced with injected services. - Template Context — In Web Forms,
Itemis implicitly available. In BWFC, addContext="Item"on template elements. - ID Rendering — Blazor doesn't generate client IDs like
ctl00_MainContent_Grid. If CSS/JS targets these IDs, switch to class selectors. - String formatting — Replace
<%#: string.Format("{0:C}", Item.Price) %>with@Item.Price.ToString("C"). - TextBox TextMode — The value is
Multiline(notMultiLine) in BWFC. - Visibility —
Visible="false"works in BWFC, but prefer@if (condition)for dynamic visibility. - ClientScript migration —
Page.ClientScript.RegisterStartupScript()works viaClientScriptShim. For new code, preferIJSRuntime. - Form POST data — Use
<WebFormsForm OnSubmit="SetRequestFormData">to enableRequest.Form["key"]in interactive mode. - Server.Transfer has NO shim — There is no BWFC shim for
Server.Transfer(). UseNavigationManager.NavigateTo()instead. Server.Transfer does server-side URL rewriting which doesn't exist in Blazor. - Server.GetLastError/ClearError have NO shim — Use
ILoggerand middleware-based error handling (app.UseExceptionHandler). - ThreadAbortException is dead code — Web Forms throws
ThreadAbortExceptiononResponse.Redirect(url, true). Blazor does not. Anycatch (ThreadAbortException)blocks are dead code after migration — review and remove. - HttpContext.Current doesn't work in Blazor — Static
HttpContext.Current.Session["key"]must be replaced withSession["key"](on pages) or constructor-injectedSessionShim(in non-page classes). The CLI tool handles this replacement automatically.