From 72159baaa2a188e9b5b6f212346cf4502d683e47 Mon Sep 17 00:00:00 2001 From: "Jeffrey T. Fritz" Date: Sun, 29 Mar 2026 21:49:22 -0400 Subject: [PATCH 1/7] feat: Phase 3 event handler wiring and DataBind pattern conversion (GAP-09, GAP-32) Add two critical L1 script transforms that eliminate ~70% of remaining manual migration work: Event Handler Wiring (GAP-32): - Convert-EventHandlerWiring adds @ prefix to On* event attributes in markup - OnClick="Handler" OnClick="@Handler" for proper Blazor event binding - Handles all Web Forms event attributes (OnClick, OnTextChanged, OnRowCommand, etc.) - Regex-safe: only targets bare C# identifiers, skips existing @ expressions DataBind Pattern Conversion (GAP-09): - Convert-DataBindPattern transforms DataSource/DataBind() in code-behind - Get-DataBindMap pre-scans code-behind for cross-file markup correlation - Add-DataBindItemsAttribute injects Items="@_fieldName" on matching markup tags - Generates private IEnumerable backing fields with _controlNameData naming Tests: 25/25 L1 tests pass (100%), 618/618 lines match - TC22: DataBind with GridView - TC23: DataBind with multiple controls (GridView + Repeater) - TC24: Event wiring with multiple event types - TC25: Combined DataBind + event wiring - TC20/TC21 updated for @ prefix in expected output Docs: Phase3-EventHandlerWiring.md, Phase3-DataBindConversion.md Skills: CODE-TRANSFORMS.md updated with Phase 3 section Nav: mkdocs.yml updated with Phase 3 section Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/Migration/Phase3-DataBindConversion.md | 177 ++++++++++++++++++ docs/Migration/Phase3-EventHandlerWiring.md | 83 ++++++++ migration-toolkit/scripts/bwfc-migrate.ps1 | 170 ++++++++++++++++- .../skills/bwfc-migration/CODE-TRANSFORMS.md | 52 +++++ .../expected/TC20-EventHandlerStandard.razor | 4 +- .../TC21-EventHandlerSpecialized.razor | 2 +- .../expected/TC22-DataBindGridView.razor | 9 + .../expected/TC22-DataBindGridView.razor.cs | 43 +++++ .../expected/TC23-DataBindMultiple.razor | 9 + .../expected/TC23-DataBindMultiple.razor.cs | 42 +++++ .../expected/TC24-EventWiringMultiple.razor | 7 + .../TC24-EventWiringMultiple.razor.cs | 44 +++++ .../expected/TC25-DataBindAndEvents.razor | 10 + .../expected/TC25-DataBindAndEvents.razor.cs | 48 +++++ .../tests/inputs/TC22-DataBindGridView.aspx | 7 + .../inputs/TC22-DataBindGridView.aspx.cs | 22 +++ .../tests/inputs/TC23-DataBindMultiple.aspx | 7 + .../inputs/TC23-DataBindMultiple.aspx.cs | 19 ++ .../inputs/TC24-EventWiringMultiple.aspx | 5 + .../inputs/TC24-EventWiringMultiple.aspx.cs | 27 +++ .../tests/inputs/TC25-DataBindAndEvents.aspx | 8 + .../inputs/TC25-DataBindAndEvents.aspx.cs | 28 +++ mkdocs.yml | 3 + 23 files changed, 822 insertions(+), 4 deletions(-) create mode 100644 docs/Migration/Phase3-DataBindConversion.md create mode 100644 docs/Migration/Phase3-EventHandlerWiring.md create mode 100644 migration-toolkit/tests/expected/TC22-DataBindGridView.razor create mode 100644 migration-toolkit/tests/expected/TC22-DataBindGridView.razor.cs create mode 100644 migration-toolkit/tests/expected/TC23-DataBindMultiple.razor create mode 100644 migration-toolkit/tests/expected/TC23-DataBindMultiple.razor.cs create mode 100644 migration-toolkit/tests/expected/TC24-EventWiringMultiple.razor create mode 100644 migration-toolkit/tests/expected/TC24-EventWiringMultiple.razor.cs create mode 100644 migration-toolkit/tests/expected/TC25-DataBindAndEvents.razor create mode 100644 migration-toolkit/tests/expected/TC25-DataBindAndEvents.razor.cs create mode 100644 migration-toolkit/tests/inputs/TC22-DataBindGridView.aspx create mode 100644 migration-toolkit/tests/inputs/TC22-DataBindGridView.aspx.cs create mode 100644 migration-toolkit/tests/inputs/TC23-DataBindMultiple.aspx create mode 100644 migration-toolkit/tests/inputs/TC23-DataBindMultiple.aspx.cs create mode 100644 migration-toolkit/tests/inputs/TC24-EventWiringMultiple.aspx create mode 100644 migration-toolkit/tests/inputs/TC24-EventWiringMultiple.aspx.cs create mode 100644 migration-toolkit/tests/inputs/TC25-DataBindAndEvents.aspx create mode 100644 migration-toolkit/tests/inputs/TC25-DataBindAndEvents.aspx.cs diff --git a/docs/Migration/Phase3-DataBindConversion.md b/docs/Migration/Phase3-DataBindConversion.md new file mode 100644 index 00000000..07fd398f --- /dev/null +++ b/docs/Migration/Phase3-DataBindConversion.md @@ -0,0 +1,177 @@ +# DataBind Pattern Conversion + +The migration script automatically converts the Web Forms `DataSource`/`DataBind()` pattern to Blazor-compatible data binding using backing fields and the `Items` parameter. This handles the most common data binding pattern in Web Forms applications. + +## Overview + +**What it does:** +- Detects `controlName.DataSource = expression;` in code-behind +- Removes `controlName.DataBind();` calls +- Generates a private backing field (`_controlNameData`) +- Replaces the DataSource assignment with a field assignment +- Injects `Items="@_controlNameData"` on the matching markup tag + +**Why it matters:** +In Web Forms, every data-bound control requires `DataSource = data; DataBind();` to display data. In Blazor, BWFC components use an `Items` parameter for reactive binding. This transform bridges the gap automatically, eliminating one of the most tedious manual migration steps. + +## The Transform + +### Before (Web Forms) + +=== "Markup (.aspx)" + ```html + + + + + + + ``` + +=== "Code-Behind (.aspx.cs)" + ```csharp + protected void Page_Load(object sender, EventArgs e) + { + if (!IsPostBack) + { + gvProducts.DataSource = GetProducts(); + gvProducts.DataBind(); + } + } + ``` + +### After (Blazor — Automated) + +=== "Markup (.razor)" + ```html + + + + + + + ``` + +=== "Code-Behind (.razor.cs)" + ```csharp + // Data binding fields (generated by L1 migration) + private IEnumerable _gvProductsData; + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + _gvProductsData = GetProducts(); + } + ``` + +## How It Works + +The transform operates in two phases with cross-file correlation: + +### Phase 1: Pre-Scan Code-Behind + +Before processing the markup, the script scans the code-behind for `DataSource` assignments: + +``` +gvProducts.DataSource = ... → Map: { gvProducts → _gvProductsData } +rptItems.DataSource = ... → Map: { rptItems → _rptItemsData } +``` + +### Phase 2: Apply Transforms + +1. **Markup:** For each control ID in the map, finds `id="controlId"` and injects `Items="@_fieldName"` +2. **Code-behind:** Replaces `controlName.DataSource = expr;` with `_fieldNameData = expr;` +3. **Code-behind:** Removes all `controlName.DataBind();` calls +4. **Code-behind:** Generates `private IEnumerable _fieldNameData;` declarations + +## Multiple Controls + +The transform handles multiple data-bound controls in the same page: + +```csharp +// Web Forms +gvOrders.DataSource = GetOrders(); +gvOrders.DataBind(); +rptCategories.DataSource = GetCategories(); +rptCategories.DataBind(); + +// Blazor (migrated) +private IEnumerable _gvOrdersData; +private IEnumerable _rptCategoriesData; + +// In OnInitializedAsync: +_gvOrdersData = GetOrders(); +_rptCategoriesData = GetCategories(); +``` + +## Field Naming Convention + +The generated field name follows this pattern: + +``` +controlName → _controlNameData +``` + +| Control ID | Generated Field | +|-----------|----------------| +| `gvProducts` | `_gvProductsData` | +| `rptCategories` | `_rptCategoriesData` | +| `dlItems` | `_dlItemsData` | +| `lvOrders` | `_lvOrdersData` | + +The first character is lowercased and `Data` is appended, with a `_` prefix for private fields. + +## Manual Review Checklist + +After the automated migration, review the following: + +### 1. Typed Data Binding + +The generated field uses `IEnumerable`. For strongly-typed binding, update the field type: + +```csharp +// Generated (generic) +private IEnumerable _gvProductsData; + +// Improved (typed) +private IEnumerable _gvProductsData; +``` + +### 2. Re-binding in Event Handlers + +If your code re-binds data in event handlers (e.g., after a delete), the DataSource assignment is also converted: + +```csharp +// Web Forms +protected void Delete_Click(object sender, EventArgs e) +{ + DeleteProduct(selectedId); + gvProducts.DataSource = GetProducts(); + gvProducts.DataBind(); +} + +// Blazor (migrated) +protected void Delete_Click() +{ + DeleteProduct(selectedId); + _gvProductsData = GetProducts(); // Re-assignment triggers re-render +} +``` + +In Blazor, assigning to the backing field and calling `StateHasChanged()` (automatic in event handlers) triggers a re-render with the new data. + +### 3. DataSourceID References + +If your control uses `DataSourceID` instead of code-behind `DataSource`, the transform doesn't apply. `DataSourceID` is handled separately with TODO comments — see the [DataSourceID Migration](DeferredControls.md) guide. + +## Test Coverage + +- **TC22** — Single GridView with DataBind in Page_Load +- **TC23** — Multiple controls (GridView + Repeater) with DataBind +- **TC25** — Combined DataBind + event handler wiring on same page + +## See Also + +- [Event Handler Wiring](Phase3-EventHandlerWiring.md) — Markup event attribute transform +- [Lifecycle Transforms](Phase2-LifecycleTransforms.md) — Page_Load → OnInitializedAsync +- [Automated Migration Guide](AutomatedMigration.md) — Full list of automated transforms diff --git a/docs/Migration/Phase3-EventHandlerWiring.md b/docs/Migration/Phase3-EventHandlerWiring.md new file mode 100644 index 00000000..ed3d55d2 --- /dev/null +++ b/docs/Migration/Phase3-EventHandlerWiring.md @@ -0,0 +1,83 @@ +# Event Handler Wiring Migration + +The migration script automatically adds the `@` prefix to event handler attributes in markup, enabling proper Blazor event binding. This transforms `OnClick="Handler"` into `OnClick="@Handler"` so the Razor compiler can resolve the method reference. + +## Overview + +**What it does:** +- Adds `@` prefix to event handler attribute values in converted markup +- Targets `On[A-Z]*` attributes with bare method name values +- Skips attributes that already have `@` or contain non-identifier characters + +**Why it matters:** +In Web Forms, `OnClick="Button_Click"` in markup is a string that the runtime resolves at page compile time. In Blazor, the `@` prefix is required to tell the Razor compiler to treat the value as a C# expression (a method group), not a string literal. Without `@`, you get compilation errors. + +## The Transform + +### Before (Web Forms) + +```html + + + +``` + +### After (Blazor — Automated) + +```html +