The migration script automatically transforms Web Forms page lifecycle methods (Page_Init, Page_Load, Page_PreRender) to their Blazor equivalents. Your page logic is preserved — only the method signatures change.
What it does:
- Renames Web Forms lifecycle methods to Blazor component lifecycle overrides
- Adjusts method signatures (removes
senderandEventArgsparameters) - Preserves method body logic unchanged
Why it matters:
Web Forms has a complex page lifecycle with specific events fired in order: Init → Load → PreRender → Render → Unload. Blazor has a different but analogous component lifecycle. The migration script handles the mapping automatically, but understanding the correspondence helps you validate the results and handle edge cases.
The following table shows how Web Forms lifecycle methods map to Blazor:
| Web Forms | Blazor | Timing | Notes |
|---|---|---|---|
Page_Init |
OnInitialized() |
Sync, runs once | Called when the component is first initialized |
Page_Load |
OnInitializedAsync() |
Async, runs once | Use for data loading; replaces the common if (!IsPostBack) pattern |
Page_PreRender |
OnAfterRenderAsync(bool firstRender) |
Async, after render | Guard with if (firstRender) for one-time logic |
Page_Unload |
Dispose() via IDisposable |
On component teardown | Implement IDisposable on the component |
!!! note
In Web Forms, Page_Load runs on every postback. In Blazor, OnInitializedAsync() runs only once when the component initializes. If your Page_Load contained if (!IsPostBack) guards, the migrated code is correct — Blazor's OnInitializedAsync() is inherently "first load only."
=== "Web Forms (Original)" ```csharp // Products.aspx.cs public partial class Products : System.Web.UI.Page { protected void Page_Init(object sender, EventArgs e) { // Initialize controls CategoryDropDown.DataSource = GetCategories(); CategoryDropDown.DataBind(); }
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// First load — fetch data
ProductGrid.DataSource = GetProducts();
ProductGrid.DataBind();
}
}
protected void Page_PreRender(object sender, EventArgs e)
{
// Update UI state before rendering
ItemCountLabel.Text = $"Showing {ProductGrid.Rows.Count} items";
}
}
```
=== "Blazor (After Migration)" ```csharp // Products.razor.cs public partial class Products : ComponentBase { protected override void OnInitialized() { // Initialize controls (was Page_Init) CategoryDropDown.DataSource = GetCategories(); CategoryDropDown.DataBind(); }
protected override async Task OnInitializedAsync()
{
// First load — fetch data (was Page_Load)
// No IsPostBack check needed — runs once
ProductGrid.DataSource = GetProducts();
ProductGrid.DataBind();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
// Update UI state after first render (was Page_PreRender)
ItemCountLabel.Text = $"Showing {ProductGrid.Rows.Count} items";
}
}
}
```
The migration script (bwfc-migrate.ps1) handles these transformations automatically:
- Renames methods —
Page_Init→OnInitialized,Page_Load→OnInitializedAsync,Page_PreRender→OnAfterRenderAsync - Removes parameters — Strips
(object sender, EventArgs e)from the signature - Adds override keyword — Changes
protected voidtoprotected override void(orasync Task) - Wraps PreRender body — Adds
if (firstRender) { ... }guard toOnAfterRenderAsync - Preserves method body — All logic inside the method is left unchanged
// INPUT: Web Forms
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
LoadProducts();
}
}
// OUTPUT: Blazor (automated)
protected override async Task OnInitializedAsync()
{
if (!IsPostBack)
{
LoadProducts();
}
}!!! tip
The IsPostBack property is provided by the BWFC WebFormsPage base class and always returns false in Blazor, making the guard effectively a no-op. You can safely remove it in a later cleanup phase.
After the automated migration, review the following:
If the original method body references the sender parameter, the automated transform will remove the parameter but leave the body reference, causing a compilation error:
// Web Forms — uses sender
protected void Page_Init(object sender, EventArgs e)
{
var page = (Page)sender; // ← References sender
page.Title = "Products";
}
// After migration — sender is gone, body breaks
protected override void OnInitialized()
{
var page = (Page)sender; // ❌ Compile error: 'sender' doesn't exist
page.Title = "Products";
}Fix: Replace sender references with direct property access or dependency injection:
protected override void OnInitialized()
{
// Use the component's own properties instead
PageTitle = "Products";
}Similarly, if the method body uses the e parameter:
// Web Forms — uses e
protected void Page_Load(object sender, EventArgs e)
{
LogEvent(e.ToString());
}
// Fix: Remove or replace the EventArgs reference
protected override async Task OnInitializedAsync()
{
LogEvent("Page initialized"); // Simplified
}Web Forms allows wiring multiple handlers to the same event. If your page has this pattern, consolidate into a single Blazor lifecycle method:
// Web Forms — multiple handlers (rare but possible)
this.Load += Page_Load;
this.Load += Page_LoadAdditional;
// Blazor — combine into one
protected override async Task OnInitializedAsync()
{
// Logic from Page_Load
LoadProducts();
// Logic from Page_LoadAdditional
LoadPromotions();
}If Page_Load calls async methods synchronously (common in Web Forms), the Blazor migration is an opportunity to improve:
// Web Forms — sync-over-async (anti-pattern)
protected void Page_Load(object sender, EventArgs e)
{
var products = GetProductsAsync().Result; // Blocking call
}
// Blazor — proper async (recommended cleanup)
protected override async Task OnInitializedAsync()
{
var products = await GetProductsAsync(); // Non-blocking
}For reference, here's how the lifecycle methods execute in each framework:
=== "Web Forms Lifecycle"
Page_PreInit ↓ Page_Init ← Component initialization ↓ Page_InitComplete ↓ Page_Load ← Data loading (every request) ↓ Page_LoadComplete ↓ [Event Handlers] ← Button clicks, grid commands, etc. ↓ Page_PreRender ← Final UI adjustments ↓ Page_Render ← HTML output ↓ Page_Unload ← Cleanup
=== "Blazor Component Lifecycle"
OnInitialized() ← Sync initialization (once) ↓ OnInitializedAsync() ← Async initialization (once) ↓ OnParametersSet() ← Parameters received ↓ OnParametersSetAsync() ← Async parameter processing ↓ [Render] ← HTML output ↓ OnAfterRender(first) ← Post-render logic ↓ OnAfterRenderAsync(first) ← Async post-render ↓ Dispose() ← Cleanup (via IDisposable)
- ✅
Page_Init→OnInitialized()— automatic - ✅
Page_Load→OnInitializedAsync()— automatic - ✅
Page_PreRender→OnAfterRenderAsync(firstRender)— automatic with guard ⚠️ Review method bodies forsenderoreparameter references⚠️ IsPostBackchecks are safe to leave (alwaysfalse) but can be removed later- 🔄 Consider converting sync-over-async patterns to proper
awaitcalls
See WebFormsPage for details on the IsPostBack shim and other page-level compatibility features.