Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Feb 1, 2026

Web Forms applications often contain custom controls inheriting from WebControl or CompositeControl that use HtmlTextWriter for rendering. This adds a compatibility adapter layer enabling migration with minimal code changes.

Implementation

Core Adapter Classes

  • HtmlTextWriter - Buffers HTML output with Web Forms-compatible API (RenderBeginTag, AddAttribute, AddStyleAttribute)
  • WebControl - Base class adapter for simple custom controls; extends BaseStyledComponent
    • Automatically applies base attributes (ID, CssClass, Style) before calling Render() - no manual intervention required
  • CompositeControl - Base class adapter for controls with child components; manages child lifecycle and rendering

Roslyn Analyzer

  • BlazorWebFormsComponents.Analyzers - NuGet package providing automated migration assistance
  • BWFC001: Detects public properties missing [Parameter] attributes with automatic code fix
  • Works in Visual Studio, VS Code, and all Roslyn-powered IDEs with quick-fix support (Ctrl+.)

Key Behaviors

  • Automatic base attribute application (ID, CssClass, Style) before Render() method execution
  • Automatic class attribute concatenation when multiple classes are added
  • CSS style semicolon normalization to prevent malformed output
  • HTML attribute encoding (prevents XSS)

Migration Example

Web Forms:

public class SearchBox : CompositeControl
{
    protected override void CreateChildControls()
    {
        Controls.Add(new Label { Text = "Search:" });
        Controls.Add(new TextBox { ID = "query" });
        Controls.Add(new Button { Text = "Go" });
    }

    protected override void Render(HtmlTextWriter writer)
    {
        writer.AddAttribute(HtmlTextWriterAttribute.Class, "search-box");
        writer.RenderBeginTag(HtmlTextWriterTag.Div);
        RenderChildren(writer);
        writer.RenderEndTag();
    }
}

Blazor (minimal changes with analyzer assistance):

public class SearchBox : CompositeControl  // Same base class
{
    protected override void CreateChildControls()  // Same lifecycle
    {
        Controls.Add(new SimpleLabel { Text = "Search:" });  // Child controls
        Controls.Add(new SimpleButton { Text = "Go" });
    }

    protected override void Render(HtmlTextWriter writer)  // Same API
    {
        // Base attributes (ID, CssClass, Style) are automatically added
        writer.AddAttribute(HtmlTextWriterAttribute.Class, "search-box");
        writer.RenderBeginTag(HtmlTextWriterTag.Div);
        RenderChildren(writer);
        writer.RenderEndTag();
    }
}

Required changes: Add [Parameter] to properties (auto-fixed by BWFC001 analyzer). Base attributes are automatically applied.

Testing

23 tests covering rendering, attribute handling, style concatenation, child control management, and encoding. Analyzer builds successfully and integrates with the solution.

Documentation

  • Complete migration guide at docs/Migration/Custom-Controls.md with side-by-side examples, best practices, and common pitfalls
  • Analyzer README at src/BlazorWebFormsComponents.Analyzers/README.md with usage instructions and examples
  • Updated main README with adapter information and installation instructions
  • Consistent use of "adapter" terminology throughout documentation
Original prompt

This section details on the original issue you should resolve

<issue_title>Custom Control shim?</issue_title>
<issue_description>When we consider converting usercontrols, master pages, and pages we also need to consider converting custom controls.

Is there a shim abstract class that we could make available that these classes that inherit from WebControl or CompositeControl could inherit from and be able to work with Blazor?

I think this may be a separate .NET Standard class library to support this goal </issue_description>

Comments on the Issue (you are @copilot in this section)

@csharpfritz Ooh... HtmlTextWriter sounds like a VERY cool feature to add

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 3 commits February 1, 2026 03:19
…CompositeControl

Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
…and clarify documentation

Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
Copilot AI changed the title [WIP] Add custom control shim for Blazor compatibility Add custom control shim layer for Web Forms migration Feb 1, 2026
Copilot AI requested a review from csharpfritz February 1, 2026 03:29
@csharpfritz
Copy link
Collaborator

@copilot For those Required changes, can we create a Roslyn Analyzer with a fixer to help implement those items?

Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 1, 2026

@copilot For those Required changes, can we create a Roslyn Analyzer with a fixer to help implement those items?

Created a Roslyn analyzer package (BlazorWebFormsComponents.Analyzers) with two analyzers and code fixes:

BWFC001: Detects public properties in WebControl/CompositeControl classes missing [Parameter] attributes. Code fix automatically adds the attribute and using Microsoft.AspNetCore.Components; directive.

BWFC002: Detects Render methods that call RenderBeginTag() without calling AddBaseAttributes(writer) first. Code fix automatically inserts the call at the beginning of the method.

Both analyzers work in Visual Studio, VS Code, and other Roslyn-powered IDEs with quick-fix support (Ctrl+.).

See commit 7f1344d and the analyzer README for details.

Copilot AI changed the title Add custom control shim layer for Web Forms migration Add custom control shim layer with Roslyn analyzers for Web Forms migration Feb 1, 2026
…tainsKey

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
@csharpfritz csharpfritz marked this pull request as ready for review February 1, 2026 22:37
… can be combined

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
@csharpfritz
Copy link
Collaborator

@copilot Let's not call these classes "shims" but rather "adapters"

Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 2, 2026

@copilot Let's not call these classes "shims" but rather "adapters"

Updated terminology from "shim" to "adapter" throughout the documentation in commit 63f7f3c. Changed references in:

  • README.md: "Adapter Classes" instead of "Shim Classes"
  • docs/Migration/Custom-Controls.md: "adapter classes" and "custom control adapters"

Note: The branch name copilot/add-custom-control-shim and historical commit message from fc8e703 remain unchanged as they're already part of the git history.

Copilot AI changed the title Add custom control shim layer with Roslyn analyzers for Web Forms migration Add custom control adapter layer with Roslyn analyzers for Web Forms migration Feb 2, 2026
/// <param name="writer">The HtmlTextWriter to write output to.</param>
internal void RenderControl(HtmlTextWriter writer)
{
Render(writer);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Instead of forcing every implementation to call AddBaseAttributes(writer) - why not call that method here and eliminate the requirement? We can then make AddBaseAttributes private and eliminate the need for that analyzer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in commit 981fa77. Base attributes (ID, CssClass, Style) are now automatically added in BuildRenderTree before calling the user's Render method. This eliminates the need for:

  • Manual AddBaseAttributes(writer) calls in every implementation
  • The BWFC002 analyzer and its code fix provider

The AddBaseAttributes method is now private. All test components updated and 518 tests passing. Documentation updated to reflect automatic attribute application.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the unnecessary analyzer and fixer for this

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Remove the unnecessary analyzer and fixer for the AddBaseAttributes method

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already completed in commit 981fa77. The BWFC002 analyzer and code fix provider have been removed. Only BWFC001 (Parameter attribute detector) remains in the analyzer package.

… AddBaseAttributes() calls and BWFC002 analyzer

Co-authored-by: csharpfritz <78577+csharpfritz@users.noreply.github.com>
Copilot AI changed the title Add custom control adapter layer with Roslyn analyzers for Web Forms migration Add custom control adapter layer with Roslyn analyzer for Web Forms migration Feb 2, 2026
Copilot AI requested a review from csharpfritz February 2, 2026 09:17
@csharpfritz csharpfritz merged commit d9d1743 into dev Feb 2, 2026
5 checks passed
@csharpfritz csharpfritz deleted the copilot/add-custom-control-shim branch February 2, 2026 09:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom Control shim?

2 participants