There is a conversation that happens in a lot of companies about once a year. Somebody points out that the internal application everybody depends on was written a long time ago, on a framework nobody talks about any more, by people who mostly do not work here. Somebody else asks what it would cost to replace. The number comes back large. Everybody agrees it is important. Nothing happens. Twelve months later the same conversation runs again, from the top.
The reason it stalls is that the question is wrong. “What would it cost to replace this?” has no useful answer, because most of these applications should not be replaced. They should be moved, in parts, in an order, with some parts left exactly where they are.
“It is old” is not a reason
Age is not a defect. An application that has run correctly for twelve years has proven something that a new one has not. If it earns money, the people who use it are fluent in it, and nothing about it is on fire, then the framework version is a fact rather than a problem.
What actually forces a decision is narrower, and worth being honest about, because if none of these are true you may be about to spend money for no return:
- You cannot hire for it. This is the most common real trigger, and the least discussed. When the pool of people who will touch a codebase gets thin enough, the risk stops being technical.
- Something it depends on has stopped being supported. Not the application. Something underneath it: the operating system, the framework, the database version, a payment integration, a library with a known vulnerability and no fixed release.
- The business needs something the architecture cannot give it. Mobile access, an API for a partner, single sign-on, an integration that assumes a modern auth flow.
- It is blocking something else. A data centre exit, a compliance requirement, an acquisition, an ERP upgrade that changes the contract underneath it.
- The cost of a small change has become absurd. When a two-line change takes three weeks because nobody can safely test it, that is a real, measurable tax and it compounds.
If one of those is true, the project has a business case. If none of them are, the honest recommendation is to do the security and dependency work, write down how the thing behaves, and revisit in a year. We would rather say that than sell a rewrite nobody needed.
The paths, and what each one actually costs
Web Forms
This is the blunt one. ASP.NET Web Forms only runs on .NET Framework. It was never brought forward, and it never will be. There is no compatibility shim and no automated conversion that produces a working modern application, because the entire model, with its page lifecycle, view state and server controls, has no equivalent on the other side.
So the UI layer is a rewrite. What is not a rewrite is everything behind it. In most Web Forms applications we look at, a large majority of the actual value sits in business logic and data access classes that are ordinary C# and move to current .NET with far less friction than anybody expects. The realistic shape is: keep the logic, port it, and rebuild the screens on Razor Pages, MVC or Blazor depending on how interactive they need to be.
The trap is scoping the whole application as a rewrite because the front end is one. That is how a nine-month project gets quoted for something closer to a three-month one.
ASMX and WCF services
ASMX is in the same position as Web Forms: .NET Framework only, no forward path. WCF is more nuanced, because CoreWCF exists and can carry a service forward largely intact if you genuinely need the WS-* stack, SOAP contracts or the transports.
Usually you do not. Most internal ASMX and WCF endpoints are doing something a plain JSON API over HTTP does more simply, and the callers are yours to update. The landing spot is normally ASP.NET Core minimal APIs or controllers, with the contract cleaned up on the way through rather than reproduced faithfully. Keeping a SOAP envelope alive purely because it was there in 2011 is a decision worth making deliberately rather than by default.
Where you cannot change the callers, because a partner or a device is on the other end, run the new API alongside and keep a thin compatibility endpoint. That is much cheaper than it sounds and it decouples your timeline from theirs.
WinForms, and the migration people get wrong
WinForms is not dead, and this is the correction that saves the most money. WinForms and WPF both run on current .NET, on Windows. A WinForms line-of-business application can move from .NET Framework to .NET 10 and stay WinForms. You get the modern runtime, current dependencies, better performance and a supported platform, without rewriting a single screen.
That matters because the reflex answer to “we have an old WinForms app” is increasingly “rewrite it in MAUI,” and for a lot of applications that is simply the wrong tool. MAUI is for cross-platform: iOS, Android, Windows and macOS from one codebase. If your application runs on Windows desktops in your own building and always will, MAUI buys you nothing and costs you a full UI rewrite.
MAUI is the right answer when the requirement has genuinely changed shape. Your field technicians need it on a phone. Your sales team needs it on an iPad. You want one codebase across desktop and mobile instead of two teams. Those are real reasons, and when they apply, the work is worth doing properly. We have written the detail of that path separately in the Xamarin to .NET MAUI migration playbook, and much of it applies equally coming from WinForms.
The order that works: get to current .NET first, still in WinForms, and treat the move to MAUI as a separate decision made afterwards on its own merits. Doing both at once is how these projects end up unable to say what broke.
The option nobody quotes
Leave it. Patch the stack underneath, fix the dependencies with known vulnerabilities, write down what the application does, and put it under a maintenance arrangement. For an application that works and is not blocking anything, this is frequently the highest-return option available, and it is the one you will never be offered by somebody selling a rebuild.
Where AI genuinely accelerates this, and where it does not
Migration is mostly a translation problem across a known boundary, and that is precisely what current AI tooling is good at. Used with discipline it changes the economics materially. Used as a magic wand it produces confident, plausible, wrong code at a rate no human reviewer can keep up with.
Where it helps
- Reading the codebase and telling you what is in it. Every screen, every stored procedure, every branch of business logic, turned into a structured as-built record of what the application does and why. On a codebase nobody has fully understood for a decade this is the highest-value application by a distance, and it is tedious enough at human speed that it never gets done.
- The mechanical translation. Web Forms code-behind to a controller and a view model. ASMX method to a minimal API endpoint. Old data access to something current. These are consistent, repetitive transformations, and consistency at volume is exactly the strength.
- Generating the tests you never had. The old application is a working specification of its own behaviour right up until you turn it off. Characterizing that behaviour as tests, while it still runs, gives you something to check the new build against. This single practice separates migrations that go well from migrations that go badly, and almost nobody did it before it became cheap.
- Dependency and vulnerability triage. Working out which of four hundred packages actually matter, what breaks if you move them, and in what order.
Where it does not
- Deciding what to keep. Some of what your application does is a deliberate business rule. Some is a workaround for a limitation that no longer exists. Some is a bug everyone quietly adapted to years ago. Telling those apart requires talking to the people who use it. No model knows which is which, and one confidently rewritten “bug” that was actually a rule is how you lose a month.
- Architecture. Whether this should be one service or four, whether that queue is necessary, what the failure modes are when a dependency is slow. These are judgment calls with consequences that show up in year three.
- Anything you do not verify. Generated code that compiles is not generated code that is correct. The review burden is real, it does not go away, and a team that skips it converts a migration into an outage.
The honest framing: AI does not remove the project. It removes most of the archaeology, most of the mechanical translation, and the excuse for not having a test suite. That is a large fraction of the cost and most of the risk, which is a very different claim from “AI rewrites your app.”
Future-proofing, meaning the next migration
Future-proofing is a phrase that usually means nothing. Made concrete, it means one thing: making the next one of these cheaper. Frameworks will keep changing. The goal is not to pick a stack that lasts forever, because none do. The goal is to be in a position where moving again is a project rather than a crisis.
What actually achieves that, in rough order of value:
- A test suite that describes behaviour. The single biggest determinant of whether a future migration is safe. If you take one thing from this article, take this one.
- Business logic that does not live in the UI. The reason Web Forms migrations hurt is that a decade of rules ended up in code-behind files. Logic that sits in its own layer moves to whatever comes next almost for free.
- Written-down requirements. Not documentation of the code, which rots. A record of the decisions and why, which does not.
- Boring, current dependencies. Fewer libraries, chosen for being maintained rather than clever, updated routinely instead of in a panic.
- Configuration and secrets outside the application. Cheap to do, and it removes an entire category of migration pain.
- A deployment you can run on demand. If shipping is hard, everything downstream of shipping is also hard.
What an aging stack actually exposes
Security on legacy applications gets discussed in the abstract, which makes it easy to defer. Concretely, on the applications we audit, the same short list comes up:
- An out-of-support framework or runtime. .NET Framework 4.6.x lost support in April 2022, and a lot of applications are still on it. Moving to 4.8 is frequently straightforward and is a real improvement even if you never move off .NET Framework at all.
- An out-of-support operating system. Windows Server 2012 R2 went out of support in October 2023 and is still in production in more places than anyone would like to admit.
- TLS and cipher configuration from the original build. Old protocol versions still enabled, which fail modern scans and increasingly break integrations with payment providers and partners.
- Dependencies with published vulnerabilities and available fixes. The unglamorous majority of real findings. Not exotic, just never done.
- Authentication that predates current practice. Home-grown session handling, passwords hashed with an algorithm that was fine in 2013, no multi-factor path.
- Secrets in configuration files. Sitting in source control, readable by everyone who has ever had a checkout.
Almost none of that requires the rewrite. It requires somebody to look, and then to do the work in priority order. Which is the argument for the last section.
Review, audit, and the awkward version of this conversation
Sometimes the useful engagement is not a migration at all. It is an outside read on a codebase and the team around it, delivered to the person who has to make a decision and has been getting conflicting answers.
What that produces:
- An as-built picture. What the application actually is, what it depends on, where the risk is concentrated, what is dead code and what only looks like it.
- A security and dependency position. Findings ranked by whether they are genuinely reachable, not a scanner dump with four hundred criticals nobody will read.
- A costed set of options. Stay and maintain, move in parts, rebuild. With real numbers and the reasoning shown, including the case for doing the least.
- An honest read on delivery. Whether the team can execute the plan, what is missing, and whether the constraint is people, process or tooling. This is the part clients ask for privately and it is often the actual question.
We will say when an internal team is doing fine and does not need us, because a review that always concludes “hire the reviewer” is worth nothing to the person reading it.
A sequence that works
- Capture the as-built first, while the old system still runs. Before any new-platform work. The window closes when it goes dark, and everything downstream is cheaper if you do this first.
- Do the security and dependency work immediately. It is independent of every other decision, it is comparatively cheap, and it is the part that has a deadline set by somebody else.
- Characterize behaviour as tests against the old system, so the new one has something to be measured against.
- Move the runtime before you move the architecture. Current .NET first, same shape. WinForms stays WinForms. Then decide about the UI, separately, on its own merits.
- Migrate in slices that can ship. One service, one screen group, one integration. Anything that cannot ship for six months cannot be corrected for six months.
- Run old and new in parallel through a real cutover. With actual users on the new path before the old one is switched off.
The takeaway
Most legacy .NET applications are in better shape than the conversation about them suggests. The framework being unfashionable is not a business problem. Not being able to hire, not being able to patch, and not being able to change it safely are business problems, and they have different fixes with different price tags.
Get the honest inventory first. Do the security work regardless. Move the runtime before the architecture. Use AI for the archaeology and the translation, not for the judgment. And be suspicious of anyone whose recommendation is a full rewrite before they have read your code.
Have one of these sitting on your risk register?
We build on .NET 10 and .NET MAUI, and we have spent a long time inside applications that other people wrote and nobody fully understood. If you want a straight read on what your options actually cost, whether that is a migration, a security and dependency pass, or an outside review of the codebase and the team, tell us what you are running. We will tell you if the answer is to leave it alone, and that is a genuine outcome rather than a polite one. You can also read more about how we approach custom software and cross-platform mobile.