A .NET Modernization, End to End

ModernLift · ·11 min read
Part 8 of 8

A typical .NET modernization moves a Framework application — Web Forms screens, WCF services, a VB.NET component, and a tangled monolith — onto modern .NET slice by slice. The program assesses the estate, multi-targets the shared logic, stands up a facade, then migrates one slice at a time: high-value Web Forms pages rebuilt on Blazor, WCF services replaced with gRPC and CoreWCF, the VB.NET logic extracted and converted, each slice proven equivalent before cutover. The end state is a modular monolith on modern .NET with a few extracted services where pressure justified them, the Framework app retired, and the business running uninterrupted throughout.

Seven parts have built the method piece by piece. This final part puts it together on one system, because a modernization is never “the Web Forms migration” or “the WCF migration” in isolation — it is all of them, sequenced into a single program that keeps the business running the whole time.

The system below is a composite, drawn from the common shape of .NET estates rather than any one client. It is deliberately representative: if your application has some of these pieces, the sequence will feel familiar. It is also deliberately clean — real systems are messier, and the closing section is honest about that. What stays constant is the method.

The starting system

Picture a mid-sized line-of-business application that has run a company’s operations for fifteen years:

  • A .NET Framework 4.8 core, Windows-only, on aging Windows Server.
  • An ASP.NET Web Forms front end — a few dozen .aspx screens, the busiest of which carry years of pricing and eligibility logic in their code-behind.
  • A handful of WCF services: some internal service-to-service calls, one SOAP endpoint a long-standing partner integration depends on.
  • A VB.NET component handling a nightly billing calculation, written by an engineer who left years ago.
  • All of it a tangled monolith — logic spread across the UI, the services, and the VB component, with a single shared database underneath.

It is supported (Framework 4.8 still gets its Windows security fixes), but it is frozen, expensive to staff, and blocking the roadmap. The pressure to move is the slow, compounding kind from Part 1 — and the trigger is that the partner integration wants new capabilities the current stack cannot deliver.

Phase 0: Assess and set up the loop

The program starts with the assessment from Part 2. The team maps dependencies, runs the compatibility analyzers, and produces the slice inventory tagged “ports cleanly” versus “must be rebuilt.” The Web Forms screens, the WCF hosts, and the VB.NET component land in the “must be rebuilt” column; the domain libraries are mostly portable.

Two pieces of scaffolding go in before any slice moves:

  • The portable class libraries are multi-targeted (net48 + net8.0) so the same business logic compiles for both the old and new worlds.
  • A facade is placed in front of the application, routing every request to the Framework app to begin with. It will be the seam every cutover flows through.

No user-visible change yet — and that is correct. The loop is now ready.

Slice 1: Prove the loop on a contained Web Forms page

The team deliberately picks a moderate first slice — not the scariest screen, not a trivial one — to prove the loop end to end. A reporting page with real but well-understood logic.

Following Part 3: the logic is extracted out of the code-behind into a multi-targeted library and unit-tested; the page is rebuilt as a Blazor component over that library; it runs in shadow mode against the live page until outputs match, including the awkward rounding case nobody had documented; then its route cuts over behind the facade. Rollback stays one config flip away for a couple of weeks. It holds.

The first proven slice is in production in weeks. More important than the slice itself, the machinery is now proven — extraction, parity gate, facade cutover — and the team trusts it.

Slices 2–4: The high-value Web Forms screens

With the loop trusted, the team takes the screens that actually hurt — the pricing and eligibility pages where the buried logic lives. These are pure logic-recovery work (Part 3’s real lesson): the value is in finding every undocumented rule and re-homing it into tested libraries, not in redrawing the form. AI assistance drafts the conversions and candidate tests (Part 6), every output reviewed by an engineer, every slice proven at the parity gate before cutover. Each screen ships independently; the roadmap never freezes.

Slice 5: The WCF services

Now the service layer (Part 4), and the per-caller choices show up exactly as described:

  • The internal service-to-service calls are rebuilt as gRPC — fast, contract-first, ideal for the internal hot path.
  • The partner SOAP endpoint is moved to CoreWCF, keeping the exact SOAP contract so the partner’s system never knows the host changed underneath it — which is what unblocks the new capabilities they asked for.

