Skip to content

Commit 498c497

Browse files
csharpfritzCopilot
andcommitted
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>
1 parent c96655a commit 498c497

9 files changed

Lines changed: 920 additions & 112 deletions

File tree

migration-toolkit/CHECKLIST.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ The checklist is organized by the [three-layer pipeline](METHODOLOGY.md). Work t
4040
- [ ] Event handlers converted to Blazor signatures (remove `sender`, `EventArgs`)
4141
- [ ]`Page_Load` / `IsPostBack` — works AS-IS via `WebFormsPageBase` (only signature `Page_Load(sender, e)``OnInitializedAsync` needs converting; `IsPostBack` inside works unchanged)
4242
- [ ]`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)
4448
- [ ]`Page.Title` / `Page.MetaDescription` — works AS-IS via WebFormsPageBase
4549
- [ ]`Request.QueryString["key"]` — works AS-IS via RequestShim
4650
- [ ]`Cache["key"]` — works AS-IS via CacheShim
@@ -54,7 +58,9 @@ The checklist is organized by the [three-layer pipeline](METHODOLOGY.md). Work t
5458

5559
- [ ] Data access pattern decided (injected service, EF Core, Dapper, etc.)
5660
- [ ] 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)
5864
- [ ] Authentication/authorization wired (if page requires auth)
5965
- [ ] Third-party integrations ported (API calls, payment, etc.)
6066
- [ ] Route registered and tested (`@page` directive matches expected URL)

migration-toolkit/METHODOLOGY.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ Layer 1 handles every transform that can be applied mechanically. The CLI tool a
7474
| File renaming (`.aspx``.razor`) | 33 | 100% |
7575
| Project scaffold (`.csproj`, `Program.cs`, `_Imports.razor`, `App.razor`) | Full ||
7676

77-
`_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.
7880

7981
### Shim Infrastructure
8082

@@ -126,6 +128,10 @@ After Layer 1, pages fall into three readiness categories:
126128

127129
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.
128130

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+
129135
### What Shims Handle Automatically (no Layer 2 work needed)
130136

131137
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.
183189
> | Approach | When to Use | Example |
184190
> |---|---|---|
185191
> | **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 |
187193
>
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.**
189195
190196
---
191197

@@ -196,6 +202,8 @@ Always review Copilot's changes before committing.
196202

197203
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.
198204

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.
206+
199207
### Common Layer 3 Decisions
200208

201209
| Decision | Web Forms Pattern | Blazor Options |

migration-toolkit/QUICKSTART.md

Lines changed: 111 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,33 @@ After the migration script runs, verify these are in place (the script scaffolds
102102
@inherits BlazorWebFormsComponents.WebFormsPageBase
103103
```
104104

105-
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:**
106+
- `Page.Title`, `Page.MetaDescription`, `Page.MetaKeywords`
107+
- `IsPostBack` (false on first render, true on interactions)
108+
- `Session["key"]` (scoped in-memory dictionary)
109+
- `Response.Redirect("~/path")` (auto-strips `~/` and `.aspx`)
110+
- `Request.Url`, `Request.QueryString["key"]`, `Request.Form["key"]`
111+
- `Cache["key"]` (application-level cache)
112+
- `Server.MapPath("~/path")` (virtual → physical path)
113+
- `ClientScript.RegisterStartupScript(...)` (JS interop)
114+
115+
Your Web Forms code-behind compiles **unchanged**. No manual conversion needed.
106116

107117
**`Program.cs`** — register BWFC services:
108118
```csharp
109119
builder.Services.AddBlazorWebFormsComponents();
110120
```
111121

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"]`)
124+
- Registers `ResponseShim` (handles `Response.Redirect`, `Response.Write`)
125+
- Registers `RequestShim` (provides `Request.QueryString`, `Request.Form`, `Request.Url`)
126+
- Registers `CacheShim` (in-memory application cache)
127+
- Registers `ServerShim` (provides `Server.MapPath`)
128+
- Registers `ClientScriptShim` (JS interop for `ClientScript.RegisterStartupScript`)
129+
- Registers `ViewStateShim` (compile-compatible dictionary)
130+
131+
After this single call, all Web Forms APIs work AS-IS in your migrated code — no manual conversion required.
113132

