WCF to gRPC: Modernising .NET Service Layers with AI-Assisted Migration
WCF (Windows Communication Foundation) is not supported on .NET 10. For internal service-to-service communication, gRPC is the closest architectural replacement: contract-first, strongly typed, and built for high-performance RPC. AI-assisted tooling handles much of the structural translation, but the migration is not a simple find-and-replace.
- gRPC is the best WCF replacement for internal services. REST is better for external APIs.
- WCF service contracts translate naturally to Protocol Buffer (proto) definitions.
- WCF data contracts map to proto messages, with some type system differences.
- CoreWCF is a short-term compatibility option, not a long-term strategy.
- AI tooling handles the structural conversion well. Human attention goes to error handling, authentication, and streaming semantics.
Your WCF services are blocking your .NET 10 migration
WCF is the single most common blocker we see in .NET Framework to .NET 10 migration projects. Applications that would otherwise migrate cleanly are held back by 10, 20, sometimes 50+ WCF service contracts that have no direct .NET 10 equivalent.
Microsoft deliberately chose not to port WCF to .NET Core or .NET 10. The reasoning was that the web services landscape had moved on: REST for external APIs, gRPC for internal RPC, and message queues for async communication. WCF’s SOAP-based, enterprise service bus paradigm no longer aligned with the direction of the platform.
That leaves you with a decision. And the right answer depends on what your WCF services actually do.
Choosing the right replacement
Not all WCF services should migrate to the same target.
gRPC for internal service-to-service communication. If your WCF services handle communication between your own systems (microservice calls, backend coordination, data synchronisation), gRPC is the natural fit. It is contract-first (like WCF), strongly typed (like WCF), and designed for high-performance RPC.
REST minimal APIs for external-facing services. If your WCF services expose SOAP endpoints that external clients consume, migrate to REST. External clients expect REST. They do not expect to install a gRPC client or handle Protocol Buffers.
Azure Service Bus for async operations. If your WCF services use one-way operations or duplex channels for asynchronous event-driven patterns, Azure Service Bus (or a similar message broker) may be a better replacement than any RPC framework.
CoreWCF for buying time. If you need to get to .NET 10 quickly and WCF migration is the bottleneck, CoreWCF lets you keep WCF-style services running on .NET 10 with minimal code changes. This is a tactical decision, not a strategic one. Plan to migrate off CoreWCF within 12-18 months.
The rest of this guide focuses on the gRPC path, which is the most common and most architecturally sound option for internal services.
How WCF concepts map to gRPC
WCF and gRPC share more conceptual DNA than most developers expect. Both are contract-first, both use code generation, and both support streaming. The vocabulary differs, but the mental model translates.
| WCF Concept | gRPC Equivalent |
|---|---|
Service contract ([ServiceContract]) | Service definition in .proto file |
Operation contract ([OperationContract]) | RPC method in service definition |
Data contract ([DataContract]) | Message in .proto file |
Data member ([DataMember]) | Field in proto message |
| basicHttpBinding | Not applicable (gRPC uses HTTP/2) |
| netTcpBinding | Default gRPC transport (HTTP/2) |
| Duplex channel | Bidirectional streaming |
| One-way operation | Client or server streaming |
| Fault contract | gRPC status codes and error details |
| Service host | ASP.NET Core Kestrel with gRPC |
Migrating a service contract
Here is how a WCF service contract translates to gRPC, step by step.
WCF service and data contracts
[ServiceContract]
public interface IOrderService
{
[OperationContract]
OrderResponse GetOrder(int orderId);
[OperationContract]
List<OrderSummary> GetOrdersByCustomer(int customerId);
[OperationContract]
CreateOrderResponse CreateOrder(CreateOrderRequest request);
}
[DataContract]
public class OrderResponse
{
[DataMember] public int Id { get; set; }
[DataMember] public string CustomerName { get; set; }
[DataMember] public DateTime OrderDate { get; set; }
[DataMember] public decimal Total { get; set; }
[DataMember] public List<OrderLineDto> Lines { get; set; }
}
[DataContract]
public class OrderLineDto
{
[DataMember] public string ProductName { get; set; }
[DataMember] public int Quantity { get; set; }
[DataMember] public decimal UnitPrice { get; set; }
}
gRPC proto definition (generated by Claude Code)
syntax = "proto3";
package orders;
import "google/protobuf/timestamp.proto";
service OrderService {
rpc GetOrder (GetOrderRequest) returns (OrderResponse);
rpc GetOrdersByCustomer (GetOrdersByCustomerRequest)
returns (OrderListResponse);
rpc CreateOrder (CreateOrderRequest) returns (CreateOrderResponse);
}
message GetOrderRequest {
int32 order_id = 1;
}
message GetOrdersByCustomerRequest {
int32 customer_id = 1;
}
message OrderResponse {
int32 id = 1;
string customer_name = 2;
google.protobuf.Timestamp order_date = 3;
DecimalValue total = 4;
repeated OrderLine lines = 5;
}
message OrderLine {
string product_name = 1;
int32 quantity = 2;
DecimalValue unit_price = 3;
}
message DecimalValue {
int64 units = 1;
int32 nanos = 2;
}
message OrderListResponse {
repeated OrderResponse orders = 1;
}
message CreateOrderRequest {
int32 customer_id = 1;
repeated CreateOrderLine lines = 2;
}
message CreateOrderLine {
string product_id = 1;
int32 quantity = 2;
}
message CreateOrderResponse {
int32 order_id = 1;
bool success = 2;
string message = 3;
}
gRPC service implementation
public class OrderGrpcService(
IOrderRepository orderRepository,
ILogger<OrderGrpcService> logger)
: OrderService.OrderServiceBase
{
public override async Task<OrderResponse> GetOrder(
GetOrderRequest request,
ServerCallContext context)
{
var order = await orderRepository.GetByIdAsync(
request.OrderId,
context.CancellationToken);
if (order is null)
{
throw new RpcException(new Status(
StatusCode.NotFound,
$"Order {request.OrderId} not found"));
}
return MapToResponse(order);
}
public override async Task<OrderListResponse> GetOrdersByCustomer(
GetOrdersByCustomerRequest request,
ServerCallContext context)
{
var orders = await orderRepository.GetByCustomerAsync(
request.CustomerId,
context.CancellationToken);
var response = new OrderListResponse();
response.Orders.AddRange(orders.Select(MapToResponse));
return response;
}
}
Claude Code generates both the .proto definitions and the C# service implementations from WCF contracts. The structural mapping is well-defined, so AI accuracy is high for this category of work. The human review focuses on three areas:
Where human attention is needed
Type system differences
Protocol Buffers have a different type system from C#. Key gotchas:
- No
decimaltype. Proto usesint64/int32pairs or customDecimalValuemessages. This affects financial calculations. Verify that the serialisation preserves precision. - No
nulldistinction for scalar types. Proto3 scalar fields cannot be null; they have default values (0, "", false). Usegoogle.protobuf.StringValue(wrapper types) if you need to distinguish “empty” from “not set.” - Timestamp representation. C#
DateTimemaps togoogle.protobuf.Timestamp, which is UTC-only. If your WCF services pass local times, the conversion may change semantics.
Error handling patterns
WCF uses fault contracts for structured errors. gRPC uses status codes and error details.
// WCF fault handling
try { /* ... */ }
catch (FaultException<ValidationFault> ex)
{
// Handle structured validation fault
}
// gRPC error handling
try { /* ... */ }
catch (RpcException ex) when (ex.StatusCode == StatusCode.InvalidArgument)
{
// Handle validation error
var details = ex.Trailers.GetValue("validation-errors");
}
The error handling migration needs human design because the patterns are architecturally different. Decide on your gRPC error handling convention before migrating, and document it in CLAUDE.md.
Authentication and authorisation
WCF supports multiple authentication mechanisms (Windows authentication, certificate authentication, custom tokens) configured through bindings. gRPC on ASP.NET Core uses the standard ASP.NET Core authentication middleware.
If your WCF services use Windows authentication (common in intranet scenarios), the gRPC migration needs to establish an alternative authentication mechanism: JWT bearer tokens, mutual TLS, or API keys, depending on your security requirements.
Streaming semantics
WCF duplex channels and gRPC bidirectional streaming are conceptually similar but architecturally different. WCF duplex channels maintain a persistent connection with callback contracts. gRPC streams are request-scoped.
If your WCF services use duplex channels for real-time notifications, evaluate whether gRPC server streaming, bidirectional streaming, or a separate technology (SignalR, Azure Service Bus) is the better replacement.
The migration sequence
For a .NET application with multiple WCF services, we recommend this sequence:
- Catalogue all WCF services. List every service contract, its endpoints, its consumers, and whether it is internal or external.
- Categorise each service. Internal RPC (gRPC target), external API (REST target), async operations (Service Bus target), or compatibility (CoreWCF for now).
- Migrate one service end-to-end as a proof of concept. Choose a simple, well-tested service. Migrate it, deploy it alongside the WCF version, and route one consumer to the gRPC endpoint.
- Roll out service by service. Using the strangler fig pattern, migrate each service and switch consumers incrementally.
This approach avoids a big-bang WCF removal. The WCF and gRPC services can coexist during migration. Each consumer switches when the gRPC service for its dependency is ready.
What we have seen in practice
[CLIENT EXAMPLE: Financial services client, 28 WCF service contracts (mix of basicHttpBinding and netTcpBinding) across 8 .NET Framework services. Migrated internal services to gRPC and external services to REST minimal APIs. Claude Code generated the .proto definitions from WCF contracts with approximately 70% accuracy (manual corrections needed for decimal handling and nullable types). Total migration: 6 weeks as part of a broader .NET 10 migration. The gRPC services showed measurably lower latency than the WCF equivalents for high-frequency internal calls.]
[CLIENT EXAMPLE: Education technology company, 12 WCF services running on Azure Service Fabric. Migrated to gRPC on Azure Container Apps. The Service Fabric dependency added complexity beyond the WCF migration itself. CoreWCF was used as an interim step for 3 services that were consumed by a third-party system not yet ready to switch to gRPC. Those 3 services were migrated to gRPC six months later when the third party updated their integration.]
Getting started
If WCF services are blocking your .NET 10 migration, start by cataloguing what you have. The number and complexity of WCF service contracts is the primary driver of migration effort.
For the broader migration context, see our .NET modernisation playbook and the step-by-step .NET Framework to .NET 10 migration guide.
For IP considerations around AI-generated code, see Who Owns AI-Written Code?.
For the general decision framework on modernisation strategy, see Modernise, Rebuild, or Replace.
Book a free Legacy .NET Assessment consultation to get a tailored migration plan for your WCF services.