This page documents all 33 transforms applied by the webforms-to-blazor CLI tool. Transforms are applied in a fixed sequence to ensure correct output.
| Order | Name | Type | Category | Purpose |
|---|---|---|---|---|
| 10 | TodoHeader | Code-Behind | Meta | Inject TODO guidance header |
| 20 | PageDirective | Markup | Directive | Convert <%@ Page %> → @page |
| 30 | MasterDirective | Markup | Directive | Convert <%@ Master %> → Blazor layout |
| 40 | ControlDirective | Markup | Directive | Convert <%@ Control %> → @inherits |
| 50 | RegisterDirective | Markup | Directive | Handle <%@ Register %> for custom controls |
| 60 | ImportDirective | Markup | Directive | Convert <%@ Import %> → @using |
| 250 | MasterPageTransform | Markup | Markup | Convert <asp:ContentPlaceHolder> → @Body |
| 300 | ContentWrapperTransform | Markup | Markup | Wrap loose content in <div> if needed |
| 310 | FormWrapperTransform | Markup | Markup | Convert <form runat="server"> to Blazor form |
| 400 | ExpressionTransform | Markup | Markup | Convert <%: %>, <%= %> to @() |
| 510 | LoginViewTransform | Markup | Markup | Convert <asp:LoginView> → <AuthorizeView> |
| 520 | SelectMethodTransform | Markup | Markup | Flag SelectMethod/InsertMethod/etc. |
| 610 | AjaxToolkitPrefixTransform | Markup | Markup | Remove ajaxToolkit: prefixes |
| 620 | AspPrefixTransform | Markup | Markup | Remove asp: prefixes from controls |
| 700 | AttributeStripTransform | Markup | Markup | Remove runat="server", AutoEventWireup |
| 750 | EventWiringTransform | Markup | Markup | Convert OnClick="X" → OnClick="@X" |
| 780 | UrlReferenceTransform | Markup | Markup | Convert ~/ paths to / |
| 800 | TemplatePlaceholderTransform | Markup | Markup | Convert Item → context in templates |
| 810 | AttributeNormalizeTransform | Markup | Markup | Normalize attribute values (booleans, enums) |
| 820 | DataSourceIdTransform | Markup | Markup | Replace DataSourceID with Items binding |
| 30 | GetRouteUrlTransform | Code-Behind | Code-Behind | Flag Page.GetRouteUrl() calls |
| 50 | GetRouteUrlTransform | Markup | Markup | Flag <%: Page.GetRouteUrl() %> expressions |
| 400 | SessionDetectTransform | Code-Behind | Code-Behind | Detect Session/Cache, inject shim references |
| 410 | ViewStateDetectTransform | Code-Behind | Code-Behind | Detect ViewState usage, flag migration |
| 500 | IsPostBackTransform | Code-Behind | Code-Behind | Unwrap if (!IsPostBack) guards |
| 510 | PageLifecycleTransform | Code-Behind | Code-Behind | Convert Page_Load, Page_Init → Blazor lifecycle |
| 520 | EventHandlerSignatureTransform | Code-Behind | Code-Behind | Adapt event handler signatures |
| 30 | BaseClassStripTransform | Code-Behind | Code-Behind | Remove System.Web.UI.Page base class |
| 20 | UsingStripTransform | Code-Behind | Code-Behind | Remove Web Forms and ASP.NET using statements |
| 25 | ResponseRedirectTransform | Code-Behind | Code-Behind | Convert Response.Redirect() → NavigationManager.NavigateTo() |
| 40 | DataBindTransform | Code-Behind | Code-Behind | Flag DataBind() calls |
| 50 | UrlCleanupTransform | Code-Behind | Code-Behind | Clean URL literals in code |
Converts ASP.NET Page directives to Blazor routes.
Web Forms:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Product.aspx.cs" Inherits="MyApp.Product" %>
<h1>Products</h1>Output:
@page "/product"
@inherits MyApp.Product
<h1>Products</h1>What It Does:
- Extracts
Inheritsattribute →@inherits - Infers route from filename or
Urlattribute - Removes boilerplate attributes (Language, AutoEventWireup, CodeBehind)
- Adds TODO if custom routing logic is detected
Converts Master Page directives to Blazor layout components.
Web Forms:
<%@ Master Language="C#" CodeBehind="Site.master.cs" Inherits="MyApp.Site" %>Output:
@inherits LayoutComponentBase
<div>
@Body
</div>What It Does:
- Replaces
<%@ Master %>with@inherits LayoutComponentBase - Converts
<asp:ContentPlaceHolder>to@Body - Strips
runat="server"from head/form tags - Adds TODO for complex head content extraction
Converts User Control directives to Blazor component inheritance.
Web Forms:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="MenuBar.ascx.cs" Inherits="MyApp.MenuBar" %>Output:
@inherits MyApp.MenuBar
<!-- component markup -->What It Does:
- Extracts
Inheritsattribute →@inherits - Removes boilerplate attributes
- Preserves component markup
Handles custom control registration.
Web Forms:
<%@ Register Namespace="MyCompany.Controls" Assembly="MyCompany.Web" TagPrefix="my" %>
<my:CustomControl ID="ctrl1" runat="server" />Output:
@* TODO(bwfc-general): Custom control <my:CustomControl> — reference as Blazor component *@
<CustomControl ID="ctrl1" />What It Does:
- Removes Register directive (Blazor uses
@using) - Flags custom controls with TODO
- Allows developer to map to appropriate Blazor component
Converts Import directives to Blazor usings.
Web Forms:
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="MyApp.Services" %>Output:
@using System.Collections.Generic
@using MyApp.ServicesWhat It Does:
- Direct conversion to
@using - Preserves namespace imports
- Placed at top of component
Converts master page layout elements to Blazor.
Details:
- Replaces
<asp:ContentPlaceHolder>blocks with@Body - Strips
runat="server"from<head>and<form>tags - Injects
@inherits LayoutComponentBase - Adds TODO comment for head content review
Example:
<!-- Before -->
<head runat="server">
<title>Site Master</title>
</head>
<form runat="server">
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
Default content
</asp:ContentPlaceHolder>
</form>
<!-- After -->
@inherits LayoutComponentBase
@* TODO(bwfc-master-page): Review head content extraction for App.razor *@
<head>
<title>Site Master</title>
</head>
<form>
@Body
</form>Wraps loose content in a div if necessary.
Purpose: Blazor requires a single root element. Wraps text nodes and mixed content.
Converts Web Forms form tags to Blazor EditForm or plain HTML form.
Web Forms:
<form runat="server">
<asp:TextBox ID="txtName" runat="server" />
<asp:Button Text="Submit" OnClick="Submit_Click" runat="server" />
</form>Output (with EditContext):
<EditForm Model="@Model" OnValidSubmit="Submit_Click">
<DataAnnotationsValidator />
<TextBox @bind-Value="@Model.Name" />
<Button Text="Submit" />
</EditForm>Converts Web Forms expression syntax to Blazor.
| Pattern | Web Forms | Blazor |
|---|---|---|
| Output | <%: expression %> |
@(expression) |
| Code | <%= expression %> |
@expression |
| Data-Bind | <%# Bind("Property") %> |
@bind="Model.Property" |
| Data-Eval | <%# Eval("Property") %> |
@Item.Property |
Example:
<!-- Before -->
<h1><%: Model.Title %></h1>
<p><%= GetDescription() %></p>
<input value="<%# Bind("Email") %>" />
<!-- After -->
<h1>@(Model.Title)</h1>
<p>@GetDescription()</p>
<input @bind="Model.Email" />Converts LoginView to Blazor AuthorizeView.
Web Forms:
<asp:LoginView runat="server">
<AnonymousTemplate>
<p><asp:LoginStatus runat="server" /></p>
</AnonymousTemplate>
<LoggedInTemplate>
<p>Welcome, <asp:LoginName runat="server" />!</p>
</LoggedInTemplate>
</asp:LoginView>Output:
<AuthorizeView>
<NotAuthorized>
<p><LoginStatus /></p>
</NotAuthorized>
<Authorized>
<p>Welcome, <LoginName />!</p>
</Authorized>
</AuthorizeView>Complex RoleGroups are flagged with TODO:
<!-- Input -->
<asp:LoginView runat="server">
<RoleGroups>
<asp:RoleGroup Roles="Admin">
<LoggedInTemplate>Admin panel</LoggedInTemplate>
</asp:RoleGroup>
</RoleGroups>
</asp:LoginView>
<!-- Output -->
@* TODO(bwfc-identity): Convert RoleGroups to policy-based AuthorizeView *@Flags data source method attributes for conversion.
Purpose: Detects SelectMethod, InsertMethod, UpdateMethod, DeleteMethod attributes and adds TODO comments.
Example:
<!-- Input -->
<GridView DataSource='<%# Products %>' AllowPaging="true" runat="server">
<!-- Output -->
@* TODO(bwfc-datasource): Wire Items binding and implement IProductsDataService *@
<GridView Items="@Products" AllowPaging="true">Removes AJAX Control Toolkit prefixes.
Web Forms:
<ajaxToolkit:TabContainer runat="server">
<ajaxToolkit:TabPanel HeaderText="Tab 1" runat="server">
Content
</ajaxToolkit:TabPanel>
</ajaxToolkit:TabContainer>Output:
<TabContainer>
<TabPanel HeaderText="Tab 1">
Content
</TabPanel>
</TabContainer>Removes asp: prefix from all ASP.NET server controls.
Web Forms:
<div>
<asp:Button ID="btnSubmit" Text="Submit" CssClass="btn-primary" runat="server" />
<asp:TextBox ID="txtName" placeholder="Enter name" runat="server" />
<asp:Label ID="lblStatus" runat="server" />
</div>Output:
<div>
<Button ID="btnSubmit" Text="Submit" CssClass="btn-primary" />
<TextBox ID="txtName" placeholder="Enter name" />
<Label ID="lblStatus" />
</div>Removes Web Forms-specific attributes.
Removes:
runat="server"AutoEventWireup="true|false"EnableEventValidation="true|false"ViewStateMode="Enabled|Disabled|Inherit"
Example:
<!-- Before -->
<asp:TextBox ID="txt1" runat="server" AutoEventWireup="true" ViewStateMode="Disabled" />
<!-- After -->
<TextBox ID="txt1" />Converts Web Forms event handler syntax to Blazor.
Web Forms:
<asp:Button ID="btnSubmit" Text="Submit" OnClick="Submit_Click" runat="server" />
<asp:TextBox ID="txtEmail" OnTextChanged="Email_Changed" AutoPostBack="true" runat="server" />Output:
<Button ID="btnSubmit" Text="Submit" OnClick="@Submit_Click" />
<TextBox ID="txtEmail" OnInput="@Email_Changed" />Converts ASP.NET virtual paths to absolute URLs.
Handles 8 URL attributes:
| Attribute | Example |
|---|---|
NavigateUrl |
~/products/list → /products/list |
ImageUrl |
~/images/logo.png → /images/logo.png |
PostBackUrl |
~/checkout → /checkout |
ToolTip |
~/help in URL context → /help |
HRef |
~/page → /page |
Src |
~/scripts/app.js → /scripts/app.js |
DataSourceID (partial) |
Handled by DataSourceIdTransform |
onclick (URLs) |
JavaScript URLs updated |
Example:
<!-- Before -->
<asp:HyperLink NavigateUrl="~/products/list" Text="Products" runat="server" />
<asp:Image ImageUrl="~/images/logo.png" runat="server" />
<script src="~/scripts/app.js"></script>
<!-- After -->
<HyperLink NavigateUrl="/products/list" Text="Products" />
<Image ImageUrl="/images/logo.png" />
<script src="/scripts/app.js"></script>Converts Item placeholder to context in Blazor templates.
Blazor uses context to reference the template context variable. Web Forms uses Item.
Example:
<!-- Before (GridView template) -->
<ItemTemplate>
<td><%# Item.Name %></td>
<td><%# Item.Price %></td>
</ItemTemplate>
<!-- After -->
<ItemTemplate>
<td>@context.Name</td>
<td>@context.Price</td>
</ItemTemplate>Normalizes attribute values to Blazor syntax.
Converts:
Visible="true"→ no attribute (show by default)Visible="false"→style="display:none"- Boolean
true/false→true/false - Enum strings → proper C# enum syntax
Example:
<!-- Before -->
<asp:Panel Visible="<%# ShowPanel %>" BackColor="Red" ForeColor="White" runat="server">
<!-- After -->
<Panel style="@(ShowPanel ? "" : "display:none")" style="background-color: red; color: white;">Replaces DataSourceID with Items binding and scaffolds data properties.
Web Forms:
<asp:GridView DataSourceID="SqlDataSource1" runat="server" />
<asp:SqlDataSource ID="SqlDataSource1" SelectCommand="SELECT * FROM Products" runat="server" />Output:
@* TODO(bwfc-datasource): Implement IProductsDataService to replace SqlDataSource *@
<GridView Items="@ProductsData" />
@code {
private List<Product> ProductsData { get; set; }
protected override async Task OnInitializedAsync()
{
// TODO(bwfc-datasource): Load ProductsData from service
}
}Injects migration guidance header at the top of code-behind files.
Output: (injected at file top)
// =============================================================================
// TODO(bwfc-general): This code-behind was copied from Web Forms and needs manual migration.
//
// Common transforms needed (use the BWFC Copilot skill for assistance):
// TODO(bwfc-lifecycle): Page_Load / Page_Init → OnInitializedAsync / OnParametersSetAsync
// TODO(bwfc-lifecycle): Page_PreRender → OnAfterRenderAsync
// TODO(bwfc-ispostback): IsPostBack checks → remove or convert to state logic
// TODO(bwfc-viewstate): ViewState usage → component [Parameter] or private fields
// TODO(bwfc-session-state): Session/Cache access → inject IHttpContextAccessor or use DI
// TODO(bwfc-navigation): Response.Redirect → NavigationManager.NavigateTo
// TODO(bwfc-general): Event handlers (Button_Click, etc.) → convert to Blazor event callbacks
// TODO(bwfc-datasource): Data binding (DataBind, DataSource) → component parameters or OnInitialized
// TODO(bwfc-general): ScriptManager code-behind references → remove (Blazor handles updates)
// TODO(bwfc-general): UpdatePanel markup preserved by BWFC (ContentTemplate supported) — remove only code-behind API calls
// TODO(bwfc-general): User controls → Blazor component references
// =============================================================================Removes Web Forms and ASP.NET using statements.
Removes:
System.Web.*System.Web.UI.*Microsoft.AspNet.*- AJAX Control Toolkit namespaces
Example:
// Before
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.AspNet.Identity;
// After
using System;
// (Web Forms usings removed — use BWFC and ASP.NET Core namespaces)Removes Web Forms base classes and replaces with Blazor ComponentBase.
Example:
// Before
public partial class Product : System.Web.UI.Page
{
// ...
}
// After
public partial class Product : ComponentBase
{
// ...
}Converts Response.Redirect() to NavigationManager.NavigateTo().
Web Forms:
private void CheckoutButton_Click(object sender, EventArgs e)
{
Response.Redirect("~/checkout");
}Output:
private void CheckoutButton_Click(object sender, EventArgs e)
{
NavigationManager.NavigateTo("/checkout");
}Flags Page.GetRouteUrl() calls for conversion guidance.
Example:
// Before
string url = Page.GetRouteUrl("ProductRoute", new { id = 123 });
// After
@* TODO(bwfc-routing): Page.GetRouteUrl() → Use NavigationManager.GetUriByPage() or Router.TryResolveRoute() *@
string url = Page.GetRouteUrl("ProductRoute", new { id = 123 });Detects Session/Cache usage and auto-wires SessionShim/CacheShim.
Web Forms:
public class CheckoutPage : Page
{
private void LoadCart()
{
string cartId = (string)Session["CartId"];
var items = (List<CartItem>)Cache["ProductList"];
}
}Output:
// --- Session State Migration ---
// TODO(bwfc-session-state): SessionShim auto-wired via [Inject] — Session["CartId"] calls compile against the shim's indexer.
// Session keys found: CartId
// Options for long-term replacement:
// (1) ProtectedSessionStorage (Blazor Server) — persists across circuits
// (2) Scoped service via DI — lifetime matches user circuit
// (3) Cascading parameter from a root-level state provider
// See: https://learn.microsoft.com/aspnet/core/blazor/state-management
// --- Cache Migration ---
// TODO(bwfc-session-state): CacheShim auto-wired via [Inject] — Cache["ProductList"] calls compile against the shim's indexer.
// Cache keys found: ProductList
// CacheShim wraps IMemoryCache — items are per-server, not distributed.
// For distributed caching, consider IDistributedCache.
public class CheckoutPage : ComponentBase
{
[Inject] private SessionShim Session { get; set; }
[Inject] private CacheShim Cache { get; set; }
private void LoadCart()
{
string cartId = (string)Session["CartId"];
var items = (List<CartItem>)Cache["ProductList"];
}
}Detects ViewState usage and flags migration strategy.
Example:
// Before
ViewState["CurrentPage"] = 1;
int page = (int)(ViewState["CurrentPage"] ?? 0);
// After
@* TODO(bwfc-viewstate): ViewState["CurrentPage"] — migrate to component private field or [Parameter] *@
// Options:
// (1) Private field (simple state within component):
// private int CurrentPage { get; set; } = 1;
// (2) [Parameter] (state passed from parent):
// [Parameter] public int CurrentPage { get; set; }
// (3) Cascading parameter (shared across hierarchy):
// [CascadingParameter] private int CurrentPage { get; set; }
ViewState["CurrentPage"] = 1;
int page = (int)(ViewState["CurrentPage"] ?? 0);Unwraps IsPostBack guards and extracts postback logic.
Web Forms:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
LoadInitialData();
}
else
{
HandlePostBack();
}
}Output:
protected async Task OnInitializedAsync()
{
LoadInitialData(); // Guard unwrapped — runs on first load only
}
private void HandlePostBack()
{
// Postback logic extracted
}Converts Web Forms lifecycle methods to Blazor.
| Web Forms | Blazor |
|---|---|
Page_Load |
OnInitializedAsync |
Page_Init |
OnInitializedAsync / Constructor |
Page_PreRender |
OnAfterRenderAsync |
Page_Unload |
Dispose |
Example:
// Before
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
LoadProducts();
}
protected void Page_PreRender(object sender, EventArgs e)
{
UpdateStatus();
}
// After
protected override async Task OnInitializedAsync()
{
await LoadProducts();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
UpdateStatus();
}Adapts Web Forms event handler signatures to Blazor.
Web Forms:
protected void SubmitButton_Click(object sender, EventArgs e)
{
// handle click
}
protected void GridView_RowCommand(object sender, GridViewCommandEventArgs e)
{
// handle row command
}Output:
// Event callback wired in markup: OnClick="@SubmitButton_Click"
private Task SubmitButton_Click()
{
// handle click (no sender/e parameters)
return Task.CompletedTask;
}
// For GridView row commands: OnRowCommand="@GridView_RowCommand"
private Task GridView_RowCommand(GridViewCommandEventArgs e)
{
// e is preserved for complex data grid scenarios
return Task.CompletedTask;
}Flags DataBind() calls for conversion.
Example:
// Before
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
ProductGrid.DataBind();
}
}
// After
@* TODO(bwfc-datasource): DataBind() → Remove (Blazor renders via @binding) or use StateHasChanged() after data load *@
protected override async Task OnInitializedAsync()
{
// Load data and StateHasChanged() will re-render
Products = await LoadProductsAsync();
}Cleans URL string literals in code.
Example:
// Before
string redirectUrl = "~/products/list";
string imageUrl = "~/images/logo.png";
// After
string redirectUrl = "/products/list";
string imageUrl = "/images/logo.png";- During migration: Open this page alongside your migrated code to understand what changed
- After migration: Use "Transform Reference" links in TODO comments to jump to specific transform details
- For troubleshooting: If output doesn't match expectations, check the transform order — earlier transforms affect later ones
- TODO Categories — Learn about TODO comment categories for L2 automation
- Migration Report — Understand how to read the migration report
- Back to CLI Overview — Return to main CLI documentation