Skip to content
← All writing
Legacy .NET27 Jun 202611 min read

Modernising a ten-year-old Web Forms app without a rewrite

120+ sites in production. No freeze window. The rewrite was never going to happen, so we did the other thing.

By Semir Hamid

Every engineer who meets a ten-year-old ASP.NET Web Forms application wants to rewrite it. I did too. The problem is that the thing monitors 120+ power plants, and the business cannot stop while you spend eighteen months rebuilding something that already works.

Strangler fig, done properly

The pattern is well known and usually done badly. People put a proxy in front, move one page, and then stop because the second page turns out to share session state with the first. The trick is to pick the seam before you pick the page.

  • Put the new service in front, not behind. Everything routes through the new edge from day one, even when it's forwarding 100% of traffic to the old app. You've now got a place to stand.
  • Move read paths before write paths. Reads are idempotent and you can run them in both systems and compare outputs without consequences.
  • Shadow-run before cutting over. Serve from the old path, call the new one too, log the differences. We caught a timezone bug this way that would have been a very bad afternoon.
  • Share the database longer than feels comfortable. Splitting the schema is the last step, not the first. It's the step that makes rollback impossible.

WCF is not the hard part

Replacing WCF with gRPC is mechanical. The hard part is that fifteen years of business logic lives in the code-behind of .aspx pages, interleaved with UI concerns, and there is no test for any of it. Before you move a line of that, write characterisation tests — not tests that assert correct behaviour, tests that assert current behaviour. Including the behaviour you're fairly sure is a bug.

You're not writing tests to prove the old code is right. You're writing them to notice when you change it.

Where it actually got faster

Not in the new services. The new services were about the same. The speed came from the things you can finally do once there's a seam: cache at the edge, batch the chatty calls, and stop a page rendering 40 controls that each fire their own query.