The contracts make parity sharp: captured and shadowed requests are replayed against old and new and reconciled field by field. Callers move across a few at a time, the facade routing stragglers to the old endpoints until they catch up.

Slice 6: The VB.NET billing component

The nightly billing calculation (Part 5). Its author is gone and the rules were never written down, so the work is recovery first: the behavior is captured from the running system as a living specification, the logic extracted into a tested library and converted to C# during the rebuild (the team standardizes on one language). Because billing is money, the parity bar is strict — the new calculation runs in parallel against production data for several cycles and must reconcile to the cent before it takes over.

The architecture decision: stop at a modular monolith

By now the application is on modern .NET, but the team faces Part 7’s question: decompose further into microservices?

They apply the test honestly. The internal services that became gRPC were extracted because they had a real fault-isolation and reuse pressure — that was justified. The partner SOAP service stands alone for the same reason. But the core application and reporting have no scaling, autonomy, or isolation pressure. So the team stops. The end state is a modular monolith on modern .NET — clean internal boundaries, in-process calls, real transactions — with a few services extracted exactly where pressure justified them, and no further. Pushing past that would have risked a distributed monolith for no return.

The migration ends when the pressure is relieved, not when everything is a service.

Retiring the Framework app

With every slice proven and cut over, traffic to the Framework application drops to nothing. The facade no longer routes anything to it. The old Windows Server, the WebForms host, the WCF hosts, and the VB.NET component are decommissioned. The Framework app is retired — not in a dramatic cutover, but quietly, because it has already been emptied of responsibility one slice at a time.

What it looked like as a program

Throughout, the business never stopped. Value shipped every few weeks rather than arriving at one distant date. No slice that misbehaved became an outage — it was caught at the parity gate or rolled back in seconds. The frozen-platform tax stopped compounding: the team hires for modern .NET, the cloud bill reflects load instead of Windows-Server overhead, and the partner integration finally got its new capabilities. This is the shape the whole series argued for — incremental, parity-first, and honest about when to stop.

Where this composite is cleaner than reality

This walkthrough is deliberately clean, and a real .NET estate will be messier: a Web Forms page that hides a tangle, a WCF contract with undocumented fault behavior, a billing rule that takes three tries to reconcile. The composite is not a promise that every migration runs this smoothly; it is a demonstration of the method, which is what holds when the specifics get hard. The discipline — assess, extract, prove parity, cut over gradually, stop when the pressure is relieved — survives contact with a messy system. The tidy timeline does not.

Where this leads

That closes the series, from the platform split in Part 1 to a system retired here. The throughline under all eight parts is the same: the move off .NET Framework is a deliberate platform migration, its safety lives in its incremental shape, and the goal is the liability removed and the roadmap unblocked — not a rewrite. If you are weighing a .NET modernization of your own, a good first step is to check the support status of your runtimes and dependencies with our software end-of-life checker, then see how we run a .NET modernization. When you want a second set of eyes on where your estate actually sits, book a 30-minute discovery call or reach us at sales@modernlift.ai.

Frequently asked questions

How long does a full .NET modernization like this take?
It varies with the size of the Framework-only surface and how tangled the code is, not with line count — but the more useful framing is that the first proven slice usually ships in weeks, and value lands continuously after that rather than at one distant end date. The program runs until the pressure is relieved and the Framework app can be retired, which for a substantial system is months to a couple of years of steady, shipping progress — never a single big-bang cutover you wait years for.
Does the business keep running during the migration?
Yes — that is the entire point of the incremental method. At every moment most of the system is the running application, and only the slices that have been proven equivalent are on the new stack. Each slice cuts over behind a facade with rollback a configuration change away, so a slice that misbehaves is a contained, reversible event rather than an outage. There is no freeze and no flag day.
Do we end up with microservices?
Only where a real pressure justifies them. A well-run .NET modernization usually lands on a modular monolith on modern .NET, with a small number of services extracted for the components that genuinely needed independent scaling, team autonomy, or fault isolation. The goal is the pain removed and the platform modernized, not a maximum number of services — over-decomposing into a distributed monolith is a failure mode, not a finish line.
All 8 parts of .NET Legacy Modernization →