diff --git a/.ai-team/agents/beast/history.md b/.ai-team/agents/beast/history.md index b0ade346b..32e9228f5 100644 --- a/.ai-team/agents/beast/history.md +++ b/.ai-team/agents/beast/history.md @@ -94,3 +94,29 @@ - Structured for non-technical readers: bottom-line callout, business value lead, tables for data, minimal jargon - Sections: What is BWFC, Migration Scope, Component Coverage, Three-Layer Pipeline, Time & Cost Impact, Layer 1 Results, Page Readiness, Risk Reduction, What's Next +- **Migration Toolkit (6 priority documents in `/migration-toolkit/`):** + 1. **README.md** — Entry point. Prerequisites, three-layer pipeline overview, quick overview (scan→transform→guide→verify), file map, links to existing artifacts, honest "what BWFC doesn't cover" section. 100% net-new. + 2. **QUICKSTART.md** — Linear 9-step walkthrough from "I have a Web Forms app" to running Blazor. ~30% extracted from migration skill, ~70% net-new. + 3. **CONTROL-COVERAGE.md** — Full 52-component table with complexity ratings (Trivial/Easy/Medium/Complex), key changes, gotchas. Includes unsupported controls (DataSource, Wizard, Web Parts, AJAX Toolkit). ~70% extracted from migration skill, ~30% net-new. + 4. **METHODOLOGY.md** — Three-layer pipeline deep-dive with ASCII diagram, layer boundaries, readiness categories, time estimates. ~60% from executive report, ~40% net-new. + 5. **CHECKLIST.md** — Copy-paste per-page migration checklist organized by layer. Usage tips for GitHub issues and tracking. 100% net-new. + 6. **copilot-instructions-template.md** — Drop-in `.github/copilot-instructions.md` template with condensed rules, expression tables, placeholder sections. ~60% from migration skill, ~40% net-new. + - **Key decisions:** No content duplication — all docs reference scripts/skill/agent by relative path. CONTROL-COVERAGE.md is single source for coverage table in toolkit. copilot-instructions-template.md is self-contained (copied out of repo). Practitioner tone throughout. + - **Sources:** MIGRATION-TOOLKIT-DESIGN.md (blueprint), SKILL.md (rules/tables), WINGTIPTOYS-MIGRATION-EXECUTIVE-REPORT.md (metrics), migration.agent.md (decision frameworks), bwfc-scan.ps1/bwfc-migrate.ps1 (parameter docs). + + + Team update (2026-03-03): ListView CRUD events ItemCreated now fires per-item, ItemCommand fires for ALL commands before specific handlers decided by Cyclops + + Team update (2026-03-03): Migration toolkit pivoted from 9-doc folder to single SKILL.md in Copilot skill format decided by Jeffrey T. Fritz + +- **Distributable BWFC Migration Skill (`.github/skills/bwfc-migration/SKILL.md`):** + - Created single distributable Copilot skill file consolidating all migration toolkit content into one self-contained document. + - **Key content decisions vs. internal webforms-migration skill:** + 1. **Self-contained and NuGet-first:** No references to internal repo scripts (`bwfc-scan.ps1`, `bwfc-migrate.ps1`), agents, or `.ai-team/` paths. BWFC comes from NuGet, not repo clone. Designed to be dropped into any project's `.github/skills/` folder. + 2. **Added three-layer methodology section:** Extracted from executive report — Layer 1 (mechanical, ~40%), Layer 2 (structural, ~45%), Layer 3 (architecture, ~15%) with expected page-readiness breakdown. + 3. **Added 10 architecture decision templates (NEW content):** Master Page→Layout, Session→Scoped Services, Identity→Blazor Identity, EF6→EF Core, Global.asax→Program.cs, Web.config→appsettings.json, DataSource→Service Injection, RouteTable→@page, Handlers/Modules→Middleware, Third-Party→HttpClient. Each has before/after code. + 4. **Added per-page migration checklist:** Layer-organized checkbox template from Forge's CHECKLIST design. + 5. **Expanded component coverage summary:** Added category counts, component list per category, and "What BWFC Does NOT Cover" table (DataSource controls, Wizard, Web Parts, AJAX Toolkit extenders). + 6. **Expanded common gotchas:** Added event handler signatures, TextMode casing, ScriptManager no-op guidance. + 7. **Removed WingtipToys-specific section:** Not applicable for a distributable skill — that content is project-specific. + - Total: ~750 lines. Preserves ~90% of existing internal skill content, adds ~30% new content from design doc and executive report. diff --git a/.ai-team/agents/colossus/history.md b/.ai-team/agents/colossus/history.md index 82200c041..ce4c63eb2 100644 --- a/.ai-team/agents/colossus/history.md +++ b/.ai-team/agents/colossus/history.md @@ -71,3 +71,5 @@ Added 5 smoke tests (Timer, UpdatePanel, UpdateProgress, ScriptManager, Substitu - The ModelErrorMessage component renders nothing when no errors exist (conditional `@if`), so error-gone assertions use `CountAsync() == 0` rather than visibility checks. - For the Clear button test, used `WaitForSelectorAsync` with `State.Hidden` to reliably wait for Blazor re-render after clearing the EditContext. - `PressSequentiallyAsync` + `Tab` pattern used for Blazor Server InputText fields, consistent with established team conventions. + + Team update (2026-03-03): ListView CRUD events ItemCreated now fires per-item, ItemCommand fires for ALL commands before specific handlers decided by Cyclops diff --git a/.ai-team/agents/cyclops/history.md b/.ai-team/agents/cyclops/history.md index 39422bab9..6a557fe87 100644 --- a/.ai-team/agents/cyclops/history.md +++ b/.ai-team/agents/cyclops/history.md @@ -80,20 +80,7 @@ Team update (2026-02-28): GetCssClassOrNull() uses IsNullOrEmpty not IsNullOrWhi 📌 Team update (2026-03-02): Skins & Themes roadmap — 3 waves, 15 WIs — decided by Forge 📌 Team updates (2026-03-02): M22 planned (Forge), project reframed as migration system (Jeff), FormView RenderOuterTable resolved (Cyclops), ModelErrorMessage 29/29 coverage (Forge), WingtipToys pipeline validated — 28/29 controls covered. 📌 Team update (2026-03-03): WingtipToys CSS fidelity — 7 visual differences identified requiring fixes (Cerulean theme, 4-column grid, BoundField bug, Trucks category, Site.css, category IDs) — decided by Forge - - -### M20 Theming & Release Process Summary (2026-03-01 through 2026-03-02) - -**Issue #366 theme wiring:** Moved CascadingParameter ThemeConfiguration to BaseWebFormsComponent (named CascadedTheme to avoid Blazor duplicate-parameter error from _Imports.razor). ApplySkin renamed to ApplyThemeSkin (virtual override chain). ThemeProvider got @inherits ComponentBase to exclude from BaseWebFormsComponent inheritance. WebFormsPage cascades Theme ?? CascadedTheme. Lesson: _Imports.razor @inherits affects ALL .razor files including infrastructure components. - -**FontInfo auto-sync:** Name and Names converted to backing-field properties with bidirectional sync (setting Name updates Names and vice versa). ApplyThemeSkin guard checks both Font.Name AND Font.Names before applying theme font. Root cause: ApplyThemeSkin set Font.Name but ToStyle() reads Font.Names. Lesson: paired/synced Web Forms properties must replicate sync behavior. - -**Unified release.yml:** Single workflow on release:published coordinates NuGet + Docker + GHCR + docs + demos. Version from tag_name stripping v prefix. NuGet override: -p:PackageVersion + -p:Version. version.json changed to 3-segment SemVer (0.17.0). deploy-server-side.yml and nuget.yml refactored to workflow_dispatch-only. docs.yml fixed deprecated ::set-output. NBGV ignores git tags -- reads version.json only. - -Team updates: Unified release process (PR #408), Skins & Themes roadmap (3 waves, 15 WIs). - - - Team update (2026-03-02): Full Skins & Themes roadmap defined 3 waves, 15 work items. Wave 1: Theme mode, sub-component styles (41 slots across 6 controls), EnableTheming propagation, runtime switching. See decisions.md for full roadmap and agent assignments decided by Forge + ### Issue #406 — ListView EditItemTemplate Not Rendering (2026-03-02) - **Bug:** Clicking Edit in a ListView with EditItemTemplate fired the ItemEditing event and set EditIndex correctly, but the ListView did not visually swap from ItemTemplate to EditItemTemplate. @@ -125,3 +112,11 @@ Team updates: Unified release process (PR #408), Skins & Themes roadmap (3 waves Team update (2026-03-02): ModelErrorMessage component spec consolidated 29/29 WingtipToys coverage, BaseStyledComponent, EditContext pattern decided by Forge +### ListView CRUD Events — Correctness Fixes (2026-03-03) + +- **Issue #356 audit:** All 16 CRUD events were already declared (EventCallback parameters + EventArgs classes + HandleCommand routing) from M7 and M21. The issue was open because the work was done incrementally across milestones. +- **Bug 1 — ItemCreated firing wrong:** Was `EventCallback` (no type param) firing once in `OnAfterRenderAsync(firstRender)`. Web Forms fires `ItemCreated` with `ListViewItemEventArgs` per-item during data binding, BEFORE `ItemDataBound`. Fixed: changed to `EventCallback`, added `RaiseItemCreated()` helper, wired per-item in both non-grouped and grouped rendering paths in ListView.razor. +- **Bug 2 — ItemCommand not firing for known commands:** Was only firing for unknown commands (in `default` case of switch). Web Forms fires `ItemCommand` for ALL commands first, then routes to the specific handler (ItemEditing, ItemDeleting, etc.). Fixed: moved `ItemCommand.InvokeAsync()` before the switch statement. +- **Pattern:** Web Forms event order for commands is: ItemCommand → specific event (ItemEditing/ItemDeleting/etc.). ItemCreated fires per-item before ItemDataBound. These are documented Web Forms lifecycle behaviors that must be matched. +- **EventArgs completeness:** Web Forms EventArgs have IOrderedDictionary properties (Keys, Values, NewValues, OldValues) tied to the DataSource control paradigm. These are deliberately omitted since Blazor has no DataSource controls — consumers work directly with typed objects via templates. + diff --git a/.ai-team/agents/forge/history.md b/.ai-team/agents/forge/history.md index 087cad296..c15a1b78a 100644 --- a/.ai-team/agents/forge/history.md +++ b/.ai-team/agents/forge/history.md @@ -230,3 +230,29 @@ Jeff's initial catalog listed RequiredFieldValidator, RegularExpressionValidator � Team update (2026-03-02): ModelErrorMessage documentation shipped docs/ValidationControls/ModelErrorMessage.md, status.md updated to 52 components decided by Beast +### Summary: Migration Toolkit Package Design (2026-03-03) + +**By:** Forge +**What:** Designed the structure and content inventory for a portable migration toolkit package at `/migration-toolkit/`. 9 documents total. Analyzed all existing skills, scripts, agents, and the executive report to determine what can be extracted vs. what's net-new. + +**Key decisions:** +- Toolkit lives at `/migration-toolkit/` (top-level, not in docs/ or .github/) because migration is the primary product per Jeff's reframing +- Toolkit REFERENCES existing artifacts (scripts, skills, agent) by relative path — no duplication +- 5 documents are primarily extraction/adaptation from existing content; 4 are primarily net-new +- Highest-value deliverable is `copilot-instructions-template.md` — a drop-in template developers copy into their own project to give Copilot migration context +- `CHECKLIST.md` is fully net-new — no existing per-page migration checklist exists + +**Existing content reusable:** +- `.github/skills/webforms-migration/SKILL.md` — the core Layer 2 skill, heavily referenced by QUICKSTART, CONTROL-COVERAGE, and copilot-instructions-template +- `.github/agents/migration.agent.md` — Layer 3 decision frameworks, extracted into ARCHITECTURE-GUIDE +- `planning-docs/WINGTIPTOYS-MIGRATION-EXECUTIVE-REPORT.md` — metrics, timelines, screenshots for METHODOLOGY and CASE-STUDY +- `scripts/bwfc-scan.ps1` and `scripts/bwfc-migrate.ps1` — referenced as-is, not modified +- Forge's own history entries on WingtipToys analysis, CSS fidelity audit, and ASPX tooling strategy — lessons learned for CASE-STUDY and FAQ + +**Net-new content needed:** +- README.md (entry point), QUICKSTART.md (step-by-step), CHECKLIST.md (per-page template), copilot-instructions-template.md (Copilot handoff with project-specific placeholders) + +**Design doc:** `planning-docs/MIGRATION-TOOLKIT-DESIGN.md` + + + Team update (2026-03-03): Migration toolkit pivoted from 9-doc folder to single SKILL.md in Copilot skill format decided by Jeffrey T. Fritz diff --git a/.ai-team/decisions.md b/.ai-team/decisions.md index 86728836c..c7a297aba 100644 --- a/.ai-team/decisions.md +++ b/.ai-team/decisions.md @@ -5494,3 +5494,14 @@ Nothing is rendered (the component returns `null` / empty fragment). This matche **Date:** 2026-03-02 **What:** Side-by-side comparison found 7 CSS/visual differences: (1) Wrong Bootstrap theme stock BS3 instead of Bootswatch Cerulean, (2) Single-column product grid instead of 4-column, (3) Missing Trucks category, (4) Site.css not referenced, (5) BoundField DataFormatString bug premature .ToString() loses numeric formatting, (6) bootstrap-theme.min.css adding unwanted gradients, (7) Cart prices missing dollar sign (symptom of #5). Fixes: replace CDN with local Cerulean CSS, add GroupItemCount/templates to ListView, add Trucks category, fix BoundField.razor.cs line 48. **Why:** Migration showcase screenshots must visually match the original. The BoundField bug is a library-level defect affecting all DataFormatString consumers. + +### 2026-03-03: ListView CRUD events — correctness fixes for ItemCreated and ItemCommand + +**By:** Cyclops +**What:** Fixed two Web Forms lifecycle deviations in ListView: (1) `ItemCreated` changed from `EventCallback` firing once on first render to `EventCallback` firing per-item before `ItemDataBound` in both grouped and non-grouped paths; (2) `ItemCommand` now fires for ALL commands before routing to specific handlers (Edit, Delete, Update, etc.), not just for unknown commands. +**Why:** Web Forms fires `ItemCommand` first for every command, then the specific event. `ItemCreated` fires per-item during data binding. These are documented lifecycle behaviors that migration code depends on. The IOrderedDictionary properties (Keys, Values, NewValues, OldValues) from Web Forms EventArgs are deliberately omitted — they're tied to the DataSource control paradigm that doesn't exist in Blazor. + +### 2026-03-03: Migration toolkit delivery format (consolidated) +**By:** Forge, Jeffrey T. Fritz +**What:** Forge designed a migration toolkit package with 9 documents in \/migration-toolkit/\ (README, QUICKSTART, METHODOLOGY, ARCHITECTURE-GUIDE, CONTROL-COVERAGE, CASE-STUDY, FAQ, CHECKLIST, copilot-instructions-template) referencing existing scripts, Copilot skill, and migration agent. Full design: \planning-docs/MIGRATION-TOOLKIT-DESIGN.md\. Jeff then directed a pivot: instead of 9 separate docs, deliver a single SKILL.md in GitHub Copilot skill format containing migration instructions plus links to the BWFC NuGet package. +**Why:** The component library, scripts, skills, and agent exist but lack a unified entry point for developers. Forge's design addressed this with a comprehensive document set. Jeff refined the delivery format to a single portable skill file, which is directly consumable by Copilot instances simpler distribution, same content goals. diff --git a/.ai-team/decisions/inbox/beast-bwfc-migration-skill.md b/.ai-team/decisions/inbox/beast-bwfc-migration-skill.md new file mode 100644 index 000000000..a85c3027d --- /dev/null +++ b/.ai-team/decisions/inbox/beast-bwfc-migration-skill.md @@ -0,0 +1,35 @@ +# Decision: Distributable BWFC Migration Skill + +**By:** Beast (Technical Writer) +**Date:** 2026-03-03 +**Context:** Jeff pivoted from 9-doc migration toolkit to single Copilot skill file + +## What + +Created `.github/skills/bwfc-migration/SKILL.md` — a distributable GitHub Copilot skill file designed to be copied into any project's `.github/skills/` folder to teach Copilot how to migrate that project from Web Forms to Blazor using BWFC. + +This is DIFFERENT from `.github/skills/webforms-migration/SKILL.md` (internal project skill). The new skill is external-facing and self-contained. + +## Key Design Decisions + +1. **Single file, not 9 documents.** Jeff explicitly changed direction: "I'd rather this deliver a skill then instructions for the AI agent." All toolkit content consolidated into one SKILL.md with GitHub Copilot skill frontmatter format. + +2. **Self-contained / NuGet-first.** Zero references to internal repo paths (`scripts/bwfc-scan.ps1`, `.ai-team/`, `planning-docs/`). BWFC is installed from NuGet (`dotnet add package Fritz.BlazorWebFormsComponents`). The file works when dropped into any project. + +3. **Copilot-optimized, not human-optimized.** Tables over prose. Exact code transforms. Literal before/after examples. Written for a Copilot instance that reads instructions literally. + +4. **Preserves existing internal skill.** The `webforms-migration/SKILL.md` remains unchanged for internal project use (e.g., WingtipToys migration). The new `bwfc-migration/SKILL.md` is the external-facing version. + +5. **Architecture decision templates are new content.** The 10 decision templates (Session→DI, Identity→Blazor Identity, EF6→EF Core, etc.) were synthesized from Forge's ARCHITECTURE-GUIDE design and the migration agent. This is the biggest content addition vs. the internal skill. + +6. **Honest about limitations.** Explicitly lists what BWFC does NOT cover: DataSource controls, Wizard, Web Parts, AJAX Toolkit extenders. Provides recommended alternatives for each. + +## Why + +Jeff reframed the project deliverable: the final product is a migration acceleration system, and the Copilot skill is the primary user-facing interface. A single skill file is more portable, discoverable, and Copilot-native than a folder of markdown documents. + +## Impact on Other Agents + +- **Forge/Cyclops:** If BWFC components are added/removed or APIs change, the `bwfc-migration` skill needs updating (control translation table, component coverage summary). +- **All:** The `migration-toolkit/` folder documents (README, QUICKSTART, etc.) still exist but are now secondary artifacts. The skill is the primary deliverable. +- **Jubilee:** No sample page changes needed. diff --git a/.ai-team/decisions/inbox/beast-migration-toolkit-content.md b/.ai-team/decisions/inbox/beast-migration-toolkit-content.md new file mode 100644 index 000000000..1d4d32e70 --- /dev/null +++ b/.ai-team/decisions/inbox/beast-migration-toolkit-content.md @@ -0,0 +1,33 @@ +# Decision: Migration Toolkit Content Structure + +**By:** Beast (Technical Writer) +**Date:** 2026-03-03 +**Context:** Migration toolkit authoring per Forge's MIGRATION-TOOLKIT-DESIGN.md + +## What + +Created 6 priority documents in `/migration-toolkit/` following Forge's design: +1. README.md (entry point) +2. QUICKSTART.md (step-by-step) +3. CONTROL-COVERAGE.md (52-component table) +4. METHODOLOGY.md (three-layer pipeline) +5. CHECKLIST.md (per-page template) +6. copilot-instructions-template.md (drop-in Copilot config) + +## Key Content Decisions + +1. **copilot-instructions-template.md is self-contained** — unlike other toolkit docs that use relative links to scripts/skill/agent, this template includes condensed migration rules inline. Reason: developers copy this file into their own project where BWFC relative paths don't exist. It must work standalone. + +2. **CONTROL-COVERAGE.md is the single coverage table** — other toolkit docs link to it rather than duplicating the 52-component table. This follows Forge's "no duplication" directive. + +3. **Remaining 3 documents deferred** — ARCHITECTURE-GUIDE.md, FAQ.md, and CASE-STUDY.md from the design are not yet written. They are lower priority per Forge's priority ordering. Can be authored in a follow-up. + +## Why + +Jeff reframed the project as a "migration acceleration system." The toolkit is the user-facing product documentation for that system. These 6 docs cover the critical path from discovery to execution. + +## Impact on Other Agents + +- **Cyclops/Forge:** If scripts (`bwfc-scan.ps1`, `bwfc-migrate.ps1`) or skill (`SKILL.md`) change parameters or behavior, toolkit docs may need updates (especially QUICKSTART.md and copilot-instructions-template.md). +- **Jubilee:** The QUICKSTART references `samples/AfterWingtipToys/` as reference implementation. +- **All:** Three remaining docs (ARCHITECTURE-GUIDE.md, FAQ.md, CASE-STUDY.md) can be authored when prioritized. diff --git a/.ai-team/log/2026-03-03-listview-crud-and-toolkit.md b/.ai-team/log/2026-03-03-listview-crud-and-toolkit.md new file mode 100644 index 000000000..0376c72cd --- /dev/null +++ b/.ai-team/log/2026-03-03-listview-crud-and-toolkit.md @@ -0,0 +1,19 @@ +# Session: 2026-03-03 — ListView CRUD & Migration Toolkit + +**Requested by:** Jeffrey T. Fritz +**Branch:** squad/listview-crud-and-toolkit + +## What happened + +- **PR #414** (WingtipToys features) merged to dev. +- **Cyclops** fixed 2 ListView CRUD event bugs: + - `ItemCreated` changed from single-fire `EventCallback` to per-item `EventCallback`, firing before `ItemDataBound` in both grouped and non-grouped paths. + - `ItemCommand` now fires for ALL commands before routing to specific handlers (Edit, Delete, Update, etc.), matching Web Forms lifecycle. + - 43 tests pass. +- **Forge** designed migration toolkit package structure (9 documents in `/migration-toolkit/`). Full design: `planning-docs/MIGRATION-TOOLKIT-DESIGN.md`. +- **Beast** writing toolkit content (6 priority documents). + +## Decisions + +- ListView CRUD event correctness fixes (Cyclops) — see decisions.md +- Migration toolkit package design (Forge) — see decisions.md diff --git a/.github/skills/bwfc-data-migration/SKILL.md b/.github/skills/bwfc-data-migration/SKILL.md new file mode 100644 index 000000000..376e8e947 --- /dev/null +++ b/.github/skills/bwfc-data-migration/SKILL.md @@ -0,0 +1,476 @@ +--- +name: bwfc-data-migration +description: "Migrate Web Forms data access and application architecture to Blazor Server. Covers Entity Framework 6 to EF Core, DataSource controls to service injection, Session state to scoped services, Global.asax to Program.cs, Web.config to appsettings.json, routing, HTTP handlers to middleware, and third-party integrations. Use for Layer 3 architecture decisions during Web Forms migration." +--- + +# Web Forms Data Access & Architecture Migration + +This skill covers migrating Web Forms data access patterns and application architecture to Blazor Server. These are the **Layer 3 architecture decisions** that require project-specific judgment. + +**Related skills:** +- `/bwfc-migration` — Core markup migration (controls, expressions, layouts) +- `/bwfc-identity-migration` — Authentication and authorization migration + +--- + +## When to Use This Skill + +Use this skill when you need to: +- Replace `SelectMethod`/`DataSource` controls with service injection +- Migrate Entity Framework 6 to EF Core +- Convert `Session`/`ViewState`/`Application` state to Blazor patterns +- Migrate `Global.asax` to `Program.cs` +- Convert `Web.config` to `appsettings.json` +- Replace HTTP Handlers/Modules with middleware +- Wire up third-party integrations + +--- + +## 1. Entity Framework 6 → EF Core + +**Web Forms:** EF6 with `DbContext` instantiated directly in code-behind or via `SelectMethod`. +**Blazor:** EF Core with `IDbContextFactory` registered in DI. + +```csharp +// Web Forms — direct DbContext in code-behind +public IQueryable GetProducts() +{ + var db = new ProductContext(); + return db.Products; +} +``` + +```csharp +// Blazor — Program.cs +builder.Services.AddDbContextFactory(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); +``` + +```csharp +// Blazor — Service layer +public class ProductService(IDbContextFactory factory) +{ + public async Task> GetProductsAsync() + { + using var db = factory.CreateDbContext(); + return await db.Products.ToListAsync(); + } + + public async Task GetProductAsync(int id) + { + using var db = factory.CreateDbContext(); + return await db.Products.FindAsync(id); + } +} +``` + +> **Critical:** Use `IDbContextFactory`, NOT `AddDbContext`, for Blazor Server. Blazor circuits are long-lived — a single `DbContext` accumulates stale data and tracking issues. + +### EF6 → EF Core API Changes + +| EF6 | EF Core | Notes | +|-----|---------|-------| +| `using System.Data.Entity;` | `using Microsoft.EntityFrameworkCore;` | Namespace change | +| `DbModelBuilder` in `OnModelCreating` | `ModelBuilder` | Same concepts, different API | +| `HasRequired()` / `HasOptional()` | Navigation properties + `IsRequired()` | Simpler relationship config | +| `Database.SetInitializer(...)` | `Database.EnsureCreated()` or Migrations | Different init strategy | +| `db.Products.Include("Category")` | `db.Products.Include(p => p.Category)` | Prefer lambda includes | +| `WillCascadeOnDelete(false)` | `.OnDelete(DeleteBehavior.Restrict)` | Cascade config | +| `.HasDatabaseGeneratedOption(...)` | `.ValueGeneratedOnAdd()` | Key generation | + +### Connection String Migration + +```xml + + + + +``` + +```json +// Blazor — appsettings.json +{ + "ConnectionStrings": { + "DefaultConnection": "Data Source=(LocalDb)\\MSSQLLocalDB;Initial Catalog=MyApp;Integrated Security=True" + } +} +``` + +--- + +## 2. DataSource Controls → Service Injection + +Web Forms `DataSource` controls have **no BWFC equivalent**. Replace with injected services. + +```xml + + + +``` + +```razor +@* Blazor — service injection *@ +@inject IProductService ProductService + + + +@code { + private List products = new(); + + protected override async Task OnInitializedAsync() + { + products = await ProductService.GetProductsAsync(); + } +} +``` + +### Service Registration Pattern + +```csharp +// Program.cs +builder.Services.AddDbContextFactory(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +``` + +### SelectMethod → Service Method Mapping + +| Web Forms SelectMethod | Blazor Service Call | +|----------------------|---------------------| +| `SelectMethod="GetProducts"` | `products = await ProductService.GetProductsAsync();` | +| `SelectMethod="GetProduct"` | `product = await ProductService.GetProductAsync(id);` | +| `InsertMethod="InsertProduct"` | `await ProductService.InsertAsync(product);` | +| `UpdateMethod="UpdateProduct"` | `await ProductService.UpdateAsync(product);` | +| `DeleteMethod="DeleteProduct"` | `await ProductService.DeleteAsync(id);` | + +--- + +## 3. Session State → Scoped Services + +**Web Forms:** `Session["key"]` dictionary accessed anywhere. +**Blazor:** Scoped services via DI. For browser persistence, use `ProtectedSessionStorage`. + +```csharp +// Web Forms +Session["ShoppingCart"] = cart; +var cart = (ShoppingCart)Session["ShoppingCart"]; +``` + +```csharp +// Blazor — Scoped service (in-memory, per-circuit) +public class CartService +{ + public ShoppingCart Cart { get; set; } = new(); + public void AddItem(Product product, int quantity = 1) { ... } + public decimal GetTotal() => Cart.Items.Sum(i => i.Price * i.Quantity); +} + +// Program.cs +builder.Services.AddScoped(); + +// Component +@inject CartService CartService +``` + +### State Storage Options + +| Web Forms | Blazor Equivalent | Scope | +|-----------|------------------|-------| +| `Session["key"]` | Scoped service | Per-circuit (lost on disconnect) | +| `Session["key"]` (persistent) | `ProtectedSessionStorage` | Browser session tab | +| `Application["key"]` | Singleton service | App-wide | +| `Cache["key"]` | `IMemoryCache` or `IDistributedCache` | Configurable | +| `ViewState["key"]` | Component fields/properties | Per-component | +| `TempData["key"]` | `ProtectedSessionStorage` | One read | +| `Cookies` | `ProtectedLocalStorage` or HTTP endpoints | Browser | + +### ProtectedSessionStorage Example + +```razor +@inject ProtectedSessionStorage SessionStorage + +@code { + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + var result = await SessionStorage.GetAsync("cart"); + cart = result.Success ? result.Value! : new ShoppingCart(); + } + } + + private async Task SaveCart() + { + await SessionStorage.SetAsync("cart", cart); + } +} +``` + +> **Note:** `ProtectedSessionStorage` only works after the first render (it requires JS interop). Always check in `OnAfterRenderAsync`, not `OnInitializedAsync`. + +--- + +## 4. Global.asax → Program.cs + +```csharp +// Web Forms — Global.asax +protected void Application_Start(object sender, EventArgs e) +{ + RouteConfig.RegisterRoutes(RouteTable.Routes); + BundleConfig.RegisterBundles(BundleTable.Bundles); +} + +protected void Application_Error(object sender, EventArgs e) +{ + var ex = Server.GetLastError(); + Logger.LogError(ex); +} + +protected void Session_Start(object sender, EventArgs e) +{ + Session["Cart"] = new ShoppingCart(); +} +``` + +```csharp +// Blazor — Program.cs +var builder = WebApplication.CreateBuilder(args); + +// Services (replaces Application_Start registrations) +builder.Services.AddRazorComponents().AddInteractiveServerComponents(); +builder.Services.AddBlazorWebFormsComponents(); +builder.Services.AddDbContextFactory(options => ...); +builder.Services.AddScoped(); // replaces Session_Start + +var app = builder.Build(); + +// Middleware pipeline +app.UseExceptionHandler("/Error"); // replaces Application_Error +app.UseStaticFiles(); +app.UseRouting(); +app.UseAuthentication(); +app.UseAuthorization(); +app.UseAntiforgery(); +app.MapRazorComponents().AddInteractiveServerRenderMode(); + +app.Run(); +``` + +### Global.asax Event → Blazor Equivalent + +| Global.asax Event | Blazor Equivalent | +|-------------------|-------------------| +| `Application_Start` | `Program.cs` — service registration and app configuration | +| `Application_Error` | `app.UseExceptionHandler(...)` middleware | +| `Session_Start` | Scoped service constructor (lazy init) | +| `Session_End` | `IDisposable` on scoped services or circuit handler | +| `Application_BeginRequest` | Custom middleware | +| `Application_EndRequest` | Custom middleware | + +--- + +## 5. Web.config → appsettings.json + +```xml + + + + + +``` + +```json +// Blazor — appsettings.json +{ + "PayPal": { + "Mode": "sandbox" + }, + "MaxItemsPerPage": 20 +} +``` + +```csharp +// Web Forms access +var mode = ConfigurationManager.AppSettings["PayPal:Mode"]; + +// Blazor access — IConfiguration +@inject IConfiguration Config +var mode = Config["PayPal:Mode"]; + +// Blazor access — Options pattern (recommended) +builder.Services.Configure(builder.Configuration.GetSection("PayPal")); +@inject IOptions PayPalOptions +var mode = PayPalOptions.Value.Mode; +``` + +--- + +## 6. Route Table → @page Directives + +```csharp +// Web Forms — RouteConfig.cs +routes.MapPageRoute("ProductRoute", "Product/{productId}", "~/ProductDetail.aspx"); +routes.MapPageRoute("CategoryRoute", "Category/{categoryId}", "~/ProductList.aspx"); +``` + +```razor +@* Blazor — ProductDetail.razor *@ +@page "/Product/{ProductId:int}" +@code { + [Parameter] public int ProductId { get; set; } +} + +@* Blazor — ProductList.razor *@ +@page "/Category/{CategoryId:int}" +@code { + [Parameter] public int CategoryId { get; set; } +} +``` + +### URL Pattern Conversion + +| Web Forms Route Pattern | Blazor @page Pattern | +|------------------------|---------------------| +| `{id}` | `{Id:int}` (add type constraint) | +| `{name}` | `{Name}` (string, no constraint needed) | +| `{category}/{subcategory}` | `{Category}/{Subcategory}` | +| Optional: `{id?}` | `{Id:int?}` | +| Default: `{action=Index}` | Multiple `@page` directives | + +### Friendly URLs + +```csharp +// Web Forms — FriendlyUrls +routes.EnableFriendlyUrls(); +// Maps Products.aspx → /Products, Products/Details/5 → Products.aspx?id=5 + +// Blazor — direct @page mapping +@page "/Products" +@page "/Products/Details/{Id:int}" +``` + +--- + +## 7. HTTP Handlers/Modules → Middleware + +```csharp +// Web Forms — IHttpHandler +public class ImageHandler : IHttpHandler +{ + public void ProcessRequest(HttpContext context) + { + var id = context.Request.QueryString["id"]; + // serve image + } + public bool IsReusable => true; +} +``` + +```csharp +// Blazor — Minimal API endpoint +app.MapGet("/api/images/{id}", async (int id, ImageService svc) => +{ + var image = await svc.GetImageAsync(id); + return Results.File(image.Data, image.ContentType); +}); +``` + +```csharp +// Web Forms — IHttpModule +public class LoggingModule : IHttpModule +{ + public void Init(HttpApplication context) + { + context.BeginRequest += (s, e) => Log("Begin: " + context.Request.Url); + } +} +``` + +```csharp +// Blazor — Middleware +app.Use(async (context, next) => +{ + Log($"Begin: {context.Request.Path}"); + await next(context); +}); +``` + +--- + +## 8. Third-Party Integrations → HttpClient + +```csharp +// Web Forms — WebRequest/WebClient +var request = WebRequest.Create("https://api.paypal.com/v1/payments"); +request.Method = "POST"; +// ... manual serialization and error handling +``` + +```csharp +// Blazor — Program.cs +builder.Services.AddHttpClient("PayPal", client => +{ + client.BaseAddress = new Uri("https://api.paypal.com/v1/"); +}); + +// Blazor — Service +public class PayPalService(IHttpClientFactory factory) +{ + public async Task CreatePaymentAsync(Order order) + { + var client = factory.CreateClient("PayPal"); + var response = await client.PostAsJsonAsync("payments", order); + return await response.Content.ReadFromJsonAsync()!; + } +} +``` + +--- + +## Files to Create During Migration + +| File | Purpose | Replaces | +|------|---------|----------| +| `Program.cs` | Service registration, middleware | `Global.asax`, `Startup.cs`, `RouteConfig.cs` | +| `appsettings.json` | Configuration | `Web.config` `` and `` | +| `App.razor` | Root component with Router | `Default.aspx` (entry point) | +| `_Imports.razor` | Global usings | `Web.config` `` | +| `Components/Layout/MainLayout.razor` | Application layout | `Site.Master` | +| `Components/Pages/*.razor` | Pages | `*.aspx` files | +| `Services/*.cs` | Data access services | `SelectMethod`s, DataSource controls, code-behind queries | +| `Models/*.cs` | Domain models | Copy from Web Forms project | + +--- + +## Common Data Migration Gotchas + +### DbContext Lifetime +Blazor Server circuits are long-lived. Always use `IDbContextFactory` and create short-lived `DbContext` instances per operation. + +### No Page-Level Transaction Scope +Web Forms `SelectMethod` runs inside a page lifecycle. Blazor doesn't have this. Use explicit transaction scopes in services if needed: +```csharp +using var db = factory.CreateDbContext(); +using var transaction = await db.Database.BeginTransactionAsync(); +// ... operations +await transaction.CommitAsync(); +``` + +### Async All the Way +Web Forms `SelectMethod` returns `IQueryable` synchronously. Blazor services should be async: +```csharp +// WRONG: return db.Products.ToList(); +// RIGHT: return await db.Products.ToListAsync(); +``` + +### No ConfigurationManager +`ConfigurationManager.AppSettings["key"]` doesn't exist. Inject `IConfiguration` or use the Options pattern. + +### Static Helpers with HttpContext +Web Forms often has static helper classes that access `HttpContext.Current`. These must be refactored to accept dependencies via constructor injection. diff --git a/.github/skills/bwfc-identity-migration/SKILL.md b/.github/skills/bwfc-identity-migration/SKILL.md new file mode 100644 index 000000000..d31ff2695 --- /dev/null +++ b/.github/skills/bwfc-identity-migration/SKILL.md @@ -0,0 +1,338 @@ +--- +name: bwfc-identity-migration +description: "Migrate ASP.NET Web Forms Identity and Membership authentication to Blazor Server Identity. Covers OWIN to ASP.NET Core auth middleware, login page migration, BWFC login controls, role-based authorization, and AuthorizeView patterns. Use when migrating authentication and authorization from a Web Forms application." +--- + +# Web Forms Identity → Blazor Identity Migration + +This skill covers migrating ASP.NET Web Forms authentication (Identity, Membership, FormsAuthentication) to Blazor Server using ASP.NET Core Identity. + +**Related skills:** +- `/bwfc-migration` — Core markup migration (controls, expressions, layouts) +- `/bwfc-data-migration` — EF6 → EF Core, data access, architecture decisions + +--- + +## Overview + +Web Forms authentication typically uses one of three systems. The migration path depends on which one: + +| Web Forms Auth System | Era | Blazor Migration Path | +|----------------------|-----|----------------------| +| ASP.NET Identity (OWIN) | 2013+ | ASP.NET Core Identity (closest match) | +| ASP.NET Membership | 2005-2013 | ASP.NET Core Identity (schema migration required) | +| FormsAuthentication | 2002-2005 | ASP.NET Core Identity or cookie auth | + +--- + +## Step 1: Add Identity Packages + +```bash +dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore +dotnet add package Microsoft.AspNetCore.Identity.UI +``` + +## Step 2: Configure Identity in Program.cs + +```csharp +// Program.cs +builder.Services.AddIdentity(options => +{ + options.SignIn.RequireConfirmedAccount = false; + options.Password.RequiredLength = 6; +}) + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + +// Required for Blazor Server +builder.Services.AddCascadingAuthenticationState(); + +// Middleware pipeline (ORDER MATTERS) +app.UseAuthentication(); +app.UseAuthorization(); +``` + +## Step 3: Create ApplicationUser and DbContext + +```csharp +// Models/ApplicationUser.cs +using Microsoft.AspNetCore.Identity; + +public class ApplicationUser : IdentityUser +{ + // Add custom properties from your Web Forms ApplicationUser +} + +// Data/ApplicationDbContext.cs +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +public class ApplicationDbContext : IdentityDbContext +{ + public ApplicationDbContext(DbContextOptions options) + : base(options) { } +} +``` + +## Step 4: Migrate the Database Schema + +If migrating from ASP.NET Identity (OWIN), the schema is similar but not identical: + +```bash +dotnet ef migrations add IdentityMigration +dotnet ef database update +``` + +**Key schema differences:** + +| ASP.NET Identity (OWIN) | ASP.NET Core Identity | +|--------------------------|----------------------| +| `AspNetUsers.Id` (string GUID) | Same | +| `AspNetUsers.PasswordHash` | Same format — passwords are compatible | +| `AspNetUserClaims` | Same | +| `AspNetUserRoles` | Same | +| `AspNetRoles` | Same | +| `__MigrationHistory` | `__EFMigrationsHistory` | + +> **Important:** ASP.NET Identity v2 password hashes (from Web Forms) are compatible with ASP.NET Core Identity. Users will NOT need to reset passwords. + +If migrating from **Membership** (older): +- Use the `Microsoft.AspNetCore.Identity.MicrosoftAccountMigration` or a custom SQL migration script +- Membership password hashes are NOT compatible — users will need password resets + +--- + +## Step 5: Migrate Login Pages + +### BWFC Login Controls + +BWFC provides login controls that match Web Forms markup. Use these during migration, then optionally replace with Blazor Identity UI later. + +| Web Forms | BWFC | Notes | +|-----------|------|-------| +| `` | `` | Login form with username/password | +| `` | `` | Displays authenticated username | +| `` | `` | Login/Logout toggle link | +| `` | `` | Shows different content for anon vs auth users | +| `` | `` | Registration form | +| `` | `` | Password change form | +| `` | `` | Password reset flow | + +### Login Page Migration Example + +```html + +<%@ Page Title="Log in" MasterPageFile="~/Site.Master" ... %> + +

Log in

+ + + + + + + + +
+``` + +```razor +@* Blazor — Login.razor *@ +@page "/Account/Login" + +

Log in

+ + + + + +