Modernising legacy .NET: WCF, AppDomains and the AI bridge
What really breaks when moving from .NET Framework to modern .NET: BinaryFormatter, AppDomains, WCF, YARP, Strangler Fig and the AI Bridge pattern.
Introduction: the myth of modernisation
Every experienced .NET architect has seen this movie: a company decides it is time to “move to the cloud” and “adopt AI”. The PowerPoint slides are beautiful: a future with 100% test coverage, perfectly separated microservices on Azure Kubernetes Service (AKS) and LLM-based agents flawlessly handling customer requests.
Reality arrives six months later. The team is stuck on a dependency to a 15-year-old proprietary DLL that only runs on Windows Server 2012 R2. The “simple” migration turns into a recursive nightmare of fixing broken WCF contracts and hunting for a replacement for System.Drawing. 80% of the budget is spent, but not a single product package has made it to the cloud.
Thesis: most failures in the modernisation of legacy .NET systems are caused not by choosing the wrong target architecture, but by underestimating the operational cost of the transition and overestimating how many problems the new architecture solves without any change in the team and the processes.
Modernising a legacy system on .NET is not just bumping the NuGet packages from .NET Framework to .NET 8. It is a fight against the “gravity” of a system that was built on the assumption of local data storage, permanent Windows processes and a monolithic data model. If you do not account for the loss of the unique knowledge of the team (tribal knowledge), the shift in operational monitoring and the inertia of the established practices, your modernisation will be just an expensive exercise in moving the technical debt around.
2. “Windows gravity” and the hidden friction in legacy .NET
Legacy .NET is especially hard because it often was “the enterprise platform”. It did not just run code, it integrated with everything the Windows OS could offer. When you try to peel those layers off to reach a cross-platform state, you discover that the glue is often stronger than the components themselves.
The problem of the proprietary and obsolete runtimes
It is not only about the .cs files. Many systems depend on:
- BinaryFormatter and the security risks: for decades
BinaryFormatterwas the standard way to pass state in distributed .NET systems. Modern .NET has officially declared it unsafe (deprecated). Replacing it in a large codebase is not a “find and replace” job — it requires rethinking how the data is serialized and passed, often across system boundaries you did not even know existed. - AppDomains: old .NET code used
AppDomainfor isolation. .NET Core and its successors do not support secondary AppDomains. If your system relies on dynamic assembly loading for plugins or for isolation, you will have to redesign that whole layer into a containerized or service model. - Global.asax and HttpModules: the old ASP.NET pipeline was heavy, but a powerful tool. Modern middleware in ASP.NET Core is much lighter and faster, but it is fundamentally different. Translating a complex authentication flow embedded in an
IHttpModuleinto modern middleware often uncovers business logic that was never documented.
The infrastructure anchors
- WCF (Windows Communication Foundation): WCF was an incredible feat of engineering, letting you use complex security protocols, transactions over SOAP and communication over named pipes. For some architects the move to REST or gRPC feels like losing a limb. CoreWCF helps, but the cost of rewriting the message contracts and retesting every consumer is usually the biggest time sink of the migration.
- Windows Services and the GAC: the Global Assembly Cache (GAC) was created to fight “DLL Hell”, but during a modernisation it turns into a “knowledge cemetery”. The assemblies registered in the GAC are not in your version control. Finding the source code for a 2011 dependency that exists only in the GAC of the server is a sort of rite of passage for modernisation consultants.
Dealing with the “uncontainerisable”
Some systems are hard to package even into Windows containers.
- Hardware binding: if your application checks for the presence of a physical USB key or a specific MAC address of the network card, the migration will require a “virtual key” service or a rework of the licensing module.
- UI dependencies: if your legacy backend actually launches hidden desktop windows to produce reports through COM interop with Excel, running it in a headless Windows container will most likely end in an error or require an extremely specific session configuration.
3. Technical debt: beyond the code
We often talk about technical debt as “bad code”. In legacy .NET, technical debt is often “structural obsolescence”.
The “it works on my machine” deployment model
Legacy systems often depend on manual IIS configuration. The deployment process includes RDP-ing into the server and changing the connection string in web.config. Turning that into an automated infrastructure-as-code (IaC) pipeline is painful, because the application was never designed to be environment-independent. In the cloud there is no “Server”; all the “State” has to be external.
The database as the “brain”
In many legacy systems the business logic lives not in C# but in stored procedures. You can have T-SQL scripts of 5000 lines that handle state logic, reporting and validation. Modernising the “application” while keeping the database as it is often brings no performance gain, because the bottleneck was always the lock contention in SQL Server.
4. Four approaches to modernisation and an honest breakdown of the trade-offs
I split the modernisation into four “paths of resistance”. Each has its place, and often all four are used within a single project for different subsystems.
1. Rehost (the safe lift-and-shift)
Strategy: move the virtual machines “as is” into Azure VM or Azure VMware Solution.
- Honest trade-off: you get zero technical agility. Your “cloud project” is just somebody else’s data center. You still have to patch Windows, the scaling problems remain, and the license cost can even grow.
- When to use: when there is a hard deadline for closing the data center. This buys you time, not quality.
2. Replatform (light containerization)
Strategy: move the application to Azure App Service or Windows containers without a rewrite.
- Honest trade-off: Windows containers are an awkward intermediate option. They are huge (multi-gigabyte images), slow to start and more expensive than the Linux equivalents. But they let you standardize the CI/CD pipeline.
- When to use: when you need environment identity (Dev=Prod) but have no budget to fix the code itself.
3. Refactor (the Strangler Fig approach)
Strategy: keep the monolith while surgically extracting the critical paths into services on .NET 8 under Linux.
- Honest trade-off: this is the highest operational complexity. Now you operate a hybrid system. You need distributed tracing across the old and the new code, token management between different frameworks and data synchronization.
- When to use: for mission-critical systems where uptime is the priority. It is the “Ship of Theseus” approach — a gradual replacement of each plank until the whole ship is new.
4. Rearchitect (the cloud-native transformation)
Strategy: a full decomposition into microservices or serverless.
- Honest trade-off: this is where the most money gets wasted. Many teams build a “distributed monolith”, where the services are tightly coupled but now have network latency between them.
- When to use: only if a specific part of the system has outgrown the capabilities of the monolith, or when the domain logic has become so tangled that no team can understand it anymore.
5. The roadmap: migrating to the cloud without stopping production
How do you move a system that processes millions per hour without a “big bang” switchover? You do not switch — you “seep”.
The gateway pattern (YARP is your best friend)
YARP (Yet Another Reverse Proxy) is a Microsoft project that provides a customizable proxy on top of .NET.
- The entry point: deploy YARP in Azure.
- The DNS shift: point all the traffic to YARP.
- The legacy fallback: to begin with, YARP routes 100% of the traffic back to the on-premise monolith over a VPN or ExpressRoute.
- The incremental release: when the new service is ready in the cloud, you update the YARP config to route the specific paths (for example
/api/payments) to the new cloud service.
This allows you to do Blue-Green testing at the URL level. If the new payment service fails, you roll back the YARP rule in seconds, not in hours.
The data dilemma: shared or separated
The biggest mistake is splitting the database too early. If you move the code to the cloud but leave the database on-prem, the network latency will kill the performance. Strategy: move the database to Azure SQL Managed Instance first. It supports cross-database queries and SQL Agent jobs, which makes it the ideal bridge. Once the data is in the cloud, moving the code becomes a question of compute rather than of the laws of data physics.
The roadmap of a migration without stopping production is primarily about the order of the cutoffs: what deploys independently goes first, and only then do you change what is tied to the rest. Plan not “to move the system to Azure”, but “to switch off one legacy component in it within a sprint” — such a plan has a measurable completion point.
6. Moving from WCF to modern services: a deep dive
WCF is the “final boss” of .NET modernisation. Because it was extremely flexible in configuration, there are no two identical WCF implementations.
The protocol mapping
- Named pipes: replace with gRPC calls inside a Kubernetes cluster or a container group.
- Net.TCP: replace with gRPC. It provides the same high performance of binary serialization, but it is the cross-platform industrial standard.
- BasicHttp / WS-Http: replace with REST APIs on top of ASP.NET Core.
Working with the security contexts
Old WCF services often used Windows authentication (NTLM/Kerberos). In the cloud that does not exist. You will have to move to OAuth2 / OpenID Connect (through Azure Entra ID). The difficulty is not the new code but building an “identity bridge”. You may need a temporary token exchange service that turns the old session cookie into a JWT the new .NET 8 services understand.
7. The AI Bridge: bringing intelligence into a legacy core
A mistake many companies make is waiting for the cloud migration to fully complete before adopting AI. In reality the AI can become a catalyst for the modernisation.
The AI Gateway pattern
Instead of forcing the old .NET 4.8 monolith to talk to OpenAI (which is painful because of the old TLS versions and the missing SDK), you build an AI Gateway on .NET 8.
- The legacy application sends a message to a queue (Azure Service Bus).
- The .NET 8 AI Gateway processes the message, calls the LLM and updates the database.
- The legacy application polls the database for the result.
Semantic Kernel as a “context bridge”
Imagine a user asks the AI: “Summarize the last 10 interactions with ACME Corp”. The AI agent does not talk to the legacy application directly. It uses Semantic Kernel plugins.
- Plugin A: calls the modern .NET 8 API to get the basic information about the customer.
- Plugin B: calls the old SQL stored procedure to get the transaction logs.
- Plugin C: uses the LLM to synthesize the data. You have just “modernised” the user experience without changing a single line of the old ASP.NET WebForms code.
8. The trap of the full rewrite: why the “blank page” is a dangerous illusion
Every developer who sees a 4000-line controller file wants to delete it and start over. “It will be faster that way”, they say. They are wrong.
The loss of institutional knowledge
Legacy code is not just code; it is a museum of every mistake the company ever made, and of its fix. When you rewrite everything from scratch, you lose those lessons.
The Ship of Theseus approach
Instead of a rewrite, focus on continuous refactoring:
- Replace the old logging library with Serilog/OpenTelemetry.
- Extract one business service into a separate library. By the end of the year you will have moved 60% of the code to .NET 8 without ever stopping the development of new features.
9. Testing legacy: building the safety net
The reason most modernisation projects fail is fear. Developers are afraid to touch the code because there are no tests for it.
“Golden baseline” testing (snapshot testing)
If you cannot write unit tests for a ten-year-old method, use snapshot testing.
- Record the exact inputs and outputs of the function for a thousand real scenarios.
- Save them as the “golden baseline”.
- Run your modernised version of the function.
- If the results do not match at 100%, you have a regression. This creates a “statistical safety net” that lets you refactor confidently, even when the business logic is impenetrable.
10. Operational readiness: the real price of the new stack
Moving from a single IIS server to a containerized environment requires a huge shift in the way you work.
Observability
In the cloud you need:
- Distributed tracing: use OpenTelemetry to follow a request across all the services.
- Metrics: not only CPU/RAM but also “business health”. Are the orders going through? Does the cost of the AI tokens match the value of the transaction?
Security: implementing Zero Trust
“The perimeter” is dead. You have to implement a Zero Trust architecture:
- Managed identities: use the Azure managed identities instead of passwords in the connection strings.
- Key vaults: the secrets management has to be centralized.
11. Management and team topology during the transition
The technical migration will fail if the team structure does not change with it (Conway’s Law).
Platform Team vs Feature Team
Do not ask the product developers to learn Kubernetes. Create a Platform Team whose job is to build the “golden path” (templates, CI/CD, dashboards), so that everyone else can just write C# code.
The migration guild
Bring together the “elders” who know the legacy and the “enthusiasts” of the new technologies. If they do not talk to each other, the elders will block the migration out of fear and the enthusiasts will break the system out of ignorance.
12. The price of doing nothing: a C-level view
“Legacy” is often perceived as a technical problem. In reality it is a business risk.
- Hiring problems: it is getting harder and harder to find talented developers who are willing to work on .NET Framework 4.5.
- Vulnerabilities: as the runtime ages, it stops getting security patches. A single breach can cost more than the whole modernisation budget.
- The agility gap: while your competitors deploy AI features 10 times a day, you deploy once a month and fight regression bugs.
13. Artifact: the modernisation decision tree
Use this guide to determine the path of each subsystem.
| Question | If YES | If NO |
|---|---|---|
| 1. Is there a risk of the hardware breaking? | Rehost on an Azure VM right away. | Go to question 2. |
| 2. Are there COM+ libraries or proprietary EXEs? | Replatform via Windows containers. | Go to question 3. |
| 3. Is the code covered by tests (>40%)? | Refactor straight onto .NET 8. | Go to question 4. |
| 4. Is the business ready to slow the features down by 30%? | Refactor through the Strangler. | Replatform and wait. |
| 5. Is AI integration the top priority? | Build an AI Bridge next to the monolith. | Standard migration. |
14. FAQ
Question: is .NET Framework 4.8 “dead”?
Answer: no, it is supported as long as Windows is supported. But it is a dead end for innovation.
Question: how do you migrate SQL Server databases?
Answer: Azure SQL Managed Instance is the best path to compatibility.
Question: will Microsoft’s .NET Upgrade Assistant help?
Answer: yes, for the “first pass”. It saves time, but it does not solve the architectural problems.
Question: can SQL Server stay on premises?
Answer: it can, but prepare for a loss of performance because of the network latency.
Related Articles
- .NET and Azure for AI systems: a reference architecture
- RAG on .NET: an architecture that survives production
- Enterprise AI architecture: four decisions that decide everything
Ready to break the “endless rewrite” cycle and start delivering results?
Book a strategic modernisation audit
Contextum: the document pipeline to Markdown for AI agents
AnetLab Contextum: a five-stage pipeline between an agent and an LLM — Markdown conversion, context compression, idempotent delivery and an honest loss registry.
RAG evaluation on .NET: Recall@5, MRR and the release gate
How to measure RAG quality on .NET: a Golden Set of 50–100 questions, computing Recall@5 and MRR, measuring faithfulness and eval-as-a-release-gate in CI.