114133
**Layout (`MainLayout.razor`)** — add the Page render component:
115134
```razor
@@ -166,6 +185,50 @@ The following are **no longer Layer 2 work** — they work AS-IS via shims:
166185
- ~~`Session["key"]` → mark for Layer 3~~ → works via SessionShim
167186
- ~~`Page.Title` conversion~~ → works via WebFormsPageBase
168187

188+
### Using Shims (No Conversion Needed)
189+
190+
**The shims preserve Web Forms API calls AS-IS.** Here's what works unchanged:
191+
192+
```csharp
193+
// Session access — works exactly like Web Forms
194+
Session["CartId"] = cartId;
195+
var cartId = Session["CartId"];
196+
197+
// Response.Redirect — auto-strips ~/ and .aspx
198+
Response.Redirect("~/Products");
199+
Response.Redirect("~/Product.aspx?id=5"); // becomes /Product/5 if routing configured
200+
201+
// Request.QueryString — reads URL parameters
202+
var productId = Request.QueryString["id"];
203+
204+
// Request.Form — reads form POST data (requires <WebFormsForm> wrapper)
205+
var username = Request.Form["username"];
206+
207+
// IsPostBack — false on first render, true on interactions
208+
if (!IsPostBack)
209+
{
210+
LoadInitialData();
211+
}
212+
213+
// Page properties — auto-rendered by <Page /> component
214+
Page.Title = "Product Details";
215+
Page.MetaDescription = "View product details";
216+
217+
// Cache — application-level cache
218+
Cache["RecentProducts"] = products;
219+
220+
// Server.MapPath — virtual to physical path
221+
var filePath = Server.MapPath("~/App_Data/config.xml");
222+
```
223+
224+
**Do NOT inject these services manually:**
225+
-`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+
169232
Look for `<!-- TODO: BWFC-MIGRATE -->` comments left by the migration script — these mark items that need manual attention.
170233

171234
---
@@ -175,14 +238,59 @@ Look for `<!-- TODO: BWFC-MIGRATE -->` comments left by the migration script —
175238
These are the decisions that need a human (or a human + the migration agent):
176239

177240
- **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)
179242
- **Authentication:** Migrate ASP.NET Membership/Identity to ASP.NET Core Identity
180243
- **EF6 → EF Core:** Update DbContext, register with DI, adjust LINQ queries
181244
- **Global.asax → Program.cs:** Convert lifecycle hooks to middleware
182245
- **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
183247

184248
> 📄 For interactive guidance, use the [Data Migration Skill](skills/bwfc-data-migration/SKILL.md)
185249
250+
### Common Mistakes to Avoid
251+
252+
**Anti-pattern #1: Manually converting shim-supported APIs**
253+
254+
**Wrong:**
255+
```csharp
256+
@inject NavigationManager Nav
257+
@code {
258+
void GoToProducts() => Nav.NavigateTo("/Products");
259+
}
260+
```
261+
262+
**Correct (use the shim):**
263+
```csharp
264+
@code {
265+
void GoToProducts() => Response.Redirect("~/Products");
266+
}
267+
```
268+
269+
**Anti-pattern #2: Injecting services that shims already provide**
270+
271+
**Wrong:**
272+
```csharp
273+
@inject IHttpContextAccessor HttpContext
274+
@code {
275+
var id = HttpContext.HttpContext.Request.Query["id"];
276+
}
277+
```
278+
279+
**Correct (use the shim):**
280+
```csharp
281+
@code {
282+
var id = Request.QueryString["id"];
283+
}
284+
```
285+
286+
**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."
291+
292+
The shims ARE the solution, not a workaround.
293+
186294
---
187295

188296
## Step 8: Build and Verify

migration-toolkit/README.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@ You're a .NET developer who owns a Web Forms application and wants to migrate it
2727

2828
---
2929

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+
3046
## How to Use This Toolkit
3147

3248
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
6581

6682
| Layer | What | How | Coverage |
6783
|---|---|---|---|
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 |
7086
| **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 |
7288

7389
**Start here:** [QUICKSTART.md](QUICKSTART.md) — the linear "just tell me what to do" path.
7490

0 commit comments

Comments
 (0)