Skip to content

Commit 72159ba

Browse files
csharpfritzCopilot
andcommitted
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<object> 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>
1 parent 1bd9b17 commit 72159ba

23 files changed

Lines changed: 822 additions & 4 deletions
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
# DataBind Pattern Conversion
2+
3+
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.
4+
5+
## Overview
6+
7+
**What it does:**
8+
- Detects `controlName.DataSource = expression;` in code-behind
9+
- Removes `controlName.DataBind();` calls
10+
- Generates a private backing field (`_controlNameData`)
11+
- Replaces the DataSource assignment with a field assignment
12+
- Injects `Items="@_controlNameData"` on the matching markup tag
13+
14+
**Why it matters:**
15+
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.
16+
17+
## The Transform
18+
19+
### Before (Web Forms)
20+
21+
=== "Markup (.aspx)"
22+
```html
23+
<asp:GridView ID="gvProducts" runat="server" AutoGenerateColumns="false">
24+
<Columns>
25+
<asp:BoundField DataField="Name" HeaderText="Product Name" />
26+
<asp:BoundField DataField="Price" HeaderText="Price" />
27+
</Columns>
28+
</asp:GridView>
29+
```
30+
31+
=== "Code-Behind (.aspx.cs)"
32+
```csharp
33+
protected void Page_Load(object sender, EventArgs e)
34+
{
35+
if (!IsPostBack)
36+
{
37+
gvProducts.DataSource = GetProducts();
38+
gvProducts.DataBind();
39+
}
40+
}
41+
```
42+
43+
### After (Blazor — Automated)
44+
45+
=== "Markup (.razor)"
46+
```html
47+
<GridView id="gvProducts" Items="@_gvProductsData" AutoGenerateColumns="false">
48+
<Columns>
49+
<BoundField DataField="Name" HeaderText="Product Name" />
50+
<BoundField DataField="Price" HeaderText="Price" />
51+
</Columns>
52+
</GridView>
53+
```
54+
55+
=== "Code-Behind (.razor.cs)"
56+
```csharp
57+
// Data binding fields (generated by L1 migration)
58+
private IEnumerable<object> _gvProductsData;
59+
60+
protected override async Task OnInitializedAsync()
61+
{
62+
await base.OnInitializedAsync();
63+
_gvProductsData = GetProducts();
64+
}
65+
```
66+
67+
## How It Works
68+
69+
The transform operates in two phases with cross-file correlation:
70+
71+
### Phase 1: Pre-Scan Code-Behind
72+
73+
Before processing the markup, the script scans the code-behind for `DataSource` assignments:
74+
75+
```
76+
gvProducts.DataSource = ... → Map: { gvProducts → _gvProductsData }
77+
rptItems.DataSource = ... → Map: { rptItems → _rptItemsData }
78+
```
79+
80+
### Phase 2: Apply Transforms
81+
82+
1. **Markup:** For each control ID in the map, finds `id="controlId"` and injects `Items="@_fieldName"`
83+
2. **Code-behind:** Replaces `controlName.DataSource = expr;` with `_fieldNameData = expr;`
84+
3. **Code-behind:** Removes all `controlName.DataBind();` calls
85+
4. **Code-behind:** Generates `private IEnumerable<object> _fieldNameData;` declarations
86+
87+
## Multiple Controls
88+
89+
The transform handles multiple data-bound controls in the same page:
90+
91+
```csharp
92+
// Web Forms
93+
gvOrders.DataSource = GetOrders();
94+
gvOrders.DataBind();
95+
rptCategories.DataSource = GetCategories();
96+
rptCategories.DataBind();
97+
98+
// Blazor (migrated)
99+
private IEnumerable<object> _gvOrdersData;
100+
private IEnumerable<object> _rptCategoriesData;
101+
102+
// In OnInitializedAsync:
103+
_gvOrdersData = GetOrders();
104+
_rptCategoriesData = GetCategories();
105+
```
106+
107+
## Field Naming Convention
108+
109+
The generated field name follows this pattern:
110+
111+
```
112+
controlName → _controlNameData
113+
```
114+
115+
| Control ID | Generated Field |
116+
|-----------|----------------|
117+
| `gvProducts` | `_gvProductsData` |
118+
| `rptCategories` | `_rptCategoriesData` |
119+
| `dlItems` | `_dlItemsData` |
120+
| `lvOrders` | `_lvOrdersData` |
121+
122+
The first character is lowercased and `Data` is appended, with a `_` prefix for private fields.
123+
124+
## Manual Review Checklist
125+
126+
After the automated migration, review the following:
127+
128+
### 1. Typed Data Binding
129+
130+
The generated field uses `IEnumerable<object>`. For strongly-typed binding, update the field type:
131+
132+
```csharp
133+
// Generated (generic)
134+
private IEnumerable<object> _gvProductsData;
135+
136+
// Improved (typed)
137+
private IEnumerable<Product> _gvProductsData;
138+
```
139+
140+
### 2. Re-binding in Event Handlers
141+
142+
If your code re-binds data in event handlers (e.g., after a delete), the DataSource assignment is also converted:
143+
144+
```csharp
145+
// Web Forms
146+
protected void Delete_Click(object sender, EventArgs e)
147+
{
148+
DeleteProduct(selectedId);
149+
gvProducts.DataSource = GetProducts();
150+
gvProducts.DataBind();
151+
}
152+
153+
// Blazor (migrated)
154+
protected void Delete_Click()
155+
{
156+
DeleteProduct(selectedId);
157+
_gvProductsData = GetProducts(); // Re-assignment triggers re-render
158+
}
159+
```
160+
161+
In Blazor, assigning to the backing field and calling `StateHasChanged()` (automatic in event handlers) triggers a re-render with the new data.
162+
163+
### 3. DataSourceID References
164+
165+
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.
166+
167+
## Test Coverage
168+
169+
- **TC22** — Single GridView with DataBind in Page_Load
170+
- **TC23** — Multiple controls (GridView + Repeater) with DataBind
171+
- **TC25** — Combined DataBind + event handler wiring on same page
172+
173+
## See Also
174+
175+
- [Event Handler Wiring](Phase3-EventHandlerWiring.md) — Markup event attribute transform
176+
- [Lifecycle Transforms](Phase2-LifecycleTransforms.md) — Page_Load → OnInitializedAsync
177+
- [Automated Migration Guide](AutomatedMigration.md) — Full list of automated transforms
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Event Handler Wiring Migration
2+
3+
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.
4+
5+
## Overview
6+
7+
**What it does:**
8+
- Adds `@` prefix to event handler attribute values in converted markup
9+
- Targets `On[A-Z]*` attributes with bare method name values
10+
- Skips attributes that already have `@` or contain non-identifier characters
11+
12+
**Why it matters:**
13+
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.
14+
15+
## The Transform
16+
17+
### Before (Web Forms)
18+
19+
```html
20+
<asp:Button ID="btnSave" Text="Save" OnClick="Save_Click" runat="server" />
21+
<asp:DropDownList ID="ddlCategory" OnSelectedIndexChanged="Category_Changed" runat="server" />
22+
<asp:GridView ID="gvItems" OnRowCommand="Grid_RowCommand" runat="server" />
23+
```
24+
25+
### After (Blazor — Automated)
26+
27+
```html
28+
<Button id="btnSave" Text="Save" OnClick="@Save_Click" />
29+
<DropDownList id="ddlCategory" OnSelectedIndexChanged="@Category_Changed" />
30+
<GridView id="gvItems" OnRowCommand="@Grid_RowCommand" />
31+
```
32+
33+
## Supported Event Attributes
34+
35+
The transform handles all Web Forms event attributes that follow the `On[A-Z]*` naming convention:
36+
37+
| Event Attribute | Controls | Description |
38+
|----------------|----------|-------------|
39+
| `OnClick` | Button, LinkButton, ImageButton | Click handler |
40+
| `OnTextChanged` | TextBox | Text change handler |
41+
| `OnSelectedIndexChanged` | DropDownList, ListBox, RadioButtonList, CheckBoxList | Selection change |
42+
| `OnCheckedChanged` | CheckBox, RadioButton | Check state change |
43+
| `OnRowCommand` | GridView | Row command handler |
44+
| `OnRowEditing` | GridView | Row edit begin |
45+
| `OnRowUpdating` | GridView | Row update handler |
46+
| `OnRowDeleting` | GridView | Row delete handler |
47+
| `OnRowCancelingEdit` | GridView | Row edit cancel |
48+
| `OnPageIndexChanging` | GridView | Paging handler |
49+
| `OnSorting` | GridView | Sort handler |
50+
| `OnItemCommand` | Repeater, DataList | Item command handler |
51+
52+
## Combined with Signature Transforms
53+
54+
This markup-side transform works together with the [Event Handler Signatures](Phase2-EventHandlerSignatures.md) code-behind transform. Together they handle the full event migration:
55+
56+
1. **Markup:** `OnClick="Save_Click"``OnClick="@Save_Click"` (this transform)
57+
2. **Code-behind:** `protected void Save_Click(object sender, EventArgs e)``protected void Save_Click()` (signature transform)
58+
59+
## How the Script Detects Event Handlers
60+
61+
The regex pattern `(On[A-Z]\w+)="([A-Za-z_]\w*)"` matches:
62+
63+
- **Attribute name** starts with `On` followed by an uppercase letter (e.g., `OnClick`, `OnRowCommand`)
64+
- **Attribute value** is a bare C# identifier (e.g., `Save_Click`, `Grid_RowCommand`)
65+
66+
Values that are **not** transformed:
67+
- Already prefixed: `OnClick="@Save_Click"` — no change
68+
- String values: `OnClick="javascript:doSomething()"` — contains `:()`, not an identifier
69+
- Empty values: `OnClick=""` — nothing to prefix
70+
- Expressions: `OnClick="@(() => HandleClick())"` — starts with `@`
71+
72+
## Test Coverage
73+
74+
- **TC20** — Standard event handlers (OnClick on Button, LinkButton)
75+
- **TC21** — Specialized event handlers (OnRowCommand, OnPageIndexChanging on GridView)
76+
- **TC24** — Multiple event types (OnTextChanged, OnSelectedIndexChanged, OnCheckedChanged, OnClick)
77+
- **TC25** — Combined with DataBind pattern (event wiring + data binding on same page)
78+
79+
## See Also
80+
81+
- [Event Handler Signatures](Phase2-EventHandlerSignatures.md) — Code-behind signature transforms
82+
- [DataBind Pattern Conversion](Phase3-DataBindConversion.md) — DataSource/DataBind → Items binding
83+
- [Automated Migration Guide](AutomatedMigration.md) — Full list of automated transforms

0 commit comments

Comments
 (0)