Migrating WCF and ASMX Services to Modern .NET
Server-side WCF and the older ASMX web services are Framework-only and were not carried into modern .NET, so service hosts must be replaced rather than ported. The three targets are gRPC (Microsoft's recommended high-performance contract-based replacement), REST/HTTP APIs (the broadest interoperability), and CoreWCF (a supported port of WCF server-side that exists specifically to ease migration of existing SOAP contracts). WCF client libraries do run on modern .NET, so callers can often stay put while the service moves. The work is done one service contract at a time behind a facade, with the new endpoint proven equivalent before callers are switched to it.
Part 3 handled the UI half of the “must be rebuilt” column. This part handles the service half: the WCF and ASMX endpoints that wire .NET systems to each other, to internal clients, and to the outside world. Like Web Forms, these are Framework-only technologies with no straight port to modern .NET — and like Web Forms, the right move is to replace them deliberately rather than to dread them.
The good news is that the service layer is, in one important respect, easier than the UI layer: a service has a contract, and a contract is a much clearer specification of correct behavior than a Web Forms page ever offers. That contract is the lever the whole migration turns on.
What did and did not come across
Precision matters here, because the common shorthand “WCF is dead” is wrong in a way that changes the plan.
- Server-side WCF hosting was not ported to .NET Core or .NET 5+. The full WCF server stack —
ServiceHost, the binding-and-behavior configuration model, the WAS/IIS hosting integration — does not exist on modern .NET. Services hosted that way have to move. - WCF client libraries do run on modern .NET. The
System.ServiceModel.*client packages are available, so code that calls a WCF or SOAP service can run on modern .NET without change. This is more useful than it sounds: it means a caller you are migrating can keep talking to a service you have not migrated yet, and vice versa. - CoreWCF is a community-driven project — now backed and supported by Microsoft — that ports the WCF server side onto modern .NET. It exists precisely to ease migration of existing SOAP contracts you cannot easily change. It reached a stable 1.0 release, and it is the bridge when “keep the SOAP contract, move the host” is the requirement.
- ASMX (the older
.asmxASP.NET Web Services) is likewise unsupported on modern .NET and is treated the same way as WCF — rebuilt on whichever target its callers need.
So the question is never “how do we keep WCF?” It is “what does each service’s contract need to become?”
Choosing the replacement, per service
There is no single answer for the whole estate; you choose per service based on who calls it and what they need:
- gRPC is Microsoft’s recommended successor for new service work and the natural target for internal, service-to-service calls. It is contract-first (you define the service in a
.proto), strongly typed, binary, and fast. If a WCF service exists to let one part of your system call another efficiently, gRPC is usually the home for it. - REST / HTTP APIs give the broadest interoperability. When the callers are browsers, third parties, mobile apps, or tooling that speaks plain HTTP and JSON, a REST endpoint (ASP.NET Core minimal APIs or controllers) is the right target. You trade gRPC’s efficiency for universal reach.
- CoreWCF is the answer when you have SOAP clients you cannot change — a partner integration, an old internal app, a contract written into someone else’s system. CoreWCF lets you keep the exact SOAP contract while moving the host onto modern .NET, so the callers never know the platform changed underneath them.
A realistic estate ends up with a mix: gRPC between your own services, REST for external and browser-facing endpoints, and CoreWCF wherever a SOAP contract has to survive untouched.
One more decision hides inside this: WCF’s hosting and transport features do not all have one-to-one replacements. Net.TCP bindings, message-level security, reliable sessions, transactions flowed across the wire, and duplex callbacks were WCF conveniences that gRPC and REST express differently or not at all. Most systems used a small, plain subset of WCF and migrate cleanly; the ones that leaned on the exotic bindings need those capabilities re-examined rather than assumed. Surfacing that early — during the Part 2 assessment — keeps a “simple WCF migration” from discovering a duplex-callback dependency halfway through. Where such a feature is genuinely needed and only WCF expressed it, CoreWCF is again the bridge, because it preserves the binding model rather than asking you to re-engineer around it.
Migrating a service without breaking its callers
The contract is what makes this safer than the UI work, because it lets old and new coexist cleanly. The loop, per service, behind the Part 2 facade:
- Pin the contract. Capture exactly what the existing service accepts and returns, including the awkward cases — fault behavior, nullability, date and number formats, ordering. The contract plus a corpus of real request/response pairs is your specification.
- Rebuild the endpoint on the chosen target (gRPC, REST, or CoreWCF), backed by the same business logic — ideally the multi-targeted library from Part 2 rather than a re-implementation.
- Put an anti-corruption layer at the seam. If the old contract is awkward and you want the new internal model to be clean, translate between them at the boundary so the new service is not shaped by the old quirks. This is the anti-corruption layer pattern, and the service boundary is its ideal home.
- Prove equivalence by replaying traffic. Send the captured (and live, shadowed) requests to both the old service and the new endpoint and compare the responses field by field — reconciliation and output comparison. Because a service contract is precise, this comparison is sharper and more automatable than parity-checking a UI.
- Switch callers gradually. Move callers to the new endpoint a few at a time, not all at once. Because WCF client libraries run on modern .NET, callers can be migrated independently of the service, and the facade can route old callers to the old endpoint until they catch up.
A service’s contract, which felt like the rigid constraint, is the thing that lets you replace the service safely — it is both the specification and the test oracle.
This is also why the service layer is usually a good early target in a broader .NET migration, even though it sits behind the UI. A service has a narrow, well-defined surface and a precise contract, so a slice here is easier to scope, easier to prove, and lower-risk than a logic-heavy Web Forms page. Migrating a couple of internal services first lets the team exercise the whole loop — capture, rebuild, shadow, reconcile, cut over — on the part of the system that gives the clearest pass/fail signal, building confidence in the machinery before tackling the murkier UI slices. Sequence by clarity, not just by visibility: the most visible thing to a user is rarely the safest thing to migrate first.
A note on the easy mistake
The tempting shortcut is to rebuild every WCF service as REST because REST is familiar. That can quietly degrade an internal high-throughput path that was fine as a fast binary call, and it can break SOAP clients that needed the exact envelope. Choose the target from the caller’s needs, not from fashion. An internal service feeding another service does not become better by speaking JSON over HTTP; it becomes slower.
Not every service earns the full loop
Not every service is worth a careful, parity-gated migration. A rarely-used internal endpoint with two callers you control can simply be rebuilt and the callers updated in the same change — the heavy replay-and-reconcile loop would be overkill. Reserve the full method for the services with many callers, external consumers, or strict contracts, where getting a field wrong is an incident. As always, match the ceremony to the blast radius.
Where this leads
Web Forms and WCF/ASMX are the two big Framework-only technologies, but plenty of .NET estates carry one more layer of history beneath them: code written in Visual Basic — VB6 components still in the mix, or VB.NET on Framework. Part 5, VB6 and Legacy VB.NET Modernization, covers what is true (and what is myth) about Visual Basic’s support story, and how to bring that code onto modern .NET.
Frequently asked questions
- Is WCF supported on .NET Core / .NET 8?
- Server-side WCF hosting is not part of modern .NET — Microsoft did not port the full WCF server stack to .NET Core or .NET 5+. The client-side libraries (System.ServiceModel.*) are available on modern .NET, so code that calls WCF services can run there. For existing server-side SOAP contracts you want to keep, the community-driven, now Microsoft-supported CoreWCF project provides a compatible host on modern .NET. For new service work, Microsoft recommends gRPC.
- Should we replace WCF with gRPC or REST?
- Use gRPC when you want a high-performance, strongly-typed, contract-first replacement for internal service-to-service calls — it is Microsoft's recommended successor to WCF for that case. Use REST/HTTP when you need the broadest interoperability with browsers, third parties, and tooling that speaks plain HTTP and JSON. Use CoreWCF when you have existing SOAP clients you cannot easily change and you need to keep the SOAP contract while moving the host onto modern .NET. Many estates use a mix, chosen per service.
- What about ASMX web services?
- ASMX (ASP.NET Web Services, the .asmx SOAP endpoints) is even older than WCF and is also unsupported on modern .NET. It is treated the same way: the endpoint is rebuilt as gRPC, REST, or a CoreWCF SOAP service depending on what its callers need. Because ASMX contracts are usually simple SOAP, they often map cleanly to a CoreWCF service when callers must keep talking SOAP, or to a REST endpoint when the callers can be updated.