- Owner: Jeffrey T. Fritz
- Project: BlazorWebFormsComponents Blazor components emulating ASP.NET Web Forms controls for migration
- Stack: C#, Blazor, .NET, ASP.NET Web Forms, bUnit, xUnit, MkDocs, Playwright
- Created: 2026-02-10
Role: Component Developer & Lead Toolsmith
Expertise: Blazor component implementation, L1/L2 migration scripts, custom controls, data binding, Web Forms patterns
- Core component development and pattern establishment (BaseWebFormsComponent, DataBoundComponent, TemplatedWebControl)
- Migration toolkit engineering (L1 script: bwfc-migrate.ps1; L2 transforms)
- Custom control migration patterns (WebControl, DataBoundWebControl, TemplatedWebControl base classes)
- EDMX parser integration for EF6 EF Core migrations
📌 Team update (2026-04-11): WebFormsForm (Issue #533) complete — FormSubmitEventArgs created by Coordinator. Cyclops built WebFormsForm.razor + ES module JS interop (bwfc-webformsform.js). Rogue delivered 39 bUnit tests (FormShimTests.cs, WebFormsFormTests.razor). FormShim dual-mode support enabled (IFormCollection + Dictionary<string,StringValues>). RequestShim SetRequestFormData() accepts form event args. @inherits ComponentBase fix applied to WebFormsForm. All 4 agents synchronized. Wave 2 (Jubilee/Beast/Colossus) ready. — decided by Coordinator
📌 Team update (2026-03-27): Multi-targeting #516 implemented and validated. BlazorWebFormsComponents now ships net8.0;net9.0;net10.0. All 2606 tests pass × 3 TFMs = 7818 total. Zero code changes, conditional package versions only. CI matrix configured. Ready for GA release. — decided by Forge & Cyclops
- L1 Script (bwfc-migrate.ps1): 15/15 test suite (100%), covers 5 core patterns (GetRouteUrl, ContentWrappers, WebFormsAttributes, DataSourceID, Response.Redirect)
- UpdatePanel enhancement: BaseStyledComponent with ContentTemplate RenderFragment, 24 tests, 0 warnings
- Students GridView LEFT JOIN fix: Fixed data-loss bug in enrollment display
- EDMXEF Core parser: Standalone script + L1 integration, handles Model1.edmx 5 entities, 4 FK relationships
- HttpHandlerBase implementation: 7 files, IEndpointConventionBuilder pattern, 94 tests passing
- Issue #472: L1 Script bug fixes (3 bugs fixed, test suite 7/10 15/15)
- Issue #451: UpdatePanel ContentTemplate feature
- Issue #475: Students GridView LEFT JOIN fix
- Issue #488: EDMXEF Core parser + L1 integration
- HttpHandlerBase: 7-file Handlers/ structure, Session markers, build 0 errors
-
Component Base Classes:
- BaseWebFormsComponent: Standard controls (properties, events)
- BaseStyledComponent: Style + CssClass support
- DataBoundComponent: Data binding (DataSource, DataBind(), ItemDataBound)
- TemplatedWebControl: RenderFragment templates (RenderTemplate helper)
- WebControl, DataBoundWebControl, TemplatedWebControl: Custom control base classes
-
L1 Script Architecture:
- Modular conversion functions (scope-specific regexes)
- Lookahead validation to prevent false-positive conversions
- Test-driven bug fixes; new patterns need test case coverage
- ItemType standardization across all data-bound controls
-
EDMX Migration:
- Parse 3 XML sections in order: C-S Mapping CSDL SSDL
- Generate entities with EF Core annotations ([Key], [Required], [MaxLength], etc.)
- Generate DbContext with OnModelCreating() fluent chains
- Skip-existing behavior for re-runs
- 15/15 L1 tests (100% pass rate)
- 94 migration tests passing across all runs
- 0 build errors post-update
- 24/24 UpdatePanel tests passing
- Cross-browser acceptance tests (Playwright)
📌 Team update (2026-03-24): ViewState Phase 1 implementation complete & merged — feature/viewstate-postback-shim ready. ViewStateDictionary core, mode-adaptive IsPostBack, SSR hidden field round-trip, IDataProtectionProvider integration, CryptographicException fallback. All 2588 tests pass (2 breaking contracts fixed by Coordinator). Phase 2 (SSR persistence integration) approved. — decided by Cyclops
📌 Team update (2026-03-17): HttpHandlerBase implementation validated by Rogue — 94 tests passing, all adapter patterns verified correct. Commit 040fbad5 (15 files, 3218 insertions) on feature/httphandler-base ready for integration. — decided by Rogue
📌 Team update (2026-03-17): Fixed #471 (GUID IDs) and #472 (L1 script). CheckBox/RadioButton/RadioButtonList now use ClientID exclusively; no GUID fallbacks. L1 script test suite: 7/10 → 15/15 (100%). All 2105 tests pass. — decided by Cyclops
📌 Team update (2026-03-16): Forge reviewed Component Health Dashboard PRD; 3 errata items identified before Cyclops implementation. (1) Appendix A: ToolTip base class error. (2) tools/WebFormsPropertyCounter/ doesn't exist—use MSDN curation as Phase 1 primary. (3) Acceptance criterion #9 needs verification (Login controls had 0 bUnit tests as of 2026-03-15). See decisions.md for full details. — decided by Forge
- Core Context (2026-02-10 through 2026-02-27)
- M17-M20 Wave 1 Context (2026-02-27 through 2026-03-01)
- M20 Theming through Migration Benchmarks (2026-03-01 through 2026-03-04)
- Script & Toolkit Summary (2026-03-02 through 2026-03-04)
- GetRouteUrl, Run 5 & Toolkit Sync Summary (2026-03-04 through 2026-03-05)
- Run 6 Script Enhancements (2026-03-05)
- @rendermode Scaffold Fix (2026-03-05)
- WebFormsPageBase Implementation (2026-03-05)
- WebFormsPage IPageService Consolidation (2026-03-05)
- LoginView Migration Script Fix (2026-03-06)
- Run 9 Script Fixes — 9 RF items (2026-03-06)
- Layer 2 AfterWingtipToys Build Conversion (2026-03-06)
Run 6: 4 script enhancements (TFM, SelectMethod TODO, wwwroot copy, stubs). @rendermode fix: removed standalone directive from _Imports.razor scaffold — @rendermode is a directive attribute for component instances only. WebFormsPageBase: ComponentBase subclass with Page => this, Title/MetaDescription/MetaKeywords delegates, IsPostBack => false. WebFormsPage consolidation: merged Page.razor head rendering into WebFormsPage via Option B. LoginView script fix: <asp:LoginView> → <LoginView> (not AuthorizeView), preserve template names. Run 9: 9 script fixes (Models copy, DbContext transform, EF6→EF Core, redirect detection, Program.cs boilerplate, Page Title extraction, QueryString/RouteData annotations, ListView GroupItemCount, csproj packages). Layer 2: full AfterWingtipToys conversion — key pattern: layout code-behind class name MUST match .razor filename. Auth pages use plain HTML forms with HTTP endpoints.
- Run 11 -- Complete WingtipToys Migration from Scratch (2026-03-07)
- Run 11 Script Fixes -- Fix 1 (Invoke-ScriptAutoDetection) & Fix 2 (Convert-TemplatePlaceholders) (2026-03-07)
- Run 12 -- Complete WingtipToys Migration from Scratch (2026-03-07)
- LoginView Namespace Fix (2026-03-07)
- Run 13 -- Full WingtipToys Migration Pipeline, 25/25 tests (2026-03-08)
Run 11: Fresh WingtipToys migration from scratch (105 files, 0 errors). Key patterns: root-level _Imports.razor for pages outside Components/, partial classes must NOT specify : ComponentBase with @inherits WebFormsPageBase, auth pages use plain HTML forms to HTTP endpoints. Run 11 script fixes: Invoke-ScriptAutoDetection (JS files to wwwroot/Scripts/ with correct dependency order) and Convert-TemplatePlaceholders (placeholder elements to @context). Run 12: Full pipeline with Layer 2, established dual DbContext pattern (later superseded by factory-only in Run 13). LoginView namespace fix: @using BlazorWebFormsComponents.LoginControls required in _Imports.razor -- added to script template. Run 13: 25/25 tests passed (100%). Confirmed patterns: SSR default, data-enhance-nav="false" for minimal API links, data-enhance="false" for auth forms, AddDbContextFactory only (no dual registration), middleware order UseAuthentication -> UseAuthorization -> UseAntiforgery, logout must use <a> not <button>.
Team updates (2026-03-07 through 2026-03-08): Coordinator must not perform domain work; FreshWingtipToys must not be committed; migration-standards updated with Static Asset Checklist/ListView Placeholder/Action Links; migration order: fresh project first; SSR default with InteractiveServer opt-in; enhanced nav bypass for minimal API; DbContext factory-only; middleware order confirmed; logout uses link not button.
- ServiceCollectionExtensions Enhancement (2026-03-11)
- Run 18a/18b/18c — Test-UnconvertiblePage False-Positive Investigation (2026-03-11)
- P0 Migration Script Fixes (2026-03-11)
- Standardize Generic Type Params to ItemType (2026-03-11)
- Layer 2 Structural Transform - AfterWingtipToys (2026-03-11)
ServiceCollectionExtensions: Added AddHttpContextAccessor() auto-registration, BlazorWebFormsComponentsOptions with EnableAspxUrlRewriting, UseBlazorWebFormsComponents() middleware, AspxRewriteMiddleware for .aspx → clean URL 301 redirects. Run 18: Fixed Test-UnconvertiblePage false positives — 'Checkout' matched button IDs, 'PayPal' matched image URLs. Both now path-based. ShoppingCart.razor restored to full GridView markup. P0 fixes: (1) Eliminated Test-UnconvertiblePage — always returns $false, pages get TODO annotations instead of stubs. (2) [Parameter] regex fix — TODO on separate line, eliminates 6 CS errors. ItemType standardization: renamed all TItemType/TItem → ItemType across 13 files, 0 build errors. L2 AfterWingtipToys: ~60 files transformed, 0 errors, 58 RZ10012 warnings. Key gotchas: class name must match .razor filename, @inject/[Inject] duplication, #hexcolor → @("..."), LoggedInTemplate has no typed context.
Team updates (2026-03-11): Migration tests reorganized to project/runNN/. Mandatory L1→L2 pipeline with no fixes between layers. All generics standardized to ItemType. Test-UnconvertiblePage eliminated. Run 18 improvements prioritized by Forge.
Summary: ContentTemplate RenderFragment parameter added to UpdatePanel; base class changed to BaseStyledComponent; dual syntax support (Web Forms + Blazor); all 24 tests pass.
Impact: Eliminates RZ10012 warnings during migration. UpdatePanel now supports full styling capabilities. No breaking changes.
📌 Team update (2026-03-13): UpdatePanel enhancement complete — ContentTemplate parameter + BaseStyledComponent base class + dual syntax support. Cyclops (component), Rogue (12 tests, 10 pass now), Jubilee (sample page + ComponentList update), Colossus (3 interaction tests). All 4 UpdatePanel integration tests passing. Decisions merged to decisions.md.
Summary: Fixed data-loss bug in GetJoinedTableData() by replacing INNER JOIN with LEFT JOIN. Verified Playwright test timing already correct.
Impact: Students without enrollments now visible in GridView. Blazor Server form submission timing stable.
📌 Team update (2026-03-14): Students LEFT JOIN fix completed by Cyclops — replaced SelectMany (INNER JOIN) with Students.Include(Enrollments) loop. Students without enrollments appear with Count=0, Date=DateTime.Today. Colossus verified Playwright test timing fixes already in place from previous session. All tests passing. Commit d3dc610f.
Summary: Fixed 3 bugs in bwfc-migrate.ps1 that caused test failures, added 5 new test cases covering all five L1 patterns from issue #472. Test suite: 7/10 → 15/15 (100%), line accuracy: 94.3% → 100%.
Bugs fixed:
ConvertFrom-GetRouteUrl—Eval()regex was global instead of scoped to GetRouteUrl lines. Corrupted<%#: Eval("Name") %>expressions by stripping the Eval wrapper but leaving<%#: %>delimiters. Fix: only apply Eval→context conversion on lines containingGetRouteUrl.ConvertFrom-ContentWrappers—\s*\r?\n?after>consumed leading indentation of the next line. Fix: changed to[ \t]*\r?\n?(horizontal whitespace only).Remove-WebFormsAttributes— ItemType fallback regex(?![^>]*ItemType=)didn't check forTItem=, so tags that already hadItemTypeconverted toTItemgot a duplicateItemType="object". Fix: lookahead now checks(?![^>]*(?:ItemType|TItem)=).
New test cases:
- TC11-BoolEnumUnit: boolean lowercase, enum type-qualifying, unit px-stripping
- TC12-DataSourceID: DataSourceID removal + data source control → TODO replacement
- TC13-ResponseRedirect: Response.Redirect → NavigationManager.NavigateTo in code-behind
- TC14-SessionDetect: Session["key"] detection with migration guidance block
- TC15-ViewState: ViewState["key"] detection with private field suggestions
Test harness enhancements: Extended Run-L1Tests.ps1 to copy .aspx.cs inputs and compare .razor.cs expected output, enabling code-behind transform verification.
Key files: migration-toolkit/scripts/bwfc-migrate.ps1, migration-toolkit/tests/Run-L1Tests.ps1, migration-toolkit/tests/inputs/TC11-TC15*, migration-toolkit/tests/expected/TC11-TC15*
Summary: Implemented 5 script fixes identified from ContosoUniversity Run 22 (39/40 tests, 97.5% pass rate): (1) Strip ContentTemplate wrappers to eliminate RZ10012 warnings, (2) Add dual @page routes for home pages (e.g., /Home + /), (3) Extract PageTitle from TitleContent placeholders, (4) Convert ID= to id= for HTML compatibility, (5) Add [DatabaseGenerated(Computed)] support to EDMX parser.
Files Modified:
migration-toolkit/scripts/bwfc-migrate.ps1(Fixes 1-4)migration-toolkit/scripts/Convert-EdmxToEfCore.ps1(Fix 5)
Key Implementation Details:
- Fix 1 (ContentTemplate): Added after asp: prefix removal in
ConvertFrom-AspPrefix— strips<ContentTemplate>wrapper tags while preserving content. UpdatePanel component accepts ContentTemplate as RenderFragment parameter, but L1 output is cleaner without wrapper tags. - Fix 2 (Home Routes): Added
$isHomePagedetection inConvertFrom-PageDirectivefor Home.aspx/Default.aspx/Index.aspx — generates both@page "/Home"and@page "/"directives. Ensures root URL routing works. - Fix 3 (TitleContent): Added regex in
ConvertFrom-ContentWrappersto extract title from<asp:Content ContentPlaceHolderID="TitleContent">blocks. Stored in$script:ExtractedTitleFromContent, consumed byConvertFrom-PageDirectiveas fallback whenTitleattribute absent. Complements existing Title attribute extraction (Run 9 RF-10). - Fix 4 (ID → id): Added in
Remove-WebFormsAttributesafter ItemType processing — broad regex replacement ofID="value"toid="value". BWFC components accept both, so safe replacement. Ensures CSS/JS selectors work. - Fix 5 (EDMX Computed): Added
IsComputedproperty to entity metadata, generated[DatabaseGenerated(DatabaseGeneratedOption.Computed)]annotation in entity files. Mirrors existingIsIdentitypattern. Prevents EF Core errors on computed columns.
Expected Impact:
- Eliminates 3 RZ10012 warnings (ContentTemplate)
- Fixes 3 HomePageTests (root URL routing)
- Improves browser tab titles (SEO + UX)
- Fixes selector failures from ID casing
- Prevents EF Core computed column errors
Gotchas:
- Fix 3 uses script-scoped variable to pass data between functions — must clear
$script:ExtractedTitleFromContentafter use to avoid cross-file pollution - Fix 2 checks
$isHomePage -and $route -ne '/'to avoid duplicate@page "/"directives - Fix 1 placed after closing tag removal to avoid interfering with asp: prefix stripping
📌 Team update (2026-03-14): Run 22 L1 script fixes implemented by Cyclops — 5 improvements targeting RZ10012 warnings, home page routing, PageTitle extraction, ID normalization, and EDMX computed properties. Decision document written to .squad/decisions/inbox/cyclops-l1-script-fixes.md.
- Run 20 L1 Script Fixes — SelectMethod Preservation + Review Item Noise Reduction (2026-03-12)
- Run 21 — Layer 2 Structural Transform AfterWingtipToys (2026-03-11)
- Layer 2 Structural Transform — AfterContosoUniversity (2026-03-12)
- L1 Script SQLite → SQL Server Fix (2026-03-12)
Run 20 L1 fixes: ConvertFrom-SelectMethod was stripping SelectMethod from markup — fixed to preserve it with TODO for L2 delegate conversion. ContentPlaceHolder and GetRouteUrl review items updated to reference BWFC components. Run 21 WingtipToys L2: 44 files, 0 errors. SelectMethod delegate conversion on 4 data pages (SelectMethod="@GetProducts" etc.), all code-behinds to async lifecycle, enum/type Razor escaping (@GridLines.Vertical), account pages stubbed for Identity migration. CU L2: 0 errors, 5 models to EF Core, 5 BLL classes with IDbContextFactory, EDMX artifacts deleted, raw SQL → LINQ. L1 script: SQLite package/example replaced with SqlServer/LocalDB.
Key gotchas (combined): Code-behind class name MUST match .razor filename. Code-behind files need explicit usings (don't inherit _Imports.razor). Don't duplicate @inject/[Inject]. #hexcolor → @("#hexcolor"). Unit params are bare integers. BWFC uses Sorting EventCallback (not OnSorting). CommandField doesn't exist — use ButtonField. TextBox Text expects string. TemplateField in DetailsView needs ItemType. Style sub-components: <RowStyleContent><GridViewRowStyle .../></RowStyleContent>. SortedAscendingCellStyle not in BWFC. L1 must not strip attributes BWFC supports.
Team update (2026-03-11): NEVER default to SQLite; SelectMethod MUST be preserved as SelectHandler delegates. All skill files hardened, L1 script now scaffolds SQL Server. decided by Jeffrey T. Fritz, Beast, Cyclops
Team update (2026-03-11): ContosoUniversity L2 confirmed SQL Server LocalDB exclusively. Earlier SQLite attempt was rejected. IDbContextFactory pattern with SqlServer provider is canonical. decided by Cyclops
Team update (2026-03-11): Database provider guidance reframed lead with 'detect and match original provider' instead of 'NEVER substitute'. L1 auto-detection connects to L2 verification. decided by Beast (requested by Jeffrey T. Fritz)
- L1 Script — Web.config Database Provider Auto-Detection (2026-03-12)
- Fix: TItem → ItemType in Tests and Samples (2026-03-12)
- L2 Automation Shims — 4 S-sized Library Enhancements (2026-07-25)
- OPP-1: EnumParameter Wrapper Struct (2026-07-25)
L1 script: Added Find-DatabaseProvider parsing Web.config connectionStrings (3-pass: providerName, connection string patterns, EntityClient inner provider). Maps SqlClient→SqlServer, SQLite→Sqlite, Npgsql→PostgreSQL. Uses GetAttribute() for StrictMode safety. TItem→ItemType: 43 files renamed (36 tests + 7 samples) after CI failures from generic param mismatch. L2 shims (OPP-2,3,5,6): Unit implicit string conversion (Parse all CSS units), ResponseShim (strips ~/ and .aspx), ViewState dict with [Obsolete], GetRouteUrl via LinkGenerator+HttpContextAccessor. OPP-1 EnumParameter: readonly struct with implicit string→enum conversion for 55 files across 46 components. Gotchas: switch expressions need .Value, Shouldly needs .Value.ShouldBe(), nullable enums need separate handling.
Team update (2026-03-12): ResponseShim.Redirect null URL bug reported by Rogue — throws NullReferenceException instead of ArgumentNullException. Add null guard. reported by Rogue (QA)
Team update (2026-03-12): Cookie shims must use graceful degradation (Pattern B+), not exceptions. NullResponseCookies for no-op writes, EmptyRequestCookieCollection for null reads, both with ILogger warnings. CookiesAvailable bool escape hatch. decided by Jeffrey T. Fritz Team update (2026-03-12): PageTitle deduplication Page.Title via IPageService is single source of truth. Remove inline from 5 AfterWingtipToys files. Fix Default.razor.cs "Home Page" "Welcome". L1 script: inject BWFC-MIGRATE marker. L2: consume marker, never invent values. decided by Forge (analysis), approved by Jeffrey T. Fritz Team update (2026-03-12): Render mode guards add IsHttpContextAvailable and RequireHttpContext() to WebFormsPageBase. Guard GetRouteUrl. HttpContext != null is the guard, RendererInfo for diagnostics only. decided by Forge
Scope: Full Layer 2 structural transform + build validation of ContosoUniversity sample from raw L1 output to 0-error Blazor Server app.
Build iterations: 4 (NuGet auth → ProjectReference, style content wrappers, BoundField ItemType, color/enum types → 0 errors)
Key transforms (39 files touched):
- 5 EF6 model classes cleaned (removed SuppressMessage, auto-generated comments, added nullable annotations)
- EF Core DbContext rewritten (removed DbModelBuilder, EF6 imports, cleaned to pure EF Core)
- 4 BLL classes fully rewritten with IDbContextFactory DI pattern, raw SQL replaced with LINQ
- 3 new DTO model classes created (EnrollmentStat, StudentListItem, StudentInfo) — anonymous objects don't work with BWFC data binding
- Program.cs: full DI registration (DbContextFactory + 4 BLL services), SQL Server LocalDB connection
- _Imports.razor: added ContosoUniversity.Models + ContosoUniversity.BLL usings
- App.razor: added @rendermode="InteractiveServer" to HeadOutlet and Routes
- MainLayout.razor: fixed .aspx links → Blazor routes, removed duplicate jQuery CDN ref
- 5 page code-behinds: Page_Load → OnInitialized, injected BLL services via [Inject], SelectMethod delegates matching SelectHandler signature
- 5 page razor files: removed ScriptManager/UpdatePanel wrappers, removed ajaxToolkit, replaced CommandField mentions, fixed all data binding
Deleted: Model1.cs, Model1.Designer.cs, Models/Enrollmet_Logic.cs (duplicate), BLL/Students_Logic.cs (duplicate), old BLL classes (4)
Key gotchas confirmed:
- GridView/DetailsView style elements MUST use
*Contentwrappers:<HeaderStyleContent><GridViewHeaderStyle .../></HeaderStyleContent>. Direct<HeaderStyle>is invalid. - Inner style component names follow pattern:
GridView{StyleName}Style/DetailsView{StyleName}Style - Colors on WebControl attributes (BackColor, ForeColor, BorderColor) are
WebColortype — useWebColor.White,WebColor.Blacketc. - Enum attributes (GridLines, BorderStyle, HorizontalAlign) wrapped in
EnumParameter<T>— pass as@("None")or use static member - BoundField inside GridView/DetailsView needs EXPLICIT
ItemType="..."— CascadingTypeParameter doesn't auto-infer for BoundField - PackageReference to Fritz.BlazorWebFormsComponents fails without GitHub Packages auth — use ProjectReference in dev
- BLL namespace must be consistent: standardized to
ContosoUniversity.BLL(uppercase matching folder name) - Anonymous objects from LINQ projections don't work with BWFC data binding — create typed DTOs instead
Scope: New standalone Convert-EdmxToEfCore.ps1 script + integration into bwfc-migrate.ps1 Models section.
What was built:
Convert-EdmxToEfCore.ps1— parses EDMX XML (SSDL/CSDL/C-S Mapping) and generates EF Core entity .cs files with full data annotations + DbContext withOnModelCreating()FK relationships and cascade deletes- Integrated into
bwfc-migrate.ps1at the Models Copy section: detects.edmxfiles, generates EF Core code, skips EDMX artifacts (*.Designer.cs, T4 bootstrap), then proceeds with normal.cscopy for any user-added models
Tested against: ContosoUniversity Model1.edmx (357 lines, 5 entities, 4 associations, all cascade delete). Output: 5 entity files + ContosoUniversityEntities.cs DbContext. All annotations correct: [Key], [DatabaseGenerated(Identity)], [Required], [MaxLength], [Table("Courses")] for Cours→Courses mismatch, navigation properties (virtual single refs + ICollection).
Key implementation details:
- XML parsing uses
XmlNamespaceManagerwithSelectNodes()/SelectSingleNode()— EDMX has 6+ XML namespaces annotation:StoreGeneratedPatternattribute requires namespace-qualifiedGetAttribute()call- C-S Mapping is the key to entity→table name mismatches (e.g.,
Cours→Courses) - Entity files skip generation when target .cs already exists (idempotent)
- DbContext groups FK relationships by dependent entity for clean
modelBuilder.Entity<T>()blocks - L1 integration uses
$edmxGeneratedFilestracking array to prevent the existing .cs copy loop from overwriting generated files
Task: Add ContentTemplate RenderFragment parameter to UpdatePanel and verify InteractiveServer render mode approach.
Changes made:
-
Added ContentTemplate parameter to
UpdatePanel.razor.cs:[Parameter] public RenderFragment ContentTemplate { get; set; }- Updated XML doc comments to clarify ContentTemplate is Web Forms equivalent
- ChildContent remains for Blazor-style syntax
-
Updated UpdatePanel.razor to render
ContentTemplate ?? ChildContent:- Both
<ContentTemplate>and<ChildContent>syntaxes now work - Eliminates RZ10012 warnings during L1 migration
- Both
-
Changed base class from
BaseWebFormsComponenttoBaseStyledComponent:- Web Forms UpdatePanel in .NET 4.0+ supports
classattribute via Attributes["class"] - This gives UpdatePanel access to CssClass, Style, ToolTip, and other styling properties
- Matches Web Forms behavior more accurately
- Web Forms UpdatePanel in .NET 4.0+ supports
-
Added CSS/Style attributes to rendered markup:
- Both div and span variants now render
class="@CssClass" style="@Style" title="@ToolTip"
- Both div and span variants now render
-
Render mode decision: Did NOT add
@attribute [RenderModeInteractiveServer]- Added code comment explaining why: library components should not force render modes
- Consuming apps control render mode at App.razor or page level
- While UpdatePanel's purpose was interactivity in Web Forms, in Blazor this is an app-level concern
-
Updated sample page (
samples/AfterBlazorServerSide/Components/Pages/ControlSamples/UpdatePanel/Default.razor):- Added "Web Forms ContentTemplate Syntax" section demonstrating
<ContentTemplate>usage - Shows both syntaxes work (ChildContent and ContentTemplate)
- Added interactive counter for ContentTemplate example
- Added "Web Forms ContentTemplate Syntax" section demonstrating
-
All 24 tests pass including new TDD tests for ContentTemplate functionality
Key learnings:
- UpdatePanel should inherit from BaseStyledComponent, not BaseWebFormsComponent, because .NET 4.0+ Web Forms UpdatePanel supports CSS class attributes
- RenderFragment parameters with
??fallback pattern enable dual syntax support (Web Forms and Blazor idioms) - Library components should not force render modes - that's an app-level architectural decision
Task: Verify L1 migration script handles UpdatePanel + ContentTemplate correctly after the ContentTemplate RenderFragment enhancement.
Findings:
- L1 script (
bwfc-migrate.ps1) has no ContentTemplate stripping logic —ContentTemplatewas never anasp:prefixed tag, soConvertFrom-AspPrefixnever touched it. It passes through L1 untouched. Correct behavior. CODE-TRANSFORMS.mdandSKILL.md— no UpdatePanel/ContentTemplate references, no stripping guidance. Clean.CONTROL-REFERENCE.md— already updated with proper ContentTemplate documentation from previous work. Clean.
One fix made: Code-behind TODO header (line 1404) said "UpdatePanel / ScriptManager references → remove" — misleading now that UpdatePanel is a real BWFC component. Split into two lines: ScriptManager → remove, UpdatePanel → BWFC preserved (remove only code-behind API calls).
Issue #16 — ToolTip promoted to BaseWebFormsComponent:
- Moved
ToolTipproperty fromBaseStyledComponenttoBaseWebFormsComponentso all ~40 components get it, not just styled ones. - Removed duplicate
ToolTipdeclarations fromChartSeries.razor.csandMenuItem.razor.cs(they inherit from BaseWebFormsComponent). DataPoint.csandTreeNode.razor.cskeep their ownToolTip— DataPoint is a plain class, TreeNode inherits ComponentBase directly.BaseStyledComponent.ApplyThemeSkinstill referencesToolTipvia inheritance — no changes needed there.
Issues #15, #17, #18 — Already implemented:
- AccessKey was already in BaseWebFormsComponent (line 139).
- BaseDataBoundComponent already inherits BaseStyledComponent (not BaseWebFormsComponent directly).
- Image and Label already inherit BaseStyledComponent.
- All 1550 tests pass after changes. 📌 Team update (2026-03-14): M20 Batch 6 orchestration spawn — Forge designing component health dashboard, Cyclops advancing L1 script fixes, Rogue building L1 test harness — decided by Scribe
Summary: Added 5 new transformation capabilities to bwfc-migrate.ps1, pushing L1 automation coverage from ~40% to ~60%.
New transforms — Markup (Normalize-AttributeValues + Add-DataSourceIDWarning):
- Boolean normalization:
Visible="True"→Visible="true",Enabled="False"→Enabled="false". Excludes text-content attributes (Text, Title, Value, etc.) to avoid false positives. - Enum type-qualifying: 18 attribute→enum mappings (GridLines→GridLines, BorderStyle→BorderStyle, TextMode→TextBoxMode, etc.).
GridLines="Both"→GridLines="@GridLines.Both"so Razor evaluates the C# enum directly instead of relying on EnumParameter string parsing. - Unit normalization:
Width="100px"→Width="100"for Width, Height, BorderWidth, CellPadding, CellSpacing. Only strips "px" — other units (%, em, pt) preserved since they carry distinct meaning. - DataSourceID warnings: Removes
DataSourceID="..."attributes and replaces data source control declarations (<SqlDataSource>,<ObjectDataSource>, etc.) with@* TODO *@comments. Uses(?s)single-line regex to handle multi-line tags containing<%$ ... %>expressions.
New transforms — Code-behind (enhanced Copy-CodeBehind):
5. Response.Redirect → NavigationManager.NavigateTo: 4 patterns (literal URL, literal+bool, expression, expression+bool). Strips ~/ prefix from literal URLs. Preserves .aspx in URLs (AspxRewriteMiddleware handles rewriting). Injects [Inject] NavigationManager into the class.
6. Session["key"] detection: Collects unique session keys, inserts migration guidance block (ProtectedSessionStorage, scoped service, cascading parameter options).
7. ViewState["key"] detection: Collects unique ViewState keys, generates suggested private field declarations, notes the [Obsolete] compatibility shim.
Pipeline placement: New markup transforms run after Convert-TemplatePlaceholders and before blank-line cleanup. Code-behind transforms run after existing [RouteData]/[QueryString] conversion and before file write.
Key gotchas:
- Data source control regex uses
(?s)mode because tags span lines and contain%>from Web Forms expressions, which breaks[^>]*patterns. - Boolean exclusion list prevents false positives on attributes like Text="True" or Title="False".
- Enum map only includes unambiguous attribute→enum mappings; ambiguous ones like SelectionMode (Calendar vs List) and Mode are skipped.
- Session/ViewState blocks are inserted after the
=====TODO header marker using LastIndexOf, not prepended, to keep the file well-organized.
Summary: Created the BlazorAjaxToolkitComponents class library project at src/BlazorAjaxToolkitComponents/. This is the new home for Blazor components emulating ASP.NET Ajax Control Toolkit controls. Project references BlazorWebFormsComponents and includes Microsoft.JSInterop for client-side behaviors.
Files Created:
BlazorAjaxToolkitComponents.csprojRazor class library, net10.0, NuGet metadata matching existing patternsBaseExtenderComponent.csStub base class with TargetControlID + IJSRuntime injection (full design pending Forge)_Imports.razorCommon usingsREADME.mdProject overview
Key Decisions:
- Package ID is
BlazorAjaxToolkitComponents(no Fritz. prefix distinct from the base library) - Depends on
BlazorWebFormsComponentsvia ProjectReference - Uses
Microsoft.JSInterop(not Microsoft.AspNetCore.Components.Web's JS) since toolkit extenders need direct JS interop - BaseExtenderComponent extends ComponentBase directly (not BaseWebFormsComponent) extenders are behavioral attachments, not visual controls
Team update: BlazorAjaxToolkitComponents project created by Cyclops. PR #71 targeting dev. Branch squad/441-ajax-toolkit-project. Fixes #441 (M24: Ajax Toolkit Components).
Summary: Implemented two Ajax Control Toolkit extender components with full JS interop lifecycle. Updated BaseExtenderComponent from minimal stub to production-ready base class. PR #462 targeting upstream dev.
BaseExtenderComponent changes:
- Full JS module lifecycle: abstract JsModulePath, JsCreateFunction, GetBehaviorProperties()
- OnAfterRenderAsync initializes behavior on firstRender when Enabled=true
- BehaviorID and Enabled parameters
- IAsyncDisposable with proper cleanup (disposes behavior, then module)
- SSR-safe: catches JSException and JSDisconnectedException during all JS calls
- TargetControlID passed as string to JS for document.getElementById() resolution (pragmatic v1, no BaseWebFormsComponent dependency)
ConfirmButtonExtender (#451): Pure C# class (no .razor needed since extenders render no HTML). ConfirmText, ConfirmOnFormSubmit, DisplayModalPopupID parameters. JS intercepts click, shows window.confirm(), prevents event on cancel.
FilteredTextBoxExtender (#450): FilterType flags enum (Numbers, LowercaseLetters, UppercaseLetters, Custom), ValidChars, InvalidChars, FilterMode enum, FilterInterval. JS blocks keystrokes on keypress, strips invalid chars on paste with debounced cleanup.
Key decisions:
- Extenders are pure .cs classes, not .razor files (they render zero HTML)
- Kept ComponentBase inheritance per task spec (not BaseWebFormsComponent) for lightweight v1
- JS modules use Map-based behavior registry with create/update/dispose exports
- FilterType is [Flags] enum so filter types can be combined (e.g., Numbers | LowercaseLetters)
- BlazorAjaxToolkitComponents project does NOT enable nullable context, so no nullable annotations
Team update: ConfirmButtonExtender and FilteredTextBoxExtender implemented by Cyclops. PR #462 targeting upstream dev. Branch squad/451-450-confirm-filtered-extenders. Fixes #451, #450.
Summary: Implemented two more Ajax Control Toolkit extender components following established patterns from ConfirmButton/FilteredTextBox. Both inherit BaseExtenderComponent, use pure .cs classes (no .razor), and follow the JS module lifecycle pattern.
Implemented: WI-1 (ThemeMode enum), WI-3 (Container EnableTheming propagation), WI-4 (Runtime theme switching support)
Files modified:
- Created:
Theming/ThemeMode.cs— enum with StyleSheetTheme (defaults) vs Theme (overrides) - Modified:
ThemeConfiguration.cs— added Mode property + WithMode() fluent API - Modified:
ThemeProvider.razor— added Mode parameter, syncs to Theme.Mode in OnParametersSet - Modified:
BaseWebFormsComponent.cs— added IsThemingEnabledByAncestors() for container propagation, updated ApplyThemeSkin signature to accept ThemeMode - Modified:
BaseStyledComponent.cs— dual-mode ApplyThemeSkin implementation (StyleSheetTheme vs Theme)
Pattern details:
- ThemeMode.StyleSheetTheme (default): Sets default values only — explicit component property values take precedence. This preserves backward compatibility with existing components.
- ThemeMode.Theme: Theme overrides all property values, even explicitly set ones. Matches Web Forms Page.Theme behavior (most common usage).
- Container propagation: IsThemingEnabledByAncestors() walks the Parent chain to check EnableTheming. O(depth) operation but control trees are shallow.
- Runtime switching: Works via Blazor's CascadingValue change detection when ThemeProvider.Theme is assigned a NEW ThemeConfiguration instance. Mutating in-place won't trigger re-renders.
Key implementation choices:
- Default mode is StyleSheetTheme to preserve backward compatibility with existing themes
- Theme mode only overrides properties when skin actually has a value (non-default/non-null check)
- ThemeProvider syncs its Mode parameter to ThemeConfiguration.Mode in OnParametersSet
- Container-level EnableTheming propagation checks entire ancestor chain before applying themes
Build verified: 0 errors, 4 warnings (restore warnings, not code issues).
ModalPopupExtender (#446): PopupControlID, BackgroundCssClass, OkControlID, CancelControlID, OnOkScript, OnCancelScript, DropShadow, Drag, PopupDragHandleControlID. JS creates overlay backdrop, centers popup with fixed positioning, traps focus within modal, supports Escape key close, and optional mouse-drag repositioning via drag handle.
CollapsiblePanelExtender (#447): CollapseControlID, ExpandControlID (same ID = toggle), Collapsed, CollapsedSize, ExpandedSize, CollapsedText, ExpandedText, TextLabelID, ExpandDirection enum (Vertical/Horizontal), AutoCollapse, AutoExpand, ScrollContents. JS uses CSS transitions on height/width with smart initial-state setup (no transition on first paint, then enables animation).
New enum: ExpandDirection (Vertical=0, Horizontal=1) in Enums/ folder.
Key patterns followed:
- Same module path convention: ./_content/BlazorAjaxToolkitComponents/js/{kebab-name}.js
- Same JS exports: createBehavior, updateBehavior, disposeBehavior with Map-based registry
- OnOkScript/OnCancelScript executed via ew Function(script)() with try/catch safety
- CollapsiblePanel measures natural size via scrollHeight when ExpandedSize=0 (auto)
- XML docs file updated with all new members
Team update: ModalPopupExtender and CollapsiblePanelExtender implemented by Cyclops. Branch squad/446-447-modal-collapsible-extenders. Fixes #446, #447.
Summary: Built the core ComponentHealthService in src/BlazorWebFormsComponents/Diagnostics/ per PRD §7. Four files created: ImplementationStatus.cs (enum), ComponentHealthReport.cs (data model), ReferenceBaselines.cs (JSON loader), ComponentHealthService.cs (reflection + scoring engine). DI registration via AddComponentHealthDashboard() added to ServiceCollectionExtensions.cs.
Key Implementation Details:
- Property/Event Counter (§5.4): Walks inheritance chain with DeclaredOnly, stops at BaseWebFormsComponent/BaseStyledComponent/BaseDataBoundComponent/DataBoundComponent<>. Uses GetGenericTypeDefinition() for generic base matching. Skips [Obsolete], [CascadingParameter], RenderFragment/RenderFragment, AdditionalAttributes, ChildContent, ChildComponents. EventCallback/EventCallback counted as events only.
- Component Discovery (§5.1-5.2): Reflects over BWFC assembly, matches against tracked components list. Falls back to hardcoded 56-component list when
dev-docs/tracked-components.jsondoesn't exist. - File Detection (§7.4): Scans test project directories/files for component names, docs/ for matching .md files, ComponentCatalog.cs for sample page registration.
- Score Computation (§4.1): Weighted average (Props 30%, Events 15%, Tests 20%, Docs 15%, Sample 10%, Status 10%). Missing baselines excluded and weights re-distributed. Scores capped at 100%. 0/0 treated as 1.0.
- Baselines Loading: ReferenceBaselines.LoadFromFile() gracefully handles missing/malformed JSON.
- Project enforces
varover explicit types via IDE0007 as error — all code uses var.
Files Created:
src/BlazorWebFormsComponents/Diagnostics/ImplementationStatus.cssrc/BlazorWebFormsComponents/Diagnostics/ComponentHealthReport.cssrc/BlazorWebFormsComponents/Diagnostics/ReferenceBaselines.cssrc/BlazorWebFormsComponents/Diagnostics/ComponentHealthService.cs
Files Modified:
src/BlazorWebFormsComponents/ServiceCollectionExtensions.cs(added AddComponentHealthDashboard extension)
Build verified: 0 errors, 99 pre-existing warnings.
Summary: Removed GUID-based fallback IDs from CheckBox, RadioButton, and RadioButtonList. When developer sets ID="X", rendered HTML uses that ID exactly (with _N suffixes for list items). When no ID is set, no id/for attributes are rendered — no GUID pollution in the DOM.
Pattern: ComponentIdGenerator.GetClientID(this) via ClientID property is the single source of truth for element IDs. Components should never generate their own GUIDs for HTML id attributes. Radio group name attribute is the only acceptable GUID fallback (required for mutual exclusion when no developer ID or GroupName is set).
Key decisions:
- FileUpload already correct — no changes needed (only renders id when ClientID present)
- RadioButton keeps GUID fallback exclusively for
EffectiveGroupName(radionameattribute), not forid - RadioButtonList
GetInputId(index)returns null when no ClientID, preserving clean HTML - 7 tests updated to match new behavior; all 2105 tests pass
Files Modified:
src/BlazorWebFormsComponents/CheckBox.razor.cs(removed GUID, use ClientID directly)src/BlazorWebFormsComponents/RadioButton.razor.cs(removed GUID for id, kept for group name)src/BlazorWebFormsComponents/RadioButtonList.razor.cs(GetInputId uses ClientID with suffix)src/BlazorWebFormsComponents.Test/CheckBox/IDRendering.razor(updated WithoutID tests)src/BlazorWebFormsComponents.Test/CheckBox/Text.razor(LabelForAttribute test uses ID)src/BlazorWebFormsComponents.Test/RadioButton/IDRendering.razor(updated WithoutID tests)src/BlazorWebFormsComponents.Test/RadioButton/Text.razor(LabelForAttribute test uses ID)src/BlazorWebFormsComponents.Test/RadioButtonList/TextAlignTests.razor(tests use developer ID)src/BlazorWebFormsComponents.Test/RadioButtonList/StableIds.razor(WithoutID expects no id attr)
Summary: Extended UseBlazorWebFormsComponents() middleware to handle legacy .ashx (HTTP handler) and .axd (Web Resource) URL patterns per issue #423.
Changes:
- BlazorWebFormsComponentsOptions: Added EnableAshxHandling (default: true), EnableAxdHandling (default: true), and AshxRedirectMappings dictionary for custom .ashx redirects.
- AshxHandlerMiddleware: Intercepts .ashx requests. Returns 410 Gone by default; 301 redirect if a custom mapping exists in AshxRedirectMappings.
- AxdHandlerMiddleware: Intercepts .axd requests. Returns 404 for WebResource/ScriptResource/Trace.axd; 410 Gone for ChartImg.axd.
- ServiceCollectionExtensions.UseBlazorWebFormsComponents(): Conditionally registers both new middleware alongside existing AspxRewriteMiddleware.
Impact: Existing .aspx rewriting unchanged. All three middleware classes follow the same internal pattern (constructor injection, Invoke method, early return on match). �ar used everywhere per IDE0007 rule. Build: 0 errors.
Summary: Implemented core HttpHandlerBase feature — 7 new files in src/BlazorWebFormsComponents/Handlers/ enabling migration of ASP.NET Web Forms .ashx handlers to ASP.NET Core with minimal code changes.
Files created:
HttpHandlerBase.cs— Abstract base class withProcessRequestAsync(HttpHandlerContext)and virtualIsReusable(default false)HttpHandlerContext.cs— Adapter wrapping ASP.NET Core HttpContext with Request/Response/Server/Session/User/Items propertiesHttpHandlerRequest.cs— Request adapter with NameValueCollection-based QueryString/Form/Headers,this[key]indexer (checks QS then Form), Files, InputStream, Url, RawUrl, etc.HttpHandlerResponse.cs— Response adapter with sync Write/BinaryWrite (safe sync-over-async), async variants, AddHeader/AppendHeader, Clear, Redirect, Flush, and[Obsolete] End()that sets IsEnded flagHttpHandlerServer.cs— Server utilities with MapPath (~/→ WebRootPath, other → ContentRootPath), HtmlEncode/Decode, UrlEncode/Decode, andTransfer()→ NotSupportedException with migration guidanceRequiresSessionStateAttribute.cs— Marker attribute; when present, MapHandler callsSession.LoadAsync()before ProcessRequestAsyncHandlerEndpointExtensions.cs— ThreeMapHandler<T>overloads on IEndpointRouteBuilder: explicit path, convention-based (strips Handler suffix → .ashx), multi-path
Key decisions:
- Return type is
IEndpointConventionBuilder(notRouteHandlerBuilder) becauseendpoints.Map(pattern, RequestDelegate)returnsIEndpointConventionBuilder. All chaining methods (.RequireAuthorization, .RequireCors) work on this interface. - No AshxHandlerMiddleware exists to modify — the existing middleware is AspxRewriteMiddleware (handles .aspx URLs only). Middleware coordination deferred until AshxHandlerMiddleware is created.
- Namespace is
BlazorWebFormsComponents(root) per spec requirement that migrated code usesusing BlazorWebFormsComponents; - HttpHandlerRequest caches NameValueCollections lazily to avoid rebuilding on each access
- Form/Files properties safely check
HasFormContentTypebefore accessing Request.Form to avoid exceptions on non-form requests - Session exposed as lazy property on HttpHandlerContext — throws standard ASP.NET Core error if session middleware not configured
Build: 0 errors, all pre-existing warnings only.
Summary: Fixed 4 compilation errors in the AfterBlazorServerSide sample project introduced by merge of upstream/main into dev (PR #475).
Errors fixed:
- CS1503 in ImageButton/Index.razor — OnClick handlers used MouseEventArgs but ButtonBaseComponent.OnClick is EventCallback. Changed both handlers to accept EventArgs.
- CS0103 in FormView/Simple.razor and FormView/Edit.razor — markup referenced WidgetFormView_PageIndexChanging but no such method existed. Added stub handlers with correct PageChangedEventArgs signature.
- CS1503 in GridView/Selection.razor — SelectedIndexChanged handler took int but component expects EventCallback. Changed handler (and code example) to accept GridViewSelectEventArgs.
Key insight: Always check the base class parameter types — ImageButton inherits OnClick from ButtonBaseComponent which uses EventArgs, not MouseEventArgs. GridView's SelectedIndexChanged was recently changed to GridViewSelectEventArgs (matching Web Forms behavior).
Summary: Implemented two new Roslyn analyzers following established patterns (ResponseRedirect/Session as templates).
- BWFC013 ResponseObjectUsageAnalyzer: Detects Response.Write(), WriteFile(), Clear(), Flush(), End() via InvocationExpression + ImmutableHashSet method matching. Code fix replaces with TODO comment.
- BWFC014 RequestObjectUsageAnalyzer: Detects Request.Form/Cookies/Headers/QueryString/ServerVariables["key"] via ElementAccessExpression, and Request.Files via SimpleMemberAccessExpression. Code fix replaces with TODO comment.
- Both analyzers use �xpressionText.EndsWith(".Response") / .EndsWith(".Request") pattern to match his.Response, HttpContext.Current.Response, and any *.Response prefix.
- 21 new tests (10 for BWFC013, 11 for BWFC014): 5+6 positive, 3+3 negative, 2+2 code fix tests.
- Updated AllAnalyzersIntegrationTests ExpectedIds and analyzer count (810).
- Updated AnalyzerReleases.Unshipped.md with both rules.
- All 111 tests pass (90 existing + 21 new).
Key patterns: Code fix uses EmptyStatement with SyntaxFactory.EndOfLine("\r\n") between comment trivia and semicolon. BWFC014 registers for both ElementAccessExpression (indexed properties) and SimpleMemberAccessExpression (direct properties like Files), with parent-check to avoid double-reporting.
Team update (2026-03-20): BWFC013/BWFC014 analyzers + architecture (6 files, 21 tests, 111 passing, commit b267b854). PR #487 opened on upstream. Next analyzer ID: BWFC015. decided by Cyclops
Summary: Two tasks completed on feature/analyzer-sprint1:
-
NuGet Analyzer Integration — Added ProjectReference from BlazorWebFormsComponents.csproj to BlazorWebFormsComponents.Analyzers.csproj with
PrivateAssets="all" ReferenceOutputAssembly="false" OutputItemType="Analyzer". Consumers now get Roslyn analyzers automatically when referencing the main NuGet package. -
L1/L2 Pipeline Pre-Scan — Added
-Prescanswitch tobwfc-migrate.ps1. Scans source .cs files for 9 BWFC analyzer patterns (BWFC001-005, 011-014) and outputs JSON summary with file-level detail + human-readable breakdown. Early return — no migration transforms executed.
Verification: Build succeeds (0 errors), all 111 analyzer tests pass, prescan tested against src/ (1,092 matches across 176 files).
Summary: Updated .github/workflows/build.yml to restore, build, run, upload, and publish analyzer test results alongside the existing unit tests. Also replaced the squad-ci.yml placeholder with real dotnet restore/build/test commands covering both test suites, including setup-dotnet for .NET 10. YAML validated with Python yaml parser.
Summary: Added public virtual void Focus() to BaseWebFormsComponent matching the ASP.NET Web Forms Control.Focus() API. Uses fire-and-forget JS interop (_ = JsRuntime.InvokeVoidAsync("bwfc.Page.Focus", ClientID)) same discard pattern validators use. Null-guards JsRuntime for SSR pre-render safety. Added �wfc.Page.Focus(elementId) JS function to both Basepage.js (IIFE global) and Basepage.module.js (ES module export + window binding). Build: 0 errors. CustomControl tests: 63/63 pass.
Summary: Migrated all 7 custom controls from samples/DepartmentPortal/Code/Controls/ to Blazor components in samples/AfterDepartmentPortal/Components/Controls/. Each control inherits from the appropriate BWFC CustomControls base class (WebControl, DataBoundWebControl, or TemplatedWebControl).
Controls migrated:
- StarRating (WebControl) ViewState[Parameter], TagKey=Span, star rendering with color params
- NotificationBell (WebControl) TagKey=Div, EventCallback, drawer rendering
- EmployeeCard (WebControl) CompositeControlflat RenderContents, photo/info/details link
- EmployeeDataGrid (DataBoundWebControl) PerformDataBinding override, paging/sorting/search
- DepartmentBreadcrumb (WebControl) IPostBackEventHandler removed, EventCallback
- PollQuestion (WebControl) IPostBackEventHandler removed, EventCallback, radio buttons
- SectionPanel (TemplatedWebControl) ITemplateRenderFragment, RenderTemplate() helper for header/content/footer
Also created: BreadcrumbEventArgs.cs, NotificationEventArgs.cs, PollVoteEventArgs.cs
Pages updated: Dashboard.razor (SectionPanel, PollQuestion, NotificationBell), Employees.razor (EmployeeDataGrid with sample data)
Key patterns applied: var everywhere (IDE0007), WebUtility.HtmlEncode instead of HttpUtility, Blazor route URLs instead of .aspx, new() target-typed syntax.
Build: 0 errors, 0 warnings (excluding pre-existing NU1510 from upstream deps).
Summary: Implemented Phase 1 of the ViewState/PostBack shim per Forge's architecture proposal. Created ViewStateDictionary class implementing IDictionary<string, object?> with null-safe indexer (Web Forms compat), type-safe convenience methods, IsDirty tracking, and IDataProtector-based Serialize/Deserialize with JsonElement type coercion. Updated BaseWebFormsComponent: ViewState upgraded from Dictionary to ViewStateDictionary, [Obsolete] removed, IsPostBack with mode-adaptive logic (SSR checks HTTP method, Interactive tracks _hasInitialized), CurrentRenderMode/IsHttpContextAvailable, RenderViewStateField, IDataProtectionProvider injection, ViewState deserialization from form POST. Updated WebFormsPageBase similarly. Created WebFormsRenderMode enum.
Key decisions:
- IDataProtectionProvider is injected as nullable (null-safe) backward compat for apps that don't register DataProtection
- ViewState deserialization happens BEFORE OnInit/OnLoad events in OnInitializedAsync matches Web Forms lifecycle
- CryptographicException from tampered payloads silently fails to empty ViewState (fail-safe)
- Component ID for hidden field uses the developer-set
IDparameter (__bwfc_viewstate_{ID}) varused everywhere per IDE0007 enforcement
Files created: ViewStateDictionary.cs, WebFormsRenderMode.cs Files modified: BaseWebFormsComponent.cs, WebFormsPageBase.cs Build: 0 errors, 122 warnings (all pre-existing)
Scope: Ship NuGet assemblies for net8.0, net9.0, and net10.0 so teams on .NET 8 LTS or .NET 9 can consume the library.
Changes:
Directory.Build.props: Added TFM-conditionalAspNetCoreVersion/BlazorWebAssemblyVersion(8.0.0 for net8.0, 9.0.0 for net9.0). Unconditional 10.0.0 defaults remain for single-target sample projects.BlazorWebFormsComponents.csproj:TargetFramework→TargetFrameworks(net8.0;net9.0;net10.0).FrameworkReferenceand version-variablePackageReferenceitems auto-resolve per TFM.BlazorAjaxToolkitComponents.csproj: Same multi-target change (required because tests reference it).BlazorWebFormsComponents.Test.csproj: Multi-targeted +TestHostversion switched to$(AspNetCoreVersion)for per-TFM resolution..github/workflows/build.yml: SDK setup now installs 8.0.x, 9.0.x, and 10.0.x.SharedSampleObjectsunchanged — itsnetstandard2.0TFM already satisfies net8.0/net9.0.
Key learnings:
- .NET 10 SDK (global.json pinned) can build for older TFMs — no SDK matrix needed locally
FrameworkReference Include="Microsoft.AspNetCore.App"auto-resolves per TFM with no conditionals- Collection expression
= [](C# 12) compiles on net8.0+ — no compatibility issue - bUnit 2.5.3 successfully builds and runs against all three TFMs
- NU1510 "will not be pruned" warnings are informational — pre-existing, not caused by multi-targeting
Microsoft.AspNetCore.Routing2.2.0 (NuGet) works across all TFMs
Build: 0 errors across 3×3 project/TFM combos. Tests: 2606 × 3 TFMs = 7818 executions, all pass.
Summary: Extended the theming system to support sub-component styles (HeaderStyle, RowStyle, AlternatingRowStyle, etc.) on all 5 data controls (GridView, DetailsView, FormView, DataGrid, DataList). Sub-styles enable theme authors to configure child styles through the skin fluent API without needing to use style RenderFragments.
Implementation:
- Extended ControlSkin.cs with SubStyles Dictionary<string, TableItemStyle>
- Added SkinBuilder.SubStyle(styleName, configure) fluent method
- Created BaseWebFormsComponent.ApplySubStyle helper that respects ThemeMode semantics (Theme=override, StyleSheetTheme=default)
- Overrode ApplyThemeSkin in all 5 data controls to apply sub-styles from skin
Technical decisions:
- SubStyles dictionary uses StringComparer.OrdinalIgnoreCase for case-insensitive lookups
- ApplySubStyle returns TableItemStyle (not ref parameter) because properties have internal setters that can't be passed by ref
- Theme mode replaces entire sub-style object; StyleSheetTheme mode merges properties only if not already set
- Style names match exact Web Forms property names (HeaderStyle, RowStyle, ItemStyle, etc.)
Sub-styles per control:
- GridView (8): HeaderStyle, RowStyle, AlternatingRowStyle, FooterStyle, PagerStyle, EditRowStyle, SelectedRowStyle, EmptyDataRowStyle
- DetailsView (10): HeaderStyle, RowStyle, AlternatingRowStyle, FooterStyle, CommandRowStyle, EditRowStyle, InsertRowStyle, FieldHeaderStyle, EmptyDataRowStyle, PagerStyle
- FormView (7): HeaderStyle, RowStyle, EditRowStyle, InsertRowStyle, FooterStyle, PagerStyle, EmptyDataRowStyle
- DataGrid (7): HeaderStyle, ItemStyle, AlternatingItemStyle, FooterStyle, PagerStyle, SelectedItemStyle, EditItemStyle
- DataList (5): HeaderStyle, FooterStyle, ItemStyle, AlternatingItemStyle, SeparatorStyle
Files modified: ControlSkin.cs, SkinBuilder.cs, BaseWebFormsComponent.cs, GridView.razor.cs, DetailsView.razor.cs, FormView.razor.cs, DataGrid.razor.cs, DataList.razor.cs
Build: 0 errors, warnings as baseline (all pre-existing)
Summary: Added JSON-based theme format as alternative to C# fluent API, plus CSS file bundling support in ThemeConfiguration and ThemeProvider.
Files Created:
src/BlazorWebFormsComponents/Theming/JsonThemeLoader.cs— Static class with FromJson/FromJsonFile/ToJson methods + 5 custom JsonConverters (WebColor, Unit, FontUnit, BorderStyle, FontInfo) for System.Text.Json
Files Modified:
src/BlazorWebFormsComponents/Theming/ThemeConfiguration.cs— Added CssFiles property + WithCssFile/WithCssFiles fluent APIsrc/BlazorWebFormsComponents/Theming/ThemeProvider.razor— Added HeadContent block to render elements for CSS files
Implementation Details:
JsonThemeLoader:
- Uses System.Text.Json (project standard) with camelCase naming policy
- Custom converters handle Web Forms types:
- WebColorConverter: accepts HTML color names and hex values
- UnitConverter: accepts strings like "100px", "50%"
- FontUnitConverter: accepts strings like "14px", "Large"
- BorderStyleConverter: enum with case-insensitive parsing
- FontInfoConverter: object with Bold, Italic, Underline, Name, Names, Size
- Property names in JSON are camelCase
- "default" key maps to default skin (empty SkinID)
- Named skins use their SkinID as key
- Supports SubStyles dictionary for data control sections (HeaderStyle, RowStyle, etc.)
JSON Schema:
json { "mode": "StyleSheetTheme", "cssFiles": ["css/theme.css", "css/gridview.css"], "controls": { "Button": { "default": { "backColor": "#507CD1", "foreColor": "White", "font": { "bold": true } }, "DangerButton": { "backColor": "Red", "foreColor": "White", "cssClass": "btn-danger" } } } }
CSS Bundling:
- ThemeConfiguration.CssFiles: List property
- WithCssFile(path): adds single file
- WithCssFiles(params string[]): adds multiple files
- ThemeProvider renders elements in component (Microsoft.AspNetCore.Components.Web, .NET 8+)
- Only renders when CssFiles is non-null and has items
Build Status: 0 errors, 124 warnings (existing)
Key Gotcha: TableItemStyle properties use EnumParameter and non-nullable Unit — conversion from nullable DTOs requires HasValue checks before assignment.
Next Steps: Consider adding validation for CSS file paths, JSON schema documentation, example theme files.
Task: Add validation and diagnostic logging to the theming system to help developers identify misconfigured themes.
Files Created:
- src/BlazorWebFormsComponents/Theming/ThemeDiagnostics.cs
Files Modified:
- src/BlazorWebFormsComponents/BaseWebFormsComponent.cs
Implementation Details:
ThemeDiagnostics class:
- KnownControlTypes: IReadOnlySet with 60+ control names from the library
- KnownSubStyles: IReadOnlyDictionary mapping control types to their valid sub-style names
- GridView: HeaderStyle, RowStyle, AlternatingRowStyle, FooterStyle, PagerStyle, EditRowStyle, SelectedRowStyle, EmptyDataRowStyle
- DetailsView: HeaderStyle, RowStyle, AlternatingRowStyle, FooterStyle, CommandRowStyle, EditRowStyle, InsertRowStyle, FieldHeaderStyle, EmptyDataRowStyle, PagerStyle
- FormView: HeaderStyle, RowStyle, EditRowStyle, InsertRowStyle, FooterStyle, PagerStyle, EmptyDataRowStyle
- DataGrid: HeaderStyle, ItemStyle, AlternatingItemStyle, FooterStyle, PagerStyle, SelectedItemStyle, EditItemStyle
- DataList: HeaderStyle, FooterStyle, ItemStyle, AlternatingItemStyle, SeparatorStyle
- Menu, TreeView, Calendar: appropriate style names for each
- Validate(ThemeConfiguration): returns List of warnings
- Uses reflection to access internal _skins field in ThemeConfiguration
- Rule 1: Warns about unknown control types not in KnownControlTypes
- Rule 2: Warns about unknown sub-style names for controls that support sub-styles
- Rule 3: Warns about empty skins (no properties set)
- Also warns if control doesn't support sub-styles but skin defines them
BaseWebFormsComponent.OnParametersSet enhancement:
- After GetSkin() call, checks if skin is null AND SkinID is not empty
- Logs warning: "Theme skin '{SkinID}' not found for control type '{TypeName}'"
- Uses existing Logger property (ILogger injected via ServiceProvider)
- Helps developers catch typos in SkinID attributes
Build Status: ✅ 0 errors, existing warnings
Key Learning: Reflection is necessary to validate themes since ThemeConfiguration's _skins dictionary is private. The Validate method provides comprehensive diagnostic output without breaking encapsulation.
Usage Example:
csharp var theme = new ThemeConfiguration().ForControl("Button", b => b.WithBackColor("Red")); var warnings = ThemeDiagnostics.Validate(theme); foreach (var warning in warnings) { Console.WriteLine(warning); }
Next Steps: Consider exposing a public API on ThemeConfiguration to enumerate skins without reflection, or document that validation requires reflection access.
Task: Minimize migration script effort for Web Forms themes — if App_Themes/ is copied to wwwroot/App_Themes/, everything should work with zero extra Program.cs configuration.
Implementation:
BlazorWebFormsComponentsOptions: AddedThemesPath(string?, defaults null = auto-discover "App_Themes") andThemeMode(defaults StyleSheetTheme)ServiceCollectionExtensions.AddBlazorWebFormsComponents: RegistersThemeConfigurationas singleton via factory usingIWebHostEnvironment.WebRootPathat resolution time. Auto-discovers.skinfiles viaSkinFileParser.ParseThemeFolder()and CSS files viaDirectory.GetFiles("*.css", AllDirectories). EmptyThemeConfigurationregistered when no App_Themes folder exists.ThemeProvider.razor: InjectsIServiceProvider(always available, avoids test breakage) and resolvesThemeConfigurationas DI fallback. ExplicitThemeparameter always wins. UsesEffectiveThemeprivate field for both CSS rendering and CascadingValue.
Key Design Decisions:
- Used
IServiceProvider.GetService<ThemeConfiguration>()instead of direct[Inject] ThemeConfigurationto avoid breaking 2,685 existing tests that don't register the service - Used
sp.GetService<IWebHostEnvironment>()(nullable) instead ofGetRequiredServiceto gracefully handle environments without web hosting - Empty string
ThemesPathexplicitly disables auto-discovery; null means use default "App_Themes"
Build: ✅ 0 errors, 135 warnings (all pre-existing) Tests: ✅ 2,685 passed, 0 failed, 0 skipped
Task: Implement three migration shims to eliminate the most common compilation blockers in migrated Web Forms apps.
Files created:
- ConfigurationManager.cs: Static shim shadowing System.Configuration.ConfigurationManager via namespace. AppSettings[key] reads AppSettings:{key} then {key} from IConfiguration. ConnectionStrings[name] reads via GetConnectionString(). Initialized by app.UseConfigurationManagerShim().
- BundleConfig.cs: No-op stubs in System.Web.Optimization for Bundle/ScriptBundle/StyleBundle/BundleTable/BundleCollection.
- RouteConfig.cs: No-op stubs in System.Web.Routing for RouteCollection/RouteTable.
- Updated ServiceCollectionExtensions.cs with UseConfigurationManagerShim(this WebApplication app).
Key decisions:
- ConfigurationManager is static (like EF6 Database shim) because the Web Forms API is static. Uses WebApplication.Configuration for initialization.
- Namespace shadowing via .targets global using avoids needing a type alias in the .targets file.
- AppSettings fallback order: AppSettings:{key} first (structured), then {key} directly (flat).
- BundleConfig/RouteConfig use original System.Web.* namespaces so existing using statements work unchanged.
Build: 0 errors on net8.0;net9.0;net10.0 (438 warnings, all pre-existing)
Task: Implement SessionShim as a scoped service providing dictionary-style Session["key"] access for migrated Web Forms code.
Files created/modified:
SessionShim.cs: Scoped service withobject? this[string key]indexer,Get<T>(key)typed helper,Remove(),Clear(),ContainsKey(),Count. UsesISessionwith JSON serialization when HTTP context is available; falls back toConcurrentDictionaryin interactive Blazor Server mode. Logs one-time warning on fallback.ServiceCollectionExtensions.cs: AddedAddDistributedMemoryCache(),AddSession(),AddScoped<SessionShim>()toAddBlazorWebFormsComponents().WebFormsPageBase.cs: Added[Inject] SessionShim _sessionShimandprotected SessionShim Sessionproperty soSession["key"]works in any page inheriting WebFormsPageBase.
Key decisions:
- System.Text.Json for serialization (zero external deps, consistent with project).
IHttpContextAccessoris optional constructor parameter — graceful degradation, no throws.TryGetSessioncatchesInvalidOperationExceptionfor cases where session middleware is not configured.- One-time
ILogger.LogWarningon fallback so devs get visibility without log spam. ISession.KeysrequiresSystem.LinqforAny()/Count()— must include the using.
Build: ✅ 0 errors on net8.0;net9.0;net10.0
Summary: Replaced flat List<string> ManualItems in MigrationReport with structured List<ManualItem> using a C# record that carries file, line, category (bwfc-* slug), description, and severity. This enables Copilot L2 orchestration to parse manual items programmatically.
Files changed:
src/BlazorWebFormsComponents.Cli/Pipeline/ManualItem.cs— new record typesrc/BlazorWebFormsComponents.Cli/Pipeline/MigrationReport.cs—List<ManualItem>,AddManualItem()convenience method, camelCase JSON serializationsrc/BlazorWebFormsComponents.Cli/Pipeline/MigrationPipeline.cs— updated sole call site to useAddManualItemtests/BlazorWebFormsComponents.Cli.Tests/PipelineIntegrationTests.cs— updated assertions for structured ManualItem and camelCase JSON
Technical decisions:
ManualItemis a positional record:ManualItem(File, Line, Category, Description, Severity)- JSON uses
JsonNamingPolicy.CamelCasefor consistent API-friendly output AddManualItemdefaults severity to"medium"for convenience- Category values follow bwfc-* slug convention (bwfc-session-state, bwfc-general, etc.)
Build: ✅ 0 errors, 124 tests pass
Task: Verify ConfigurationManager shim (Gap G17) — already implemented in Phase 1.
Status: Shim already exists at src/BlazorWebFormsComponents/ConfigurationManager.cs with full test coverage at src/BlazorWebFormsComponents.Test/ConfigurationManagerTests.cs (9 tests, all passing).
API surface:
ConfigurationManager.AppSettings["key"]→ readsAppSettings:{key}with fallback to{key}fromIConfigurationConfigurationManager.ConnectionStrings["name"]→ returnsConnectionStringSettingsviaIConfiguration.GetConnectionString()ConfigurationManager.Initialize(IConfiguration)→ static binding, called viaapp.UseConfigurationManagerShim()- Supporting types:
AppSettingsCollection,ConnectionStringSettingsCollection,ConnectionStringSettings
Key pattern: Static class (like Web Forms original), initialized once at startup. Lives in BlazorWebFormsComponents namespace so .targets global using shadows System.Configuration.ConfigurationManager.
Summary: Standardized all emitted TODO comments across 8 CLI transform/scaffolding files to use the TODO(bwfc-{category}): prefix format. This enables Copilot L2 skill matching by providing a parseable, consistent pattern.
Convention:
- C# output (.razor.cs):
// TODO(bwfc-{category}): {description} - C# inline (within expressions):
/* TODO(bwfc-{category}): {description} */ - Razor output (.razor):
@* TODO(bwfc-{category}): {description} *@
Category slugs: bwfc-general, bwfc-lifecycle, bwfc-ispostback, bwfc-viewstate, bwfc-session-state, bwfc-navigation, bwfc-datasource, bwfc-identity, bwfc-ajax-toolkit, bwfc-expression, bwfc-master-page, bwfc-login-view, bwfc-select-method, bwfc-route-url.
Files changed (transforms):
- TodoHeaderTransform.cs 11-item checklist header now uses category-prefixed TODOs
- PageLifecycleTransform.cs 3 TODOs
bwfc-lifecycle - IsPostBackTransform.cs 3 TODOs
bwfc-ispostback(inline/* */converted to//on own line) - ResponseRedirectTransform.cs 3 TODOs
bwfc-navigation(inline expression TODOs stay/* */) - AjaxToolkitPrefixTransform.cs 2 TODOs
bwfc-ajax-toolkit - DataSourceIdTransform.cs 2 TODOs
bwfc-datasource - ShimGenerator.cs 1 TODO
bwfc-identity - ProjectScaffolder.cs 5 TODOs
bwfc-datasource,bwfc-identity,bwfc-session-state,bwfc-general
Files changed (test expectations): 18 expected output files updated across tests/ and migration-toolkit/tests/.
Build: 0 errors. Tests: 124/124 pass.
Summary: Enhanced BWFC022, BWFC023 with pattern-specific TODO guidance and created new BWFC024 (ScriptManagerUsageAnalyzer). Phase 1 of the approved ClientScript Migration Strategy from PRD.
BWFC022 changes:
- Parameterized MessageFormat:
"Page.ClientScript{0} is not available in Blazor. {1}" - Added
GetMethodSpecificGuidance()that inspects the parentMemberAccessExpressionSyntaxto determine which ClientScript method is called - 5 specific patterns: RegisterStartupScript → IJSRuntime/OnAfterRenderAsync, RegisterClientScriptInclude → layout script tag, RegisterClientScriptBlock → InvokeVoidAsync, GetPostBackEventReference → @onclick/EventCallback, fallback → generic IJSRuntime
BWFC023 changes:
- Enhanced message with 3-step migration path: remove interface, replace RaisePostBackEvent with EventCallback, use @onclick handlers
BWFC024 (new):
- Detects ScriptManager.{GetCurrent, SetFocus, RegisterStartupScript, RegisterClientScriptBlock, RegisterAsyncPostBackControl} static calls
- Method-specific guidance for each pattern (ElementReference/FocusAsync for SetFocus, remove for RegisterAsyncPostBackControl, etc.)
- Instance methods on local variables correctly NOT flagged (e.g.
sm.RegisterAsyncPostBackControl)
Tests: 172 analyzer tests pass (was 159). 13 new tests added across BWFC022/023/024.
Key technical decision: Used parameterized MessageFormat with {0}/{1} args rather than separate DiagnosticDescriptor per method. Keeps single BWFC022 ID for all Page.ClientScript patterns, differentiated by message content. Existing tests without .WithMessage() continue to pass; new tests verify exact messages.
Summary: Built ClientScriptShim — a scoped service emulating Page.ClientScript (ClientScriptManager) from Web Forms. Follows the same queue-and-flush pattern used in Web Forms: methods like RegisterStartupScript queue scripts into dictionaries keyed for deduplication, and FlushAsync(IJSRuntime) executes them via eval after render.
Files created: src/BlazorWebFormsComponents/ClientScriptShim.cs
Files modified:
ServiceCollectionExtensions.cs— registeredClientScriptShimas scoped service afterCacheShimBaseWebFormsComponent.cs— added lazy-resolvedClientScriptproperty (same pattern asJsInterop), added auto-flush call inOnAfterRenderAsync
API surface:
RegisterStartupScript(Type, string, string, bool)/RegisterStartupScript(Type, string, string)IsStartupScriptRegistered(Type, string)RegisterClientScriptBlock(Type, string, string, bool)/IsClientScriptBlockRegistered(Type, string)RegisterClientScriptInclude(string, string)/RegisterClientScriptInclude(Type, string, string)/IsClientScriptIncludeRegistered(string)FlushAsync(IJSRuntime)— executes queued scripts/blocks via eval, loads includes via dynamic script tag injection- Unsupported:
GetPostBackEventReference,GetPostBackClientHyperlink,GetCallbackEventReferencethrowNotSupportedExceptionwith migration guidance
Design decisions:
- Script tag stripping uses compiled Regex for perf; strips
<script>wrappers whenaddScriptTags=true - Three separate dictionaries for startup scripts, blocks, and includes — mirrors Web Forms' separate registries
- Flush runs blocks before startup scripts (Web Forms page lifecycle order)
- Includes loaded via dynamic
<script>element creation in eval - Auto-flush happens every render (not just firstRender) so scripts registered in event handlers work correctly
- Build: 0 errors across net8.0/net9.0/net10.0
Summary: Replaced NotSupportedException stubs with working PostBack/Callback implementations. Created full JS interop bridge for __doPostBack and ScriptManager.GetCurrent() pattern.
Files Created:
- PostBackEventArgs.cs, ScriptManagerShim.cs, bwfc-postback.js
Files Modified:
- ClientScriptShim.cs: 3 methods now return working JS strings; added ResolveControlId
- WebFormsPageBase.cs: IAsyncDisposable, ClientScript, PostBack event, JSInvokable handlers, OnAfterRenderAsync registration
- ServiceCollectionExtensions.cs: ScriptManagerShim DI registration
- ClientScriptShimTests.cs: Updated tests for new behavior + null edge cases
Technical Decisions:
- Inline JS bootstrap avoids race conditions with external script loading
- ResolveControlId prefers BaseWebFormsComponent.ID, falls back to type name
- ScriptManagerShim: both DI and static GetCurrent() factory
- PostBack target ID: TypeName_HashCode for per-instance uniqueness
Build: 0 errors, 32 tests pass x 3 TFMs
Summary: Built WebFormsForm component enabling Request.Form to work in both SSR and interactive Blazor Server modes.
Files Created:
FormMethod.cs— enum (Get=0, Post=1)WebFormsForm.razor— dual-mode form component (SSR: plain form; interactive: JS interop submit handler)wwwroot/js/bwfc-webformsform.js— ES module exportingreadFormData(formElement)that reads FormData from DOM
Files Modified:
FormShim.cs— Added_interopDatadictionary field, newFormShim(Dictionary<string, StringValues>)constructor,SetFormData()method. All members (indexer, GetValues, AllKeys, Count, ContainsKey) now check_interopDatawhen_formis null.RequestShim.cs— Added_cachedFormShimfield for interactive mode persistence. Form getter caches the shim so SetFormData survives. Warning message updated to mention<WebFormsForm>. AddedSetFormData()method.WebFormsPageBase.cs— ChangedRequestfrom always-new to cached_requestShimfield. AddedSetRequestFormData()public method. Addedusing Microsoft.Extensions.Primitives.
Design Decisions:
- JS file is ES module (matching Basepage.module.js pattern), loaded via
IJSObjectReferenceimport - FormShim dual-source:
IFormCollectionfor SSR,Dictionary<string, StringValues>for interop - RequestShim caches FormShim in interactive mode so form data persists across accesses
- WebFormsForm detects interactive mode via
HttpContext == nullpattern (consistent with WebFormsPageBase) @onsubmit:preventDefaultprevents default form submission in interactive mode
Build: ✅ 0 errors on net8.0;net9.0;net10.0
Summary: Fixed two failing Playwright tests in WebFormsFormTests.cs where locators were matching the always-visible migration guidance card instead of the actual results card.
Key Learning — data-audit-control naming convention:
- Demo pages use
data-audit-controlattributes prefixed with the component name, e.g.webforms-form-resultsnot justform-results. - Always use the exact
[data-audit-control='...']selector in Playwright tests rather than fuzzy text-based locators liketext=Request.Formor:has-text(), which can match unrelated visible sections. - When a section is conditionally rendered (e.g., gated by
@if (isPostBack)), the test locator must target that specific section's attribute, not generic text that may appear elsewhere on the page. Summary: Fixed both .targets files by adding Validations namespace and removing Identity namespace to prevent CS0104 ambiguity errors.
Files Modified:
build/Fritz.BlazorWebFormsComponents.targets- ReplacedBlazorWebFormsComponents.Identityglobal using withBlazorWebFormsComponents.ValidationsbuildTransitive/Fritz.BlazorWebFormsComponents.targets- Identical change
Rationale:
- Validations added: Fixes 10 CS0246 errors where RequiredFieldValidator and RegularExpressionValidator types were not found. These validators are common in migrated Web Forms code and should be globally available.
- Identity removed: The Identity namespace defines shim types (IdentityUser, IdentityResult, UserLoginInfo) with identical names to Microsoft.AspNetCore.Identity types. Real migration projects reference both, causing CS0104 ambiguity errors. The migration CLI will inject BlazorWebFormsComponents.Identity usings on a per-file basis only where needed.
Final Global Usings:
- BlazorWebFormsComponents
- BlazorWebFormsComponents.LoginControls
- BlazorWebFormsComponents.Validations
- BlazorWebFormsComponents.EntityFramework
Build: 0 errors, 358 warnings (expected)