Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions BlazorMeetsWebForms.sln
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionInfo", "SolutionInf
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BeforeWebForms", "samples\BeforeWebForms\BeforeWebForms.csproj", "{CA277C6F-A3DD-4FAF-9B7C-56E7B844CEF7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorWebFormsComponents.Cli", "src\BlazorWebFormsComponents.Cli\BlazorWebFormsComponents.Cli.csproj", "{68B5F091-1D95-4E31-A5C3-64211108325E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -155,6 +159,24 @@ Global
{CA277C6F-A3DD-4FAF-9B7C-56E7B844CEF7}.WebForms|x64.Build.0 = WebForms|Any CPU
{CA277C6F-A3DD-4FAF-9B7C-56E7B844CEF7}.WebForms|x86.ActiveCfg = WebForms|Any CPU
{CA277C6F-A3DD-4FAF-9B7C-56E7B844CEF7}.WebForms|x86.Build.0 = WebForms|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.Debug|x64.ActiveCfg = Debug|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.Debug|x64.Build.0 = Debug|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.Debug|x86.ActiveCfg = Debug|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.Debug|x86.Build.0 = Debug|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.Release|Any CPU.Build.0 = Release|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.Release|x64.ActiveCfg = Release|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.Release|x64.Build.0 = Release|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.Release|x86.ActiveCfg = Release|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.Release|x86.Build.0 = Release|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.WebForms|Any CPU.ActiveCfg = Debug|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.WebForms|Any CPU.Build.0 = Debug|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.WebForms|x64.ActiveCfg = Debug|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.WebForms|x64.Build.0 = Debug|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.WebForms|x86.ActiveCfg = Debug|Any CPU
{68B5F091-1D95-4E31-A5C3-64211108325E}.WebForms|x86.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -165,6 +187,7 @@ Global
{7218D3E2-38BD-429D-9D84-15E688BDC4A1} = {240E45D9-B9FF-42E8-B0C1-332861E02DBF}
{1669CD22-5CCE-4D96-A02B-31D81B5EFB2B} = {240E45D9-B9FF-42E8-B0C1-332861E02DBF}
{CA277C6F-A3DD-4FAF-9B7C-56E7B844CEF7} = {240E45D9-B9FF-42E8-B0C1-332861E02DBF}
{68B5F091-1D95-4E31-A5C3-64211108325E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E288F9FB-039F-4718-8AEB-85F89B29EB4E}
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,26 @@ A collection of Blazor components that emulate the web forms components of the s

[Live Samples running on Azure](https://blazorwebformscomponents.azurewebsites.net)

## WebForms to Blazor CLI Tool

A command-line tool is available to help automate the conversion of ASP.NET Web Forms user controls (.ascx) to Blazor Razor components (.razor). The tool handles common conversions like:
- Control directives to `@inherits`
- ASP.NET server control syntax (`<asp:Button>` → `<Button>`)
- Expression syntax (`<%:`, `<%=` → `@()`)
- Common template issues (Item → context)

**Installation:**
```bash
dotnet tool install --global WebformsToBlazor.Cli
```

**Usage:**
```bash
webforms-to-blazor --input path/to/controls/ --recursive
```

See the [CLI tool README](src/BlazorWebFormsComponents.Cli/README.md) for full documentation.

## Approach + Considerations

We believe that Web Forms applications that have been well maintained and provide value should have a path forward to the new user-interface frameworks with minimal changes. This is not an application converted nor is it a patch that can be applied to your project that magically makes it work with ASP<span></span>.NET Core. This repository contains a library and series of strategies that will allow you to re-use much of your markup, much of your business code and help shorten your application re-write process.
Expand Down
5 changes: 5 additions & 0 deletions dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"version": 1,
"isRoot": true,
"tools": {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RootNamespace>BlazorWebFormsComponents.Cli</RootNamespace>
<AssemblyName>webforms-to-blazor</AssemblyName>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<!-- Global Tool Configuration -->
<PackAsTool>true</PackAsTool>
<ToolCommandName>webforms-to-blazor</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>

<!-- Package Metadata -->
<PackageId>WebformsToBlazor.Cli</PackageId>
<Authors>Jeffrey T. Fritz</Authors>
<Description>A command-line tool to convert ASP.NET Web Forms user controls (.ascx) to Blazor Razor components, with assistance from GitHub Copilot SDK</Description>
<Copyright>Copyright Jeffrey T. Fritz 2019-2026</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/FritzAndFriends/BlazorWebFormsComponents</PackageProjectUrl>
<PackageTags>blazor;webforms;aspnet;migration;cli;tool</PackageTags>
<RepositoryUrl>https://github.com/FritzAndFriends/BlazorWebFormsComponents</RepositoryUrl>
<RepositoryType>GitHub</RepositoryType>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\BlazorWebFormsComponents\BlazorWebFormsComponents.csproj" />
</ItemGroup>

</Project>
88 changes: 88 additions & 0 deletions src/BlazorWebFormsComponents.Cli/EXAMPLES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# WebForms to Blazor CLI Tool - Conversion Examples

This document shows examples of how the `webforms-to-blazor` CLI tool converts ASP.NET Web Forms user controls to Blazor Razor components.

## Example 1: Simple View Switcher

**Before (ViewSwitcher.ascx):**
```aspx
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ViewSwitcher.ascx.cs" Inherits="BeforeWebForms.ViewSwitcher" %>
<div id="viewSwitcher">
<%: CurrentView %> view | <a href="<%: SwitchUrl %>" data-ajax="false">Switch to <%: AlternateView %></a>
</div>
```

**After (ViewSwitcher.razor):**
```razor
@inherits BeforeWebForms.ViewSwitcher

<div id="viewSwitcher">
@(CurrentView) view | <a href="@(SwitchUrl)" data-ajax="false">Switch to @(AlternateView)</a>
</div>
```

**Conversions Made:**
- `<%@ Control ... Inherits="..." %>` → `@inherits ...`
- `<%: expression %>` → `@(expression)`

## Example 2: Product Card with Data Binding

**Before (ProductCard.ascx):**
```aspx
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ProductCard.ascx.cs" Inherits="MyApp.Controls.ProductCard" %>
<div class="product-card">
<asp:Image ID="imgProduct" ImageUrl='<%# Item.ImageUrl %>' AlternateText='<%# Item.Name %>' runat="server" />
<div class="product-info">
<h3><asp:Literal ID="litName" Text='<%# Item.Name %>' runat="server" /></h3>
<p class="price">$<%: Item.Price %></p>
<asp:Button ID="btnAddToCart" Text="Add to Cart" OnClick="AddToCart_Click" CssClass="btn-primary" runat="server" />
</div>
</div>
```

**After (ProductCard.razor):**
```razor
@inherits MyApp.Controls.ProductCard

<div class="product-card">
<Image ID="imgProduct" ImageUrl='@(context.ImageUrl)' AlternateText='@(context.Name)' />
<div class="product-info">
<h3><Literal ID="litName" Text='@(context.Name)' /></h3>
<p class="price">$@(context.Price)</p>
<Button ID="btnAddToCart" Text="Add to Cart" OnClick="AddToCart_Click" CssClass="btn-primary" />
</div>
</div>
```

**Conversions Made:**
- `<asp:Image ... />` → `<Image ... />`
- `<asp:Literal ... />` → `<Literal ... />`
- `<asp:Button ... />` → `<Button ... />`
- `runat="server"` removed
- `<%# Item.Property %>` → `@(context.Property)` (fixes Item vs context issue)
- `<%: expression %>` → `@(expression)`

## Usage

To convert these files, use:

```bash
# Single file
webforms-to-blazor --input ProductCard.ascx

# Directory (all .ascx files)
webforms-to-blazor --input ./Controls --recursive --overwrite
```

## What Still Needs Manual Work

After conversion, you'll typically need to:

1. **Update event handlers** to use Blazor's event system
2. **Convert data binding** to use `@bind` syntax where appropriate
3. **Add @using directives** for namespaces
4. **Migrate code-behind** logic to `@code` blocks or separate `.razor.cs` files
5. **Replace ViewState** with component state or parameters
6. **Update postback logic** to use Blazor's component lifecycle

See the [main README](README.md) for full documentation.
65 changes: 65 additions & 0 deletions src/BlazorWebFormsComponents.Cli/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System.CommandLine;
using BlazorWebFormsComponents.Cli.Services;

namespace BlazorWebFormsComponents.Cli;

class Program
{
static async Task<int> Main(string[] args)
{
var rootCommand = new RootCommand("WebForms to Blazor - Convert ASP.NET Web Forms user controls (.ascx) to Blazor Razor components")
{
Name = "webforms-to-blazor"
};

var inputOption = new Option<string>(
aliases: new[] { "--input", "-i" },
description: "Path to .ascx file or directory containing .ascx files to convert")
{
IsRequired = true
};

var outputOption = new Option<string>(
aliases: new[] { "--output", "-o" },
description: "Output directory for converted Razor components (defaults to input directory)");

var recursiveOption = new Option<bool>(
aliases: new[] { "--recursive", "-r" },
description: "Recursively process all .ascx files in subdirectories",
getDefaultValue: () => false);

var overwriteOption = new Option<bool>(
aliases: new[] { "--overwrite", "-f" },
description: "Overwrite existing .razor files without prompting",
getDefaultValue: () => false);

var aiOption = new Option<bool>(
aliases: new[] { "--use-ai" },
description: "Use GitHub Copilot/OpenAI for enhanced conversion (requires GITHUB_TOKEN or OPENAI_API_KEY environment variable)",
getDefaultValue: () => false);

rootCommand.AddOption(inputOption);
rootCommand.AddOption(outputOption);
rootCommand.AddOption(recursiveOption);
rootCommand.AddOption(overwriteOption);
rootCommand.AddOption(aiOption);

rootCommand.SetHandler(async (input, output, recursive, overwrite, useAi) =>
{
try
{
var converter = new AscxToRazorConverter(useAi);
await converter.ConvertAsync(input, output, recursive, overwrite);
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Error.WriteLine($"Error: {ex.Message}");
Console.ResetColor();
Environment.Exit(1);
}
}, inputOption, outputOption, recursiveOption, overwriteOption, aiOption);

return await rootCommand.InvokeAsync(args);
}
}
Loading
Loading