The migration script automatically transforms Web Forms event handler signatures to Blazor-compatible equivalents. The (object sender, EventArgs e) pattern is stripped or simplified based on the EventArgs type, preserving your handler logic unchanged.
What it does:
- Strips
(object sender, EventArgs e)parameters from standard event handlers - Strips only the
senderparameter when specializedEventArgsare used (e.g.,GridViewCommandEventArgs) - Preserves all method body logic unchanged
Why it matters:
Every Web Forms event handler follows the (object sender, EventArgs e) convention. Blazor event callbacks use different signatures — parameterless for simple actions, or with a single event args parameter for specialized events. The migration script automates this transformation for the common cases, but you should understand the rules for manual review.
The migration follows two simple rules based on the EventArgs type:
When the handler uses the base EventArgs class (or no args), remove both parameters — the handler becomes parameterless:
// Web Forms
protected void Button_Click(object sender, EventArgs e)
{
SaveData();
}
// Blazor (migrated)
protected void Button_Click()
{
SaveData();
}When the handler uses a specialized EventArgs subclass, remove only sender — keep the event args parameter:
// Web Forms
protected void Grid_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "Delete")
{
int rowIndex = Convert.ToInt32(e.CommandArgument);
DeleteRow(rowIndex);
}
}
// Blazor (migrated)
protected void Grid_RowCommand(GridViewCommandEventArgs e)
{
if (e.CommandName == "Delete")
{
int rowIndex = Convert.ToInt32(e.CommandArgument);
DeleteRow(rowIndex);
}
}The following table shows before/after signatures for the most common Web Forms event handlers:
| Control | Web Forms Signature | Blazor Signature | Rule |
|---|---|---|---|
| Button Click | Button_Click(object sender, EventArgs e) |
Button_Click() |
1 — Standard |
| LinkButton Click | Link_Click(object sender, EventArgs e) |
Link_Click() |
1 — Standard |
| ImageButton Click | Image_Click(object sender, ImageClickEventArgs e) |
Image_Click(ImageClickEventArgs e) |
2 — Specialized |
| DropDownList Changed | DropDown_Changed(object sender, EventArgs e) |
DropDown_Changed() |
1 — Standard |
| CheckBox Changed | Check_Changed(object sender, EventArgs e) |
Check_Changed() |
1 — Standard |
| GridView RowCommand | Grid_RowCommand(object sender, GridViewCommandEventArgs e) |
Grid_RowCommand(GridViewCommandEventArgs e) |
2 — Specialized |
| GridView RowEditing | Grid_RowEditing(object sender, GridViewEditEventArgs e) |
Grid_RowEditing(GridViewEditEventArgs e) |
2 — Specialized |
| GridView RowDeleting | Grid_RowDeleting(object sender, GridViewDeleteEventArgs e) |
Grid_RowDeleting(GridViewDeleteEventArgs e) |
2 — Specialized |
| GridView PageIndexChanging | Grid_PageChanging(object sender, GridViewPageEventArgs e) |
Grid_PageChanging(GridViewPageEventArgs e) |
2 — Specialized |
| GridView RowDataBound | Grid_RowDataBound(object sender, GridViewRowEventArgs e) |
Grid_RowDataBound(GridViewRowEventArgs e) |
2 — Specialized |
| Repeater ItemCommand | Repeater_ItemCommand(object sender, RepeaterCommandEventArgs e) |
Repeater_ItemCommand(RepeaterCommandEventArgs e) |
2 — Specialized |
| ListView ItemCommand | List_ItemCommand(object sender, ListViewCommandEventArgs e) |
List_ItemCommand(ListViewCommandEventArgs e) |
2 — Specialized |
| TextBox TextChanged | TextBox_Changed(object sender, EventArgs e) |
TextBox_Changed() |
1 — Standard |
| Calendar SelectionChanged | Calendar_Changed(object sender, EventArgs e) |
Calendar_Changed() |
1 — Standard |
| Timer Tick | Timer_Tick(object sender, EventArgs e) |
Timer_Tick() |
1 — Standard |
=== "Web Forms (Original)" ```csharp // ProductAdmin.aspx.cs public partial class ProductAdmin : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { BindGrid(); } }
protected void AddButton_Click(object sender, EventArgs e)
{
Response.Redirect("AddProduct.aspx");
}
protected void ProductGrid_RowCommand(object sender, GridViewCommandEventArgs e)
{
if (e.CommandName == "Edit")
{
int index = Convert.ToInt32(e.CommandArgument);
Response.Redirect($"EditProduct.aspx?id={index}");
}
}
protected void ProductGrid_RowDeleting(object sender, GridViewDeleteEventArgs e)
{
int productId = (int)ProductGrid.DataKeys[e.RowIndex].Value;
DeleteProduct(productId);
BindGrid();
}
protected void SearchBox_TextChanged(object sender, EventArgs e)
{
string query = SearchBox.Text;
ProductGrid.DataSource = SearchProducts(query);
ProductGrid.DataBind();
}
}
```
=== "Blazor (After Migration)" ```csharp // ProductAdmin.razor.cs public partial class ProductAdmin : ComponentBase { protected override async Task OnInitializedAsync() { // Was Page_Load — lifecycle also transformed if (!IsPostBack) { BindGrid(); } }
protected void AddButton_Click()
{
// Rule 1: Standard EventArgs → parameterless
Response.Redirect("AddProduct.aspx");
}
protected void ProductGrid_RowCommand(GridViewCommandEventArgs e)
{
// Rule 2: Specialized EventArgs → sender removed, e kept
if (e.CommandName == "Edit")
{
int index = Convert.ToInt32(e.CommandArgument);
Response.Redirect($"EditProduct.aspx?id={index}");
}
}
protected void ProductGrid_RowDeleting(GridViewDeleteEventArgs e)
{
// Rule 2: Specialized EventArgs → sender removed, e kept
int productId = (int)ProductGrid.DataKeys[e.RowIndex].Value;
DeleteProduct(productId);
BindGrid();
}
protected void SearchBox_TextChanged()
{
// Rule 1: Standard EventArgs → parameterless
string query = SearchBox.Text;
ProductGrid.DataSource = SearchProducts(query);
ProductGrid.DataBind();
}
}
```
The migration script (bwfc-migrate.ps1) handles these transformations automatically:
- Detects EventArgs type — Inspects the second parameter's type to determine which rule to apply
- Applies Rule 1 or Rule 2 — Strips parameters according to the rules above
- Preserves method body — All logic inside the handler is left unchanged
- Updates method visibility — Keeps
protected voidas-is (no override needed for event handlers)
The script recognizes these as specialized EventArgs that trigger Rule 2 (keep the parameter):
GridViewCommandEventArgs,GridViewEditEventArgs,GridViewDeleteEventArgs,GridViewUpdateEventArgs,GridViewPageEventArgs,GridViewRowEventArgs,GridViewSortEventArgs,GridViewSelectEventArgsRepeaterCommandEventArgs,RepeaterItemEventArgsListViewCommandEventArgs,ListViewEditEventArgs,ListViewDeleteEventArgs,ListViewInsertEventArgs,ListViewUpdateEventArgsFormViewInsertEventArgs,FormViewUpdateEventArgs,FormViewDeleteEventArgsDataListCommandEventArgs,DataListItemEventArgsImageClickEventArgsCommandEventArgs- Any type name ending in
EventArgsthat is not the baseSystem.EventArgs
After the automated migration, review the following:
If the handler body references sender, the automated transform removes the parameter but leaves the reference:
// Web Forms — uses sender to identify which button was clicked
protected void Button_Click(object sender, EventArgs e)
{
var btn = (Button)sender;
StatusLabel.Text = $"You clicked {btn.Text}";
}
// After migration — sender reference breaks
protected void Button_Click()
{
var btn = (Button)sender; // ❌ Compile error
StatusLabel.Text = $"You clicked {btn.Text}";
}Fix: Replace sender with direct control references or component state:
// Option 1: Pass identifying data via CommandArgument
protected void Button_Click()
{
StatusLabel.Text = "Button clicked";
}
// Option 2: Use separate handlers per button
protected void SaveButton_Click()
{
StatusLabel.Text = "You clicked Save";
}Web Forms allows dynamically wiring events in code:
// Web Forms — dynamic wiring
protected void Page_Init(object sender, EventArgs e)
{
var btn = new Button();
btn.Click += DynamicButton_Click;
PlaceHolder1.Controls.Add(btn);
}This pattern doesn't have a direct Blazor equivalent. Consider using RenderFragment or conditional rendering instead.
If your application defines custom EventArgs subclasses, verify the migration script recognized them:
// Custom EventArgs — should trigger Rule 2
public class ProductEventArgs : EventArgs
{
public int ProductId { get; set; }
}
protected void Product_Selected(object sender, ProductEventArgs e)
{
LoadProduct(e.ProductId);
}
// Should migrate to (Rule 2):
protected void Product_Selected(ProductEventArgs e)
{
LoadProduct(e.ProductId);
}- ✅ Standard
EventArgs→ strip bothsenderande(parameterless handler) - ✅ Specialized
EventArgs→ stripsenderonly, keepe - ✅ Automated by the migration script
⚠️ Review handler bodies that referencesender— may need refactoring⚠️ Dynamic event wiring needs manual conversion to Blazor patterns⚠️ CustomEventArgssubclasses should be auto-detected but verify
See Automated Migration Guide for the full list of automated transformations performed by the migration script.