Evaluating AWS Transform for a .NET Framework 4.8 to .NET 10 Migration
I’ve spent the last while planning the migration of a lending serviceability calculator from .NET Framework 4.8 on IIS to .NET 10 ASP.NET Core on ECS Fargate. The obvious question early on was whether AWS Transform for .NET should do the porting, and the migration plan of record actually named it as the tool. After a proper evaluation I reversed that decision, and the reasons say something useful about porting tools in general, so this post walks through the evaluation rather than just the verdict.
The starting point
The application is a classic mid-2010s enterprise shape: four old-style projects under one solution, packages.config, ASP.NET Web API 2 plus MVC5 hosted on IIS, and a SQL Server reference database reached through LINQ-to-SQL via a .dbml designer file. It computes loan serviceability, so correctness is not negotiable; the output is decimal arithmetic that downstream systems and, ultimately, lending decisions depend on. About 8,200 lines of C# across the four projects, of which roughly 7,500 sit in a framework-agnostic calculation engine.
The target is a different world entirely: .NET 10 on Linux/ARM64 containers behind an internal ALB, with the reference tables moved out of the process into Aurora Serverless v2 fronted by a small Lambda, so the calculator itself ends up with no database connection at all. Seven waves, from a regression baseline through containerisation, front-door integration, a shadow-traffic parity test against the incumbent MuleSoft path, and a weighted cutover. The plan estimates 12 to 14 weeks end to end.
What AWS Transform actually offers
AWS Transform for .NET is a porting agent: point it at a .NET Framework codebase and it produces a .NET (modern) equivalent, handling the project-file modernisation, namespace swaps, API replacements, and the long tail of mechanical changes. It comes in two forms, and the difference matters more than the marketing suggests.
The web experience is the full product: connect your repos, let the agent run in AWS, get back transformed branches with a plan and a worklog. The catch for us is the word “connect”. It reaches source through CodeConnections, and our code lives on self-hosted GitHub Enterprise inside the corporate network, so lighting that up means a connector plus VPC or VPN reachability before the tool reads a single line. That’s real platform work, and for one repository it’s hard to justify.
The Visual Studio extension is the more modest cousin: it runs locally against an open solution, needs no repo connector, and the core agent is free. That version stayed in the evaluation to the end.
By AWS’s own framing, and consistent with what I saw, the tool automates roughly 70 per cent of a Framework-to-modern port. Our own pre-transformation assessment produced a manual remediation backlog of 15 items covering the remaining 30 per cent. So far, so reasonable; the question is what lives in each bucket.
Where the difficulty actually sits
Here’s the finding that decided it: the 70 per cent the tool automates is the cheap part, and the 30 per cent it can’t do is where all the risk lives. The engine is mostly framework-agnostic C#; its only real seams are System.Configuration and System.Data.Linq. The DTOs are clean. The web host is thin once the auto-generated HelpPage boilerplate is deleted. The genuinely hard items are architectural, and they’re concentrated:
- LINQ-to-SQL doesn’t get ported, it gets replaced by an HTTP client to the reference-data Lambda, because the target architecture forbids the calculator any direct database connection.
- The static lookup-table caches become
IMemoryCachewith a TTL, since static fields fragment per Fargate task and quietly break horizontal scaling. ConfigurationManagerbecomesIConfigurationwith typed options bound from environment variables.- An MVC5 render-view-to-string path (a popup the API returns as HTML) moves to
Razor.Templating.Core, which we’d already de-risked in a spike. - Globalisation on Linux: without
InvariantGlobalization=falseand an explicit en-NZ culture,decimal.ToString("c")renders the generic ¤ sign instead of$. Also spike-confirmed, also the kind of thing no porting tool flags.
And this is where the tool doesn’t just fail to help; on the largest single item it’s wrong by design. AWS Transform ports a .dbml to Entity Framework Core, which is a perfectly sensible default and precisely what our target architecture prohibits. Its output for the biggest, riskiest backlog item would be deleted on arrival. A tool whose headline contribution to your hardest problem is a diff you must throw away is not accelerating your critical path.
The other frictions
Three more things counted against it, none individually fatal.
The correctness gate doesn’t move. Our acceptance test is a golden-file regression suite: captured input JSON paired with byte-identical expected output from the Framework engine, re-run after every change. That suite has to be built and passed regardless of who or what writes the port, so the tool doesn’t shorten the path to “proven correct”; it only shortens the typing, which was never the bottleneck.
The review model fights ours. Transform hands back the port as one large changeset, where our plan wants one backlog item per commit so the risky changes are individually reviewable. Unpicking a bulk port into fifteen reviewable commits is work you’ve created, not saved.
And the build-test loop is local. The engine builds and runs its golden files on a Windows dev box in seconds; a cloud batch tool with a queue in the middle is a poor fit for the tight fix-and-re-run loop the gate demands.
The decision
Human-led and AI-assisted instead: the port runs in the IDE with Anthropic’s Claude models doing the typing, routed by difficulty (Opus on the architecture seams, Sonnet on the mechanical bulk), a human in the review seat per hunk, and the golden-file gate run before every commit. Which harness drives the models is deliberately an open choice rather than part of the decision: Claude Code runs the workflow natively, VS Code with GitHub Copilot’s agent mode can drive the same models where that’s the approved enterprise tooling, and orgs that need AWS-resident inference can point either at Claude on Bedrock. The evaluation verdict is about the models and the working method, not any single front-end. The mechanical 70 per cent that Transform would have automated is exactly the work an LLM does quickly and cheaply under supervision, so we lose little by declining the tool, and we gain per-item commits, a local loop, and correct architectural decisions on the seams.
AWS Transform keeps two roles. The Visual Studio extension remains on the table as a free first pass whose csproj and packages.config conversion output is worth harvesting as a starting diff before continuing by hand. And the decision explicitly flips if the scope changes: if this application turns out to be the pilot for migrating a wider .NET Framework estate, the CodeConnections plumbing and the built-in governance artefacts amortise across a portfolio, and tool-led becomes the right call. For one repo, it isn’t.
The general lesson
Porting tools are sized against volume, and volume is the wrong axis. The evaluation question that mattered here was not “how much code is there” but “where does the difficulty sit”, and the honest answer was: in about five architectural seams totalling a few hundred lines, every one of which needs a decision the tool either can’t make or makes wrongly. Measure that before you pick tooling. If your hard 30 per cent is concentrated and architectural, a tool that excels at the diffuse 70 per cent is solving the part of your problem that was never going to hurt you.
Next week’s post covers how the port actually ran: Claude models driving the whole lifecycle, from the evaluation docs above through a generated multi-agent harness for the transformation, to the deployment, including an org-level policy surprise that invalidated a chunk of the design after the code was already signed and working.