Engineering2026-05-14
Moving off .NET Framework without a rewrite
Almost every .NET Framework application we are asked to replace should be migrated instead. Here is the order the work goes in, and the four things that actually block it.
- Author
- Daniel Okonkwo
- Published
- 14 MAY 2026
- Read
- 6 MIN
- Ref
- D57A0D
The application works. It has worked for eleven years. It cannot be upgraded, the people who wrote it have gone, and somebody has just quoted you a rewrite.
Before you accept that quote: most of these applications do not need one. The runtime is the problem, not the code, and the runtime can usually be changed incrementally while the application stays in production. I have taken four estates across this way and the pattern holds well enough to write down.
Do this first: find out what actually blocks you
Not what might block you. Run the upgrade assistant, then read the dependency graph yourself, because the tooling reports the symptom and you need the cause.
In practice the blockers fall into four groups, and they are very unequal in difficulty.
Packages with no modern equivalent. Usually the easiest. Most have a successor; a few need replacing with something differently shaped. Budget a day each and expect two to take a week.
System.Web. The genuinely structural one. If the code reaches for HttpContext.Current from a business-logic class — and it will — those call sites have to be untangled before anything moves. This is the bulk of the work in most migrations, and it is also work worth doing regardless of the runtime, because it is what makes the logic testable.
WCF services. Server side needs replacing. Client side is usually fine with the compatibility packages. Do not discover which one you have in month three.
Anything talking to Windows directly. Registry, COM interop, WMI, Windows-only cryptography. Sometimes fine, sometimes the reason a component stays where it is. Find these in week one.
In rough order of how much trouble each causes:
| Blocker | Frequency | Typical cost | Notes |
|---|---|---|---|
| Packages with no modern equivalent | Every project | A day each | Two of them will take a week |
System.Web reached from business logic | Every project | Weeks | The bulk of the work, and worth doing regardless |
| WCF services, server side | Common | 1–3 weeks | Client side is usually fine on the compatibility packages |
| Windows-only interop | Occasional | Days to never | Sometimes the reason a component stays put |
The order that works
1. Retarget the class libraries first. Move the shared libraries to netstandard2.0, which both runtimes load. Now the same library builds into the old application and the new one, and you have a bridge instead of a cliff.
2. Untangle System.Web from the logic. Where a business class reads HttpContext.Current, pass the value in instead. This is dull, mechanical and the single highest-value step in the whole exercise. It is also the step people skip, and skipping it is what turns a migration into a rewrite.
3. Stand up the new host alongside the old one. An ASP.NET Core application that serves nothing yet, deployed, in the pipeline, with health checks. It exists so the next step is a routing change rather than a launch.
4. Move endpoints behind a proxy. A reverse proxy in front of both — YARP does this well — routing per path. Move one endpoint. Watch it. Move the next. Both applications are in production simultaneously and traffic can go back within a minute.
5. Delete the old host last. Not in parallel with step four. When the last route has moved and stayed moved for a full reporting cycle.
What this costs
For a mid-sized application — a few hundred thousand lines, a normal dependency set — eight to sixteen weeks with two engineers is the range I would quote. Portside's quoting engine took eleven.
A rewrite of the same application is a year and change, and it is a year during which the business cannot have anything else. That difference is the entire argument.
When a rewrite is right
I am not claiming it never is. Rewrite when:
- The data model is the actual problem. A runtime change will not fix a schema that was wrong in 2013 and has been worked around ever since.
- The application is small enough that a rewrite is under three months. Below that threshold the incremental machinery costs more than it saves.
- The business rules are genuinely obsolete. If half of what the system does should no longer be done, migrating it forward preserves the wrong thing carefully.
Notice that none of those are "the code is old" or "the framework is out of support". Both are true of most of these applications and neither is sufficient on its own.
The thing nobody puts in the estimate
Write down each blocker as you clear it, and how. You will hit the same four categories on the next application, and the second migration should be meaningfully cheaper than the first.
Ours are, and the record of why is the reason.