Migrate ASP.NET Web Forms User Controls (.ascx) to Blazor components using BlazorWebFormsComponents. Covers ASCX-to-Razor conversion, code-behind preservation, tag prefix resolution, property/event mapping, and partial-class base class alignment. WHEN: 'migrate ascx', 'convert user control', 'ascx to blazor', 'user control migration'. FOR SINGLE OPERATIONS: use /bwfc-migration for full page migration, /bwfc-custom-control-migration for WebControl-based controls.
73
92%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
ASP.NET Web Forms User Controls (.ascx files) are reusable markup fragments with code-behind. They map directly to Blazor .razor components with .razor.cs code-behind files.
The webforms-to-blazor CLI tool handles L1 conversion automatically. This skill guides L2 repair for common patterns that require contextual understanding.
| Step | What Happens |
|---|---|
| 1. Directive removal | <%@ Control ... %> is stripped |
| 2. Markup transforms | asp: prefixes removed, expressions converted |
| 3. Code-behind preservation | Base class remapped, usings updated |
4. @inherits injection | Added to .razor when code-behind inherits a CustomControls type |
| 5. Output | .razor + .razor.cs pair in the output project |
ASCX code-behind files contain business logic that should compile unchanged. The CLI:
System.Web.UI.* usingsusing BlazorWebFormsComponents.CustomControls;UserControl base class (now mapped to BWFC's UserControl)@inherits UserControl into the .razor fileDo NOT rewrite code-behind logic. Fix compile errors by adding missing shims or stubs.
Web Forms exposes ASCX properties to parent pages. In Blazor, these become [Parameter] properties:
// Web Forms (worked without attribute)
public string Title { get; set; }
// Blazor (requires [Parameter])
[Parameter]
public string Title { get; set; }The CLI's ParameterAttributeTransform handles this automatically for public properties.
ASCX controls that raise events to parent pages:
// Web Forms
public event EventHandler ItemSelected;
protected void OnItemSelected(EventArgs e) => ItemSelected?.Invoke(this, e);
// Blazor
[Parameter]
public EventCallback<EventArgs> ItemSelected { get; set; }
protected async Task OnItemSelected(EventArgs e) => await ItemSelected.InvokeAsync(e);Web Forms requires tag prefix registration in Web.config or <%@ Register %>:
<!-- Web.config -->
<add tagPrefix="uc" src="~/Controls/StatusPanel.ascx" tagName="StatusPanel" />
<!-- Usage -->
<uc:StatusPanel runat="server" Title="Dashboard" />In Blazor, components are referenced directly by name (no prefix, no registration):
<StatusPanel Title="Dashboard" />The CLI strips <%@ Register %> directives and removes tag prefixes automatically.
The .razor file and .razor.cs file form a partial class. Their base class must agree:
@* In the .razor file *@
@inherits UserControl// In the .razor.cs file
public partial class MyControl : UserControl { }If the CLI misses the @inherits directive, you'll get CS0263. Add it manually.
Web Forms can load user controls dynamically:
var ctrl = (StatusPanel)LoadControl("~/Controls/StatusPanel.ascx");
ctrl.Title = "Dynamic";
PlaceHolder1.Controls.Add(ctrl);Blazor equivalent: Use RenderFragment or DynamicComponent:
<DynamicComponent Type="typeof(StatusPanel)"
Parameters="@(new Dictionary<string, object> { ["Title"] = "Dynamic" })" />Code-behind that uses FindControl to locate child controls works unchanged through the BWFC runtime:
// This works in Blazor via BWFC's FindControl runtime
var lbl = (Label)FindControl("lblStatus");
lbl.Text = "Updated";Web Forms controls often wrap and expose child control properties:
// Web Forms pattern
public string StatusText
{
get { return lblStatus.Text; }
set { lblStatus.Text = value; }
}In Blazor, this still works if lblStatus is resolved via FindControl or @ref. The BWFC runtime supports both patterns.
User control lifecycle methods auto-wire through BWFC's virtual methods:
// Works unchanged — BaseWebFormsComponent provides virtual Page_Load
protected override void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
BindData();
}
}// Before (Web Forms)
Controls/
StatusPanel.ascx ← Markup
StatusPanel.ascx.cs ← Code-behind
StatusPanel.ascx.designer.cs ← Auto-generated (discarded)
// After (Blazor)
Components/Controls/ (or wherever the CLI places them)
StatusPanel.razor ← Converted markup + @inherits UserControl
StatusPanel.razor.cs ← Preserved code-behind (usings updated)For each ASCX file in the migration:
.razor file has @inherits UserControl (or appropriate base).razor file has @using BlazorWebFormsComponents.CustomControls[Parameter] attributeEventCallback<T>Page_Load / Page_Init overrides work (virtual methods)FindControl calls resolve at runtimeFritzAndFriends/BlazorWebFormsComponents
3154758
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.