feat: P1-P5 Custom Controls Framework Drop-in Replacement for Web Forms Controls#489
Merged
csharpfritz merged 33 commits intoFritzAndFriends:devfrom Mar 23, 2026
Merged
Conversation
Forge scoped a .NET Framework 4.8 sample app featuring 12 ASCX user controls, 3 custom base classes, and 14 pages to test migration toolkit coverage for enterprise Web Forms patterns. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ssion Session: 2026-03-21T14-35-35Z-custom-controls-plan Requested by: Jeffrey T. Fritz Changes: - Logged orchestration: Forge session expanding DepartmentPortal with 6 custom server controls - Logged session: Custom controls design (WebControl, CompositeControl, templated, data-bound, postback, custom events) - Merged decisions: User directive + Forge decision for custom controls milestone scope - Merged 2 decision files from inbox into decisions.md, deleted inbox files
… specs Added section 3.7 DepartmentBreadcrumb inherits directly from System.Web.UI.Control. Demonstrates pure Render() override, zero ViewState, custom BreadcrumbEventArgs. Updated executive summary: 7 custom server controls covering Control, WebControl, CompositeControl, DataBoundControl, ITemplate, IPostBackEventHandler, and custom events. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Phase 1 Foundation (28 files): - .NET Framework 4.8 Web Application Project with old-style .csproj - 6 model POCOs + static PortalDataProvider (in-memory data) - 3 base classes (BasePage, BaseMasterPage, BaseUserControl) - Site.Master with Bootstrap 3 CDN, Default/Login/Dashboard pages - Site.css with portal component styles Phase 2 ASCX Controls (24 files): - 12 user controls: Breadcrumb, PageHeader, Footer, AnnouncementCard, EmployeeList, TrainingCatalog, SearchBox, DepartmentFilter, Pager, DashboardWidget, ResourceBrowser, QuickStats - Patterns: data-bound, event-driven, nested ASCX, web.config tagPrefix - Custom EventArgs: SearchEventArgs, NotificationEventArgs, BreadcrumbEventArgs Builds successfully with MSBuild. Phases 3-4 (custom server controls, remaining pages) to follow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| <?xml version="1.0" encoding="utf-8"?> | ||
| <configuration> | ||
| <system.web> | ||
| <compilation debug="true" targetFramework="4.8" /> |
Check failure
Code scanning / CodeQL
Creating an ASP.NET debug binary may reveal sensitive information High
Phase 3 Custom Server Controls (7 files): - StarRating.cs (WebControl) 1-5 star rating display - EmployeeCard.cs (CompositeControl) programmatic child controls - SectionPanel.cs (ITemplate) templated container with Header/Content/Footer - PollQuestion.cs (IPostBackEventHandler) interactive voting control - NotificationBell.cs (WebControl + custom events) bell icon with drawer - EmployeeDataGrid.cs (DataBoundControl) searchable/sortable/pageable grid - DepartmentBreadcrumb.cs (bare Control) pure Render() override Phase 4 Pages (22 files, 11 pages): - Employees.aspx directory with search, filter, EmployeeDataGrid - EmployeeDetail.aspx single employee with EmployeeCard + StarRating - Announcements.aspx listing with SectionPanel wrapper - AnnouncementDetail.aspx single announcement view - Training.aspx catalog with PollQuestion, enrollment to Session - MyTraining.aspx enrolled courses from Session - Resources.aspx library with ResourceBrowser + SectionPanel - ResourceDetail.aspx single resource view - Admin/ManageAnnouncements.aspx admin CRUD - Admin/ManageTraining.aspx admin CRUD - Admin/ManageEmployees.aspx admin grid with EmployeeDataGrid All custom controls registered in Web.config. Builds with 0 errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Renamed App_Code/ to Code/ (prevents ASP.NET double-compilation) - Switched all pages from CodeFile to CodeBehind (WAP model) - Added back protected field declarations for code-behind controls - Restored code-behind files as Compile items in .csproj - Fixed SectionPanel: removed non-existent IsCollapsible/IsExpanded attrs - Fixed PollQuestion: Question QuestionText, OnAnswerSubmitted OnVoteSubmitted - Fixed TrainingCatalog event handler: EventArgs int (EnrollmentRequested) All 14 pages now return 200 OK under IIS Express. Tested: Default, Login, Dashboard, Employees, EmployeeDetail, Announcements, AnnouncementDetail, Training, MyTraining, Resources, ResourceDetail, Admin/ManageAnnouncements, Admin/ManageTraining, Admin/ManageEmployees. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Session: 20260322T003035Z-departmentportal-phases3-5 Requested by: Scribe Changes: - Logged orchestration: Phase 3 custom controls, Phase 4 ASPX pages, IIS fixes - Logged session: DepartmentPortal phases 3-5 completion summary - Merged decision inbox (4 phase decisions) into decisions.md - Deleted inbox files after merge - No duplicates found; all 4 phase decisions are unique Outcome: All phases (3, 4, IIS fixes) complete. 7 custom controls, 11 ASPX pages, 12 ASCX controls. All 14 pages return HTTP 200 OK in IIS Express.
Nerdbank.GitVersioning from Directory.Build.props auto-generates AssemblyVersion and AssemblyFileVersion attributes. The manual ones in AssemblyInfo.cs caused CS0579 duplicate attribute errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…binding - Site.Master: Replace asp:LoginStatus with session-aware HyperLink/LinkButton for login state display (FormsAuthentication not configured) - SectionPanel.cs: Add [ParseChildren(true)] attribute so ASP.NET page compiler treats <ContentTemplate> as ITemplate property instead of literal HTML - SectionPanel.cs: Add public EnsureChildControls() wrapper for page code access - Announcements.aspx.cs: Use OnPreRender + EnsureChildControls + FindControl pattern for SectionPanel template data binding - Resources.aspx.cs: Same PreRender pattern; fix category filters to use FileType (PDF/DOCX/XLSX/PPTX) instead of non-existent Category values - EmployeeList.ascx.cs: Move grid binding to Page_PreRender so parent page search events (which fire after Page_Load) take effect - TrainingCatalog.ascx.cs: Same PreRender binding fix - Training.aspx.cs: Use protected fields instead of FindControl (which fails across MasterPage naming containers); move binding to OnPreRender All pages verified: 200 OK with data, search works on Employees/Announcements. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…OnPreRender Same pattern as Training.aspx.cs fix FindControl fails across MasterPage naming containers. Protected fields are auto-wired by ASP.NET runtime. Moved BindEmployees() to OnPreRender; event handlers now only set state. Verified: search for 'Alice' correctly filters from 20 to 1 employee. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…r DepartmentPortal analysis Session: 2026-03-22-departmentportal-migration-analysis Requested by: Scribe Changes: - Added 3 orchestration logs (Jubilee, Forge, Bishop) to .squad/orchestration-log/ - Added session summary log to .squad/log/ - Merged 2 decision files from inbox into .squad/decisions.md - Deleted merged inbox files - Deduplication: No duplicates found Agents: Jubilee (bug fix), Forge (control gap analysis), Bishop (prescan analysis) Decision: DepartmentPortal migration readiness 7.5/10 GO
…ployeeList - EmployeeDataGrid.RenderContents now reads actual Employee properties from bound dataItems instead of hardcoded placeholder text - Removed duplicate EmployeeList ASCX from Employees.aspx - Removed unused EmployeeListControl field from code-behind Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nalysis - AfterDepartmentPortal: 31 files, net10.0 Blazor SSR with BWFC reference - 7 stub pages (Dashboard, Employees, Announcements, Training, Resources, details) - 12 shared components migrated from ASCX user controls - Models + PortalDataProvider identical to DepartmentPortal Before app - MainLayout.razor from Site.Master, full project builds clean - Solution file updated with AfterDepartmentPortal project - 44KB migration analysis by Forge covering ASCX + custom control gaps - Top BWFC improvements identified: DataBoundWebControl<T>, TagKey/AddAttributesToRender Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Session: 2026-03-22-migration-docs-and-issues Requested by: User Changes: - Orchestration logs for Beast (migration docs) and Forge (upstream issues) - Session log documenting documentation completion + issue specifications - Merged Beast and Forge decisions from inbox to decisions.md - Updated forge/history.md with upstream issue numbers (FritzAndFriends#490-FritzAndFriends#496) - Deleted merged inbox files
Implement 6 phases of custom control shimming for Web Forms Blazor migration: Phase 1 (P2 FritzAndFriends#492): TagKey + AddAttributesToRender on WebControl - TagKey property (default Span), TagName accessor - AddAttributesToRender virtual method (ID, CssClass, Style, ToolTip, Enabled) - RenderBeginTag/RenderEndTag pipeline matching Web Forms rendering lifecycle - Backward compatible with controls overriding Render() directly Phase 2 (P3 FritzAndFriends#493): HtmlTextWriter enum expansion - 57 new HtmlTextWriterTag members (HTML5 semantic, media, structural) - 43 new HtmlTextWriterAttribute members (form, ARIA, state, linking) - 65 new HtmlTextWriterStyle members (flexbox, grid, visual, position, text) - Fallback ToString().ToLowerInvariant() for resilience Phase 3 (P1 FritzAndFriends#490): DataBoundWebControl + DataBoundWebControl<T> - Bridges WebControl rendering (TagKey) with data binding (DataSource) - PerformDataBinding(IEnumerable) virtual for subclass data consumption - Generic version provides TypedDataItems for compile-time type safety Phase 4 (P4 FritzAndFriends#491): CompositeControl fix + shim types - LiteralControl/Literal: raw text rendering without outer tag - Panel (div), PlaceHolder (invisible), HtmlGenericControl (any tag) - INamingContainer marker interface - CompositeControl.RenderChildren graceful fallback for non-WebControl children Phase 5 (P5 FritzAndFriends#494): TemplatedWebControl (ITemplate RenderFragment bridge) - RenderTemplate() inserts RenderFragment into HtmlTextWriter output - Placeholder-based interleaving in BuildRenderTree - Null template graceful no-op Phase 6 (FritzAndFriends#496): FindControlRecursive - Deep search across naming container boundaries - Added to BaseWebFormsComponent alongside existing flat FindControl Tests: 48 new bUnit tests (123 total custom control tests passing) Docs: 3 migration guides (User-Controls, FindControl, CustomControl-BaseClasses) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| // Emit any remaining markup after the last placeholder | ||
| if (!string.IsNullOrEmpty(remaining)) | ||
| { | ||
| builder.AddMarkupContent(sequence++, remaining); |
Check warning
Code scanning / CodeQL
Useless assignment to local variable Warning
| { | ||
| public class SimpleDataList : DataBoundWebControl | ||
| { | ||
| private List<string> _items = new(); |
Check notice
Code scanning / CodeQL
Missed 'readonly' opportunity Note test
Session: 2026-03-22-p1p5-custom-controls Requested by: Squad (Cyclops, Rogue, Beast, Coordinator) Changes: - Orchestration logs for Cyclops, Rogue, Beast - Session log documenting P1-P5 framework completion (33 tests, 4 source files, 2515 total tests passing) - Merged 9 architectural decisions from forge-p1p5-plan.md and copilot-directive inbox - Deleted inbox files after merge (forge-p1p5-plan.md, copilot-directive-2026-03-22T14-48-37Z.md) - Deduplicated decisions (no overlaps detected)
Add comprehensive developer documentation for the P1-P5 drop-in replacement framework covering: - Architecture and class hierarchy - API reference for all 9 classes/interfaces - 5 migration patterns with code examples - Design decisions and rationale - DepartmentPortal validation results - Test coverage map (48 new tests) - Upstream issue tracking (FritzAndFriends#490-FritzAndFriends#496) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace pessimistic 'What Can''t Be Shimmed' with evidence-backed 'Shimming & Migration Compatibility' covering: - ViewState Dictionary shim (already working) - Lifecycle event mapping (OnInit/OnLoad/OnPreRender/OnUnload/OnDisposed) - Theming system (ThemeProvider, ControlSkin, SkinBuilder) - Focus() via JS interop (pattern proven in validators) - BWFC001-BWFC014 Roslyn analyzer suite Only PostBack and DataSourceID remain as true architectural mismatches. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Session: 2026-03-22-scribe-merge Requested by: Beast (Spawn Manifest) Changes: - Merged beast-p1p5-devdocs.md: P1-P5 Developer Documentation Scope decision - Merged copilot-directive-2026-03-22T17-57-30Z.md: User directive on Section 6 shimming - Deleted inbox files after merge - decisions.md now contains both decisions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mirrors System.Web.UI.Control.Focus() API using fire-and-forget JS interop. - public virtual void Focus() on BaseWebFormsComponent - Null-safe for SSR (no JsRuntime) and missing ID - bwfc.Page.Focus(elementId) JS function in both Basepage.js and module - Uses proven pattern from validator SetFocus implementation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Cyclops history.md updated with Focus() learnings - Decision inbox from Focus() method implementation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…BWFC Migrated controls using BWFC CustomControls base classes: - StarRating (WebControl) star display with TagKey, AddAttributesToRender - NotificationBell (WebControl) bell icon with badge and drawer - EmployeeCard (WebControl) composite employee display card - EmployeeDataGrid (DataBoundWebControl) data-bound grid with paging/sorting - DepartmentBreadcrumb (WebControl) hierarchical breadcrumb with EventCallback - PollQuestion (WebControl) radio poll with vote EventCallback - SectionPanel (TemplatedWebControl) template-based section with RenderFragment Updated Dashboard.razor and Employees.razor to use migrated controls. Added CustomControls namespace to _Imports.razor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
New analyzers: - BWFC020: ViewStatePropertyPattern detects ViewState-backed properties, code fix converts to [Parameter] auto-property - BWFC021: FindControlUsage detects FindControl calls, code fix replaces with FindControlRecursive - BWFC022: PageClientScriptUsage detects Page.ClientScript usage - BWFC023: IPostBackEventHandlerUsage detects IPostBackEventHandler impl 9 new tests, 139 total analyzer tests passing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Session: 2026-03-22T18-28-00Z-migration-analyzers Requested by: Copilot CLI (Scribe) Changes: - Merged 3 inbox decisions into decisions.md (no duplicates detected) - Logged Cyclops orchestration: DepartmentPortal migration (7 controls Blazor, 10 new components, build passing) - Logged Colossus orchestration: BWFC020-023 analyzers (4 new, 2 code fixes, 9 tests, 139 total passing) - Logged session summary: Migration & Analyzers completion Files: - .squad/decisions.md merged (3 inbox files processed) - .squad/decisions/inbox/ cleaned (3 files deleted) - .squad/orchestration-log/2026-03-22T18-28-00Z-cyclops.md created - .squad/orchestration-log/2026-03-22T18-28-00Z-colossus.md created - .squad/log/2026-03-22T18-28-00Z-migration-analyzers.md created Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Analyzer now uses semantic analysis to check whether FindControl is called on a BaseWebFormsComponent subclass (BWFC's own recursive implementation) and skips those only flags non-BWFC types. - Updated diagnostic message to reference BWFC's FindControl on BaseWebFormsComponent with recursive search. - Code fix provider gutted: no automatic rename since the method IS called FindControl on BWFC. Migration requires inheriting from BaseWebFormsComponent, which is too complex for an automated fix. - Tests updated: added 3 negative tests for BWFC types, removed 2 obsolete code-fix rename tests, restored FindControlRecursive test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…atibility Web Forms Control.FindControl is the API developers know. Our implementation already does recursive search renaming to match the original API means zero code changes needed during migration. - Merged shallow + recursive into single FindControl method - Updated BWFC021 analyzer to skip calls on BWFC base class types - Updated dev-docs and migration guide references - 2515 main tests + 139 analyzer tests passing Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…page - Switch App.razor from local Bootstrap CSS to Bootstrap 5.3.3 CDN - Add Bootstrap Icons CDN for NotificationBell icon support - Copy Site.css from DepartmentPortal to wwwroot/css/site.css - Create Home.razor welcome page at /home with navigation cards - Fix SectionPanel duplicate CssClass parameter (was shadowing base class) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Home.razor now serves / and /home as the landing page. Dashboard.razor serves /dashboard. MainLayout nav link updated to /dashboard. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Design strategy for extracting CSS/JS from NuGet packages that use the legacy Web Forms pattern (packages.config + BundleConfig.cs + Content/ Scripts folders). Recommends hybrid approach: CDN for known OSS packages, extraction tool for custom packages, with bwfc migrate-assets CLI command. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
6 tasks
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Complete implementation of the P1-P5 Custom Controls "drop-in replacement" framework, enabling ASP.NET Web Forms custom controls to work in Blazor with minimal code changes. The primary migration change is
using System.Web.UI;using BlazorWebFormsComponents.CustomControls;.What's Included
Phase 1 (P2): TagKey + AddAttributesToRender (#492 )
WebControl.TagKeyproperty (default:Span, matching Web Forms)AddAttributesToRender(HtmlTextWriter)virtual method auto-adds ID, CssClass, Style, ToolTip, EnabledRenderBeginTag/RenderEndTagpipeline refactored to match Web FormsRender()flowPhase 2 (P3): HtmlTextWriter Enum Expansion (#493 )
Phase 3 (P1): DataBoundWebControl (#490 )
DataBoundWebControl+ genericDataBoundWebControl<T>base classesDataSourceparameter binding with HtmlTextWriter rendering pipelinePerformDataBinding(IEnumerable)virtual method for subclass consumptionPhase 4 (P4): CompositeControl + Shim Types (#491 )
LiteralControlrenders raw text/HTML (no outer tag)Panelrenders<div>with child controlsPlaceHolderinvisible container (no tag output)HtmlGenericControlrenders any HTML tagINamingContainermarker interfaceCompositeControlfixed for graceful non-WebControl child handlingPhase 5 (P5): TemplatedWebControl (#494 )
TemplatedWebControlbase class with placeholder-based RenderFragmentHtmlTextWriter interleavingRenderTemplate(HtmlTextWriter, RenderFragment)helper using<!--BWFC_TPL_N-->markersPhase 6: FindControlRecursive (#496 )
FindControlRecursive(string id)onBaseWebFormsComponentFocus() Method
public virtual void Focus()onBaseWebFormsComponentbwfc.Page.Focus(clientId))Test Coverage
Documentation
dev-docs/proposals/p1-p5-custom-controls-framework.md661-line comprehensive developer docdocs/Migration/User-Controls.mdASCX Razor migration guidedocs/Migration/FindControl-Migration.mdFindControl patternsdocs/Migration/CustomControl-BaseClasses.mdBase class migration referenceBreaking Changes
None. All changes are additive. Existing
WebControlsubclasses that overrideRender()continue to work unchanged.Related Issues
Closes #490, #491, #492, #493, #494, #496
Progress on #495