You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(migration-toolkit): enforce shim-first migration strategy across all skills
Rewrote all 5 skills and 4 docs to prevent agents from manually
implementing session, redirect, and cookie logic when shims exist.
Key changes:
- Added MANDATORY RULES and ANTI-PATTERNS sections
- SessionShim is now THE primary session strategy (not Minimal API)
- Response.Redirect/Request.QueryString preserved via shims
- Added migration decision tree for common patterns
- Demoted shim-to-native conversion to optional L3 optimization
- Added DbContext materialization pattern for SelectMethod
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
-[ ] ✅ `Page_Load` / `IsPostBack` — works AS-IS via `WebFormsPageBase` (only signature `Page_Load(sender, e)` → `OnInitializedAsync` needs converting; `IsPostBack` inside works unchanged)
42
42
-[ ] ✅ `Response.Redirect` — works AS-IS via ResponseShim (auto-strips `~/` and `.aspx`)
43
-
-[ ] ✅ `Session["key"]` — works AS-IS via SessionShim (no longer needs Layer 3)
43
+
-[ ] Session state: Use `Session["key"]` from `WebFormsPageBase` (SessionShim) — works in interactive mode ✅
44
+
-[ ] ✅ Response.Redirect() calls work via ResponseShim
45
+
-[ ] ✅ Request.QueryString[] calls work via RequestShim
46
+
-[ ] No raw `IHttpContextAccessor` injected (use shim properties from `WebFormsPageBase` instead)
47
+
-[ ] No Minimal API endpoints created for page actions (use shim methods instead; minimal APIs are ONLY for cookie auth operations)
44
48
-[ ] ✅ `Page.Title` / `Page.MetaDescription` — works AS-IS via WebFormsPageBase
45
49
-[ ] ✅ `Request.QueryString["key"]` — works AS-IS via RequestShim
46
50
-[ ] ✅ `Cache["key"]` — works AS-IS via CacheShim
@@ -54,7 +58,9 @@ The checklist is organized by the [three-layer pipeline](METHODOLOGY.md). Work t
54
58
55
59
-[ ] Data access pattern decided (injected service, EF Core, Dapper, etc.)
56
60
-[ ] Data service implemented and registered in `Program.cs`
57
-
-[ ] Session state: basic usage works AS-IS via SessionShim; if persistent/distributed state needed, replace with `ProtectedSessionStorage` or scoped service
61
+
-[ ] Session state: basic usage works AS-IS via SessionShim; if persistent/distributed state needed, replace with `ProtectedSessionStorage` or scoped service (OPTIONAL — shim works correctly)
62
+
-[ ] Response.Redirect: works AS-IS via ResponseShim; if removing BWFC dependency, replace with `NavigationManager.NavigateTo()` (OPTIONAL — shim works correctly)
63
+
-[ ] Request.QueryString: works AS-IS via RequestShim; if cleaner Blazor pattern desired, replace with `[SupplyParameterFromQuery]` (OPTIONAL — shim works correctly)
`_Imports.razor` includes `@inherits BlazorWebFormsComponents.WebFormsPageBase` so that all converted pages get `Page.Title`, `Page.MetaDescription`, `Page.MetaKeywords`, and `IsPostBack` without per-page injection. The layout scaffold includes `<BlazorWebFormsComponents.Page />` to render `<PageTitle>` and `<meta>` tags.
77
+
The CLI generates `_Imports.razor` with `@inherits BlazorWebFormsComponents.WebFormsPageBase` so every page automatically gets `Page.Title`, `Page.MetaDescription`, `Page.MetaKeywords`, `IsPostBack`, `Session`, `Response`, `Request`, `Server`, `Cache`, and `ClientScript` — with the same API as Web Forms. The layout scaffold includes `<BlazorWebFormsComponents.Page />` to render `<PageTitle>` and `<meta>` tags.
78
+
79
+
The CLI also generates `Program.cs` with `builder.Services.AddBlazorWebFormsComponents()`, which registers all the shim infrastructure (SessionShim, ResponseShim, RequestShim, CacheShim, ServerShim, ClientScriptShim, ViewStateShim) automatically.
78
80
79
81
### Shim Infrastructure
80
82
@@ -126,6 +128,10 @@ After Layer 1, pages fall into three readiness categories:
126
128
127
129
Layer 2 handles transforms that follow consistent patterns but require understanding control semantics. A human *could* do these mechanically, but it's tedious and error-prone. Copilot with the BWFC migration skill handles them reliably.
128
130
131
+
**L2 Principle: Wire up data binding and lifecycle — NOT to replace shims with native patterns.**
132
+
133
+
The shims ARE the L2 strategy. If the original Web Forms code says `Session["CartId"]`, the migrated code says `Session["CartId"]`. The shim makes this work. Don't reinvent what already exists. Layer 2 is about making data flow through the page and connecting event handlers — NOT about converting `Response.Redirect()` to `NavigationManager.NavigateTo()`. That's an optional Layer 3 optimization.
134
+
129
135
### What Shims Handle Automatically (no Layer 2 work needed)
130
136
131
137
These items were previously Layer 2 manual transforms but are now handled AS-IS by BWFC shims:
@@ -183,9 +189,9 @@ Always review Copilot's changes before committing.
183
189
> | Approach | When to Use | Example |
184
190
> |---|---|---|
185
191
> |**Shim path** (keep AS-IS) | Migration speed is the priority; code works correctly; team isn't ready to learn Blazor-native patterns yet |`Response.Redirect("~/Products")` — works via ResponseShim |
186
-
> |**Native Blazor path** (refactor later) |Post-migration polish; team is comfortable with Blazor; want to reduce BWFC dependency |`NavigationManager.NavigateTo("/Products")` — native Blazor |
192
+
> |**Native Blazor path** (refactor later) |**Layer 3 optimization** — post-migration polish; team is comfortable with Blazor; want to reduce BWFC dependency |`NavigationManager.NavigateTo("/Products")` — native Blazor |
187
193
>
188
-
> **Recommendation:** Use shims to get migrated fast, then refactor to native Blazor incrementally as your team builds Blazor expertise. Both paths produce working code.
194
+
> **Recommendation:** Use shims to get migrated fast (Layer 2), then refactor to native Blazor incrementally in Layer 3 as your team builds Blazor expertise. Both paths produce working code.**Replacing shims with native patterns is OPTIONAL, not required.**
189
195
190
196
---
191
197
@@ -196,6 +202,8 @@ Always review Copilot's changes before committing.
196
202
197
203
Layer 3 is the ~15% of migration work that requires understanding your application's architecture. No script or AI can make these decisions for you — but the data migration skill and Copilot can guide you through the options and trade-offs.
198
204
205
+
**Important:** Replacing shims with native Blazor patterns (e.g., `Response.Redirect()` → `NavigationManager.NavigateTo()`) is an **OPTIONAL Layer 3 optimization**, not a migration requirement. Your app works with the shims. Native patterns are for teams that want to reduce BWFC dependency long-term.
The `@inherits` line makes every page inherit from `WebFormsPageBase`, which provides `Page.Title`, `Page.MetaDescription`, `Page.MetaKeywords`, and `IsPostBack` — so Web Forms code-behind compiles unchanged.
105
+
**This one line gives every page the Web Forms API:**
Your Web Forms code-behind compiles **unchanged**. No manual conversion needed.
106
116
107
117
**`Program.cs`** — register BWFC services:
108
118
```csharp
109
119
builder.Services.AddBlazorWebFormsComponents();
110
120
```
111
121
112
-
> **This single call registers ALL shims automatically.** After this, `Response.Redirect`, `Session["key"]`, `Request.QueryString`, `Cache["key"]`, `Server.MapPath`, `ClientScript`, and `ViewState` all work AS-IS in your migrated code-behind files — no manual conversion needed.
122
+
**What this does:**
123
+
- Registers `SessionShim` (scoped in-memory dictionary for `Session["key"]`)
- ❌ `IHttpContextAccessor` — use `Request` property instead
226
+
- ❌ `NavigationManager` (for redirects) — use `Response.Redirect()` instead
227
+
- ❌ `IMemoryCache` — use `Cache` property instead
228
+
- ❌ `IJSRuntime` (for startup scripts) — use `ClientScript.RegisterStartupScript()` instead
229
+
230
+
The shim properties are already available via `WebFormsPageBase`. Injecting these services and manually converting is extra work that provides no migration benefit.
231
+
169
232
Look for `<!-- TODO: BWFC-MIGRATE -->` comments left by the migration script — these mark items that need manual attention.
170
233
171
234
---
@@ -175,14 +238,59 @@ Look for `<!-- TODO: BWFC-MIGRATE -->` comments left by the migration script —
175
238
These are the decisions that need a human (or a human + the migration agent):
176
239
177
240
-**Data access:** Replace `SqlDataSource`/`ObjectDataSource` with injected services
178
-
-**Session state:** Convert `Session["key"]` to scoped services or `ProtectedSessionStorage` (if you need persistence — basic usage works AS-IS via SessionShim)
241
+
-**Session state:** Convert `Session["key"]` to scoped services or `ProtectedSessionStorage` (if you need persistence or distributed sessions — basic usage works AS-IS via SessionShim)
179
242
-**Authentication:** Migrate ASP.NET Membership/Identity to ASP.NET Core Identity
180
243
-**EF6 → EF Core:** Update DbContext, register with DI, adjust LINQ queries
181
244
-**Global.asax → Program.cs:** Convert lifecycle hooks to middleware
182
245
-**Third-party integrations:** Port to `HttpClient` pattern
246
+
-**Shim replacement (OPTIONAL):** Replace `Response.Redirect()` with `NavigationManager.NavigateTo()`, `Session` with injected state services, etc. — this is a performance/modernization step, NOT a migration requirement
183
247
184
248
> 📄 For interactive guidance, use the [Data Migration Skill](skills/bwfc-data-migration/SKILL.md)
**Anti-pattern #3: Treating shims as temporary scaffolding**
287
+
288
+
❌ **Wrong mindset:** "I'll use shims to get it compiling, then replace them with 'real' Blazor code."
289
+
290
+
✅ **Correct mindset:** "Shims are the migration strategy. They work correctly. Replacing them is an optional optimization I can do later if my team wants to reduce BWFC dependency."
Copy file name to clipboardExpand all lines: migration-toolkit/README.md
+19-3Lines changed: 19 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -27,6 +27,22 @@ You're a .NET developer who owns a Web Forms application and wants to migrate it
27
27
28
28
---
29
29
30
+
## Key Principle: Shim-First Migration
31
+
32
+
**The goal is to preserve the original Web Forms API calls, not rewrite them.**
33
+
34
+
When you migrate with BWFC:
35
+
36
+
- Pages inherit from `WebFormsPageBase`, which provides `Session`, `Response`, `Request`, `Server`, `Cache`, and `ClientScript` — **all with the SAME API as Web Forms**
37
+
- Your code-behind continues to use `Response.Redirect("~/Products")`, `Session["CartId"]`, `Request.QueryString["key"]`, and `IsPostBack` — no changes needed
38
+
-`AddBlazorWebFormsComponents()` in `Program.cs` registers all the shim infrastructure automatically
39
+
40
+
**This means:** If the original Web Forms code says `Session["CartId"]`, the migrated code says `Session["CartId"]`. The shim makes it work. You are not converting to `IMemoryCache`, `NavigationManager`, or `IHttpContextAccessor` — those are native Blazor patterns that you can adopt *later* if desired, but they are not required for migration.
41
+
42
+
Shims get your app compiling and running **fast**. Refactoring to native Blazor patterns is an optional Layer 3 optimization, not a migration requirement.
43
+
44
+
---
45
+
30
46
## How to Use This Toolkit
31
47
32
48
1.**Copy the `skills/` folder** into your project's `.github/skills/` directory.
@@ -65,10 +81,10 @@ Migration isn't one step — it's three layers that handle different kinds of wo
65
81
66
82
| Layer | What | How | Coverage |
67
83
|---|---|---|---|
68
-
|**Layer 1** — Automated | Tag prefix removal, `runat` removal, expression conversion, file renaming, shim infrastructure |[`webforms-to-blazor` CLI](../src/BlazorWebFormsComponents.Cli/) or [`bwfc-migrate.ps1`](scripts/bwfc-migrate.ps1) + `AddBlazorWebFormsComponents()`|~60% of work |
69
-
|**Layer 2** — Copilot-Assisted | Data binding rewiring, layout conversion, lifecycle method signatures, event handlers |[`skills/bwfc-migration/SKILL.md`](skills/bwfc-migration/SKILL.md)|~30% of work |
84
+
|**Layer 1** — Automated | Tag prefix removal, `runat` removal, expression conversion, file renaming, **shim infrastructure setup** (`_Imports.razor` with `@inherits WebFormsPageBase`, `Program.cs` with `AddBlazorWebFormsComponents()`) |[`webforms-to-blazor` CLI](../src/BlazorWebFormsComponents.Cli/) or [`bwfc-migrate.ps1`](scripts/bwfc-migrate.ps1)|~60% of work |
85
+
|**Layer 2** — Copilot-Assisted | Data binding rewiring, layout conversion, lifecycle method signatures, event handlers — **uses shims to preserve Web Forms patterns** (`Response.Redirect`, `Session`, `Request`, `IsPostBack` all work AS-IS) |[`skills/bwfc-migration/SKILL.md`](skills/bwfc-migration/SKILL.md)|~30% of work |
70
86
|**Layer 3** — Architecture Decisions | Identity, EF Core, third-party integrations |[`skills/bwfc-data-migration/SKILL.md`](skills/bwfc-data-migration/SKILL.md) + human judgment |~10% of work |
71
-
|**L3-opt** — Performance (optional) | Async/await, `AsNoTracking`, `IDbContextFactory`, .NET 10 patterns |[`skills/l3-performance-optimization/SKILL.md`](skills/l3-performance-optimization/SKILL.md)| After app is functional |
87
+
|**L3-opt** — Performance (optional) | Async/await, `AsNoTracking`, `IDbContextFactory`, .NET 10 patterns, **optionally replacing shims with native Blazor patterns**|[`skills/l3-performance-optimization/SKILL.md`](skills/l3-performance-optimization/SKILL.md)| After app is functional |
72
88
73
89
**Start here:**[QUICKSTART.md](QUICKSTART.md) — the linear "just tell me what to do" path.
0 commit comments