A Claude Code agent framework that autonomously converts .NET Framework 4.8 Web Forms applications to .NET 10 Razor Pages using Microsoft Clean Architecture. It decomposes requirements, plans the conversion, and executes it screen by screen — with two mandatory human review gates before any code is touched.
The framework operates in three phases separated by human checkpoints:
Phase 0: Decompose ──► [Human Review] ──► Phase 1: Plan ──► [Human Review] ──► Phase 2: Convert
Phase 0 — Decompose. Claude reads every .aspx, .aspx.cs, and .ascx file and produces:
- A
Requirements.mdcovering every screen, every field, every validator, and every security rule - A security analysis flagging PII fields, CSRF gaps, SQL injection surface, and IDOR risks
- A code-behind audit mapping every event handler to a business operation with complexity ratings
- A Clean Architecture layer assignment for every
.csfile in the project - Auto-detection of which scenario (small or large) fits the application
Phase 1 — Plan. Claude reads Requirements.md and produces:
migration-manifest.json— every file classified, layered, and given complexity + story pointsConversionPlan.md— per-screen target file paths, PageModel skeletons, field mappings, security remediations, and skill execution order
Phase 2 — Convert. Claude scaffolds the Clean Architecture solution, then converts infrastructure, bootstrap, data, shared controls, pages, cross-cutting concerns, and tests — in dependency order, with build validation between layers.
The framework auto-detects which structure fits your application during Phase 0. You can override the choice before approving requirements.
Use when: ≤ ~20 pages, no external API consumers, single team, simple CRUD.
src/
{AppName}.Domain/ ← entities, value objects, domain interfaces, specifications
{AppName}.Infrastructure/ ← EF Core, repositories, identity, external services
{AppName}.Web/ ← Razor Pages (PageModels inject IRepository<T> directly)
tests/
{AppName}.Tests/
PageModels call repositories directly from Domain — no MediatR, no Application layer. Keeps the solution simple for apps that don't need the extra abstraction.
Use when: > ~20 pages, external API consumers (mobile / third-party), multiple teams, full CQRS.
src/
{AppName}.Domain/ ← entities, value objects, domain interfaces, domain events
{AppName}.Application/ ← MediatR commands/queries, DTOs, FluentValidation, AutoMapper
{AppName}.Infrastructure/ ← EF Core, repositories, identity, external services
{AppName}.Api/ ← ASP.NET Core Minimal API, Swagger, API versioning
{AppName}.Web/ ← Razor Pages consuming Api via typed HttpClient only
tests/
{AppName}.{Layer}.Tests/ ← one test project per layer (five total)
The Web project never touches the database. All data access goes through the API. The API is independently deployable and versioned.
| Tool | Minimum version | Install |
|---|---|---|
| .NET SDK | 10.x | winget install Microsoft.DotNet.SDK.10 |
| Node.js | 18+ | winget install OpenJS.NodeJS.LTS |
| npm | bundled with Node | — |
| Claude Code CLI | latest | npm install -g @anthropic-ai/claude-code |
git clone <repo-url> net-migration-framework
cd net-migration-frameworkUser-level install only (installs MCP servers to your profile, no project changes):
.\install.ps1Full install into a target project (also deploys all framework files):
.\install.ps1 -ProjectPath "C:\Code\MyLegacyWebFormsApp"With source control tokens for PR creation:
.\install.ps1 -ProjectPath "C:\Code\MyLegacyApp" -GitHubPat "ghp_xxxx"
# or
.\install.ps1 -ProjectPath "C:\Code\MyLegacyApp" -AzureDevOpsPat "your-pat"The script:
- Validates all prerequisites
- Copies
dotnet-mcp,nuget-mcp, andmslearn-mcpto~/.claude/mcp-servers/and runsnpm installin each - Merges MCP server registrations into your user-level
~/.claude/settings.json - If
-ProjectPathis given: deploysCLAUDE.md,.mcp.json,.claude/settings.json, agents, skills, anddocs/templates into the project - Smoke-tests all three MCP servers with
node --check
If you did not use -ProjectPath, copy these files to your project root:
CLAUDE.md → {project root}/CLAUDE.md
project-mcp.json → {project root}/.mcp.json
project-settings.json → {project root}/.claude/settings.json
claude-agents/ → {project root}/.claude/agents/
skills/ → {project root}/.claude/skills/
docs/ → {project root}/docs/
cd C:\Code\MyLegacyWebFormsApp
claudeThen type:
start migration
Claude will run Phase 0 automatically and stop when it's done.
| Say this | What happens |
|---|---|
start migration |
Runs from current position in state |
requirements approved |
Sets gate and begins Phase 1 |
plan approved |
Sets gate and begins Phase 2 |
migration status |
Prints current state from docs/migration-state.json |
reset migration |
Asks for confirmation, then clears state |
Gate 1 — after Phase 0:
Review docs/Requirements.md and docs/clean-arch-layer-summary.md. The layer summary shows which scenario was detected and why. If the wrong scenario was chosen, edit docs/migration-state.json and change "scenario": "small" or "scenario": "large" before approving. For Scenario 2, also review docs/api-surface.md which shows the derived API endpoint table.
Gate 2 — after Phase 1:
Review docs/ConversionPlan.md, especially the Manual-only entries. Pages flagged Manual-only (VB.NET code-behind, dynamic control generation, deeply nested UpdatePanel) are excluded from automation and listed in the final PR description.
net-migration-framework/
├── README.md ← you are here
├── CLAUDE.md ← orchestrator instructions (copy to project root)
├── install.ps1 ← PowerShell install script
├── project-mcp.json ← project-level MCP config (→ .mcp.json)
├── project-settings.json ← project-level Claude settings
│
├── mcp-servers/
│ ├── dotnet-mcp/index.js ← dotnet CLI wrapper (build, test, format, ef)
│ ├── nuget-mcp/index.js ← NuGet API (search, versions, replacements)
│ └── mslearn-mcp/index.js ← Microsoft Learn documentation search
│
├── claude-agents/
│ ├── project-analyzer.md ← reads project, writes docs/project-inventory.md
│ ├── requirements-extractor.md ← Phase 0 loop over all pages
│ ├── conversion-planner-agent.md ← Phase 1 (audit + plan)
│ ├── file-converter.md ← converts a single screen
│ ├── build-validator.md ← runs dotnet build + test, categorises errors
│ └── pr-creator.md ← builds and opens PR
│
├── skills/
│ ├── phase0-decompose/
│ │ ├── screen-decomposer/ ← extracts fields, controls, routes from .aspx
│ │ ├── security-analyzer/ ← auth, PII, injection, CSRF per screen
│ │ ├── codebehind-auditor/ ← maps event handlers → business operations
│ │ ├── shared-control-extractor/ ← inventories .ascx controls + dependency order
│ │ ├── clean-arch-mapper/ ← detects scenario, maps every .cs to a layer
│ │ └── requirements-writer/ ← merges all Phase 0 output into Requirements.md
│ ├── phase1-plan/
│ │ ├── net-migration-audit/ ← classifies every file → migration-manifest.json
│ │ └── conversion-planner/ ← produces ConversionPlan.md
│ └── phase2-convert/
│ ├── clean-arch-scaffold/ ← creates the solution structure (Scenario 1 or 2)
│ ├── csproj-migration/ ← .csproj → SDK-style net10.0
│ ├── nuget-upgrade/ ← packages.config → PackageReference
│ ├── webconfig-migration/ ← Web.config → appsettings.json
│ ├── globalasax-to-program/ ← Global.asax → Program.cs
│ ├── ef6-to-efcore/ ← Entity Framework 6 → EF Core 10
│ ├── aspx-to-razor/ ← .aspx + .aspx.cs → .cshtml + PageModel
│ ├── htmlhelper-to-taghelper/ ← @Html.* → Tag Helpers
│ ├── sync-to-async/ ← sync DB calls → async/await + CancellationToken
│ ├── auth-migration/ ← FormsAuthentication → cookie auth / Identity
│ ├── bundling-migration/ ← BundleConfig → LibMan + esbuild
│ └── migration-test-gen/ ← generates xUnit + Moq + FluentAssertions tests
│
└── docs/
├── migration-state.json ← state machine (phase, gates, per-screen status)
├── Requirements.md ← generated by Phase 0
└── ConversionPlan.md ← generated by Phase 1
Three Node.js MCP servers extend Claude Code's capabilities:
Wraps the dotnet CLI. Tools: dotnet_build, dotnet_test, dotnet_format, dotnet_ef_migrations_add, dotnet_ef_database_update, dotnet_list_packages, dotnet_version. Used by build-validator and ef6-to-efcore.
Queries the NuGet API. Tools: nuget_search, nuget_get_versions, nuget_latest_compatible, nuget_find_replacement. The replacement table includes 20+ known mappings (EntityFramework → EF Core, Unity/Ninject → built-in DI, Web.Optimization → LibMan, etc.). Used by nuget-upgrade.
Queries learn.microsoft.com. Tools: mslearn_search, mslearn_get_article, mslearn_api_lookup, mslearn_migration_guide, mslearn_search_breaking_changes. Hardcoded migration guide URLs for: aspnet-to-aspnetcore, ef6-to-efcore, membership-to-identity, clean-architecture, razor-pages, auth-cookie, tag-helpers, and more. Used by all Phase 2 skills before converting any .NET Framework API.
Skills run in this order. Each layer is build-validated before the next begins.
| Layer | Skills | Writes to |
|---|---|---|
| Scaffold | clean-arch-scaffold |
src/ solution structure |
| Infrastructure | csproj-migration, nuget-upgrade |
all .csproj files |
| Bootstrap | globalasax-to-program, webconfig-migration |
Program.cs, appsettings.json |
| Data | ef6-to-efcore |
Infrastructure/Data/ |
| Shared UI | (ascx converter) | Web/Pages/Shared/, view components |
| Page UI | aspx-to-razor, htmlhelper-to-taghelper |
Web/Pages/ (+ Api/Endpoints/ for Scenario 2) |
| Cross-cutting | sync-to-async, auth-migration, bundling-migration |
across all layers |
| Tests | migration-test-gen |
tests/ |
Scenario 2 additions in Page UI layer: for each page, aspx-to-razor also generates the MediatR command/query handler in Application/UseCases/ and the Minimal API endpoint in Api/Endpoints/. The PageModel in Web/Pages/ injects a typed HttpClient wrapper ({Entity}ApiClient) rather than a repository.
- Each screen gets up to 3 retry attempts if conversion fails or the build breaks
- After 3 failures, the screen is marked
failedindocs/migration-state.json, errors are logged todocs/build-errors.md, and conversion continues with the next screen - Screens that cannot be automated (VB.NET code-behind, dynamic control generation, deeply nested UpdatePanel) are flagged Manual-only with the reason. They are excluded from conversion and listed in the PR description
All progress is tracked in docs/migration-state.json. If Claude Code is interrupted, running start migration resumes from the last completed step.
Key fields:
{
"phase": 0,
"phaseStatus": "awaiting-review",
"scenario": "small",
"requirementsApproved": false,
"planApproved": false,
"lastProcessedPage": "Admin/Users/Edit.aspx",
"screens": [
{
"path": "Admin/Users/Edit.aspx",
"status": "converted",
"complexity": "Medium",
"manualOnly": false,
"blockedBy": []
}
]
}Ardalis.GuardClauses— guard clauses for domain validationArdalis.Result— discriminated result type (replaces throwing exceptions for expected failures)Ardalis.Specification— specification pattern for EF Core queriesMicrosoft.EntityFrameworkCore.SqlServer— EF Core 10 SQL Server providerMicrosoft.AspNetCore.Identity.EntityFrameworkCore— ASP.NET Core Identity
MediatR— CQRS command/query dispatchingFluentValidation— command/query validationAutoMapper— entity → DTO mappingSwashbuckle.AspNetCore— Swagger / OpenAPIAsp.Versioning.Http— API versioning
xUnit— test frameworkMoq— mockingFluentAssertions— readable assertionsMicrosoft.AspNetCore.Mvc.Testing— integration testing withWebApplicationFactory
- VB.NET code-behind is flagged Manual-only. The framework only converts C# Web Forms.
- Dynamic control generation (controls added in
Page_Loadat runtime) cannot be fully automated. - Third-party Web Forms controls (Telerik, DevExpress, Infragistics) require manual replacement.
- ASMX web services are not in scope — convert to Minimal API endpoints manually.
- The framework targets SQL Server via EF Core. Other databases need
Infrastructure/DependencyInjection.csedits after scaffolding.