Skip to main content
Version: 1.2.0

gRPC Support

Fory can generate C# gRPC service companions for schemas that define services. The generated code uses normal gRPC clients, service bases, method descriptors, metadata, deadlines, cancellations, and status codes, while request and response objects are serialized with Fory instead of protobuf.

Use this mode when both RPC peers are generated from the same Fory IDL, protobuf IDL, or FlatBuffers IDL and both sides expect Fory-encoded message bodies. Use normal protobuf gRPC generation for APIs that must be consumed by generic protobuf clients, reflection tools, or components that expect protobuf message bytes.

Add Dependencies

The Apache.Fory package does not add gRPC dependencies. Add the gRPC packages in the application that compiles or runs generated service companions.

Server project:

<ItemGroup>
<PackageReference Include="Apache.Fory" Version="1.2.0" />
<PackageReference Include="Grpc.AspNetCore" Version="2.71.0" />
</ItemGroup>

Client project:

<ItemGroup>
<PackageReference Include="Apache.Fory" Version="1.2.0" />
<PackageReference Include="Grpc.Core.Api" Version="2.71.0" />
<PackageReference Include="Grpc.Net.Client" Version="2.71.0" />
</ItemGroup>

Grpc.Core.Api is the API surface used by generated companions. Server and client applications can choose their normal gRPC hosting or transport packages.

Define a Service

Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers rpc_service definitions. A Fory IDL service looks like this:

package demo.greeter;
option csharp_namespace = "Demo.Greeter";

message HelloRequest {
string name = 1;
}

message HelloReply {
string reply = 1;
}

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

Generate C# model and gRPC companion code with --grpc:

foryc service.fdl --csharp_out=./Generated --grpc

For this schema, the C# generator emits:

FilePurpose
Demo/Greeter/Service.csFory model types and schema module
Demo/Greeter/GreeterGrpc.csgRPC service base, client, and descriptors
ServiceForyModule in Service.csFory registration module for generated types
Greeter.GreeterBase in GreeterGrpc.csBase class for server implementations
Greeter.GreeterClient in GreeterGrpc.csClient stub for gRPC calls

Implement a Server

Extend the generated Greeter.GreeterBase class and map it through normal ASP.NET Core gRPC hosting:

using System.Threading.Tasks;
using Demo.Greeter;
using Grpc.Core;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();

var app = builder.Build();
app.MapGrpcService<GreeterService>();
app.Run();

public sealed class GreeterService : Greeter.GreeterBase
{
public override Task<HelloReply> SayHello(
HelloRequest request,
ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Reply = "Hello, " + request.Name,
});
}
}

Generated request and response types are registered by the generated schema module used by the service companion, so service implementations do not perform manual serializer registration.

Create a Client

Use the generated client with a Grpc.Net.Client call invoker:

using Demo.Greeter;
using Grpc.Net.Client;

using GrpcChannel channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel.CreateCallInvoker());

HelloReply reply = await client.SayHelloAsync(
new HelloRequest { Name = "Fory" });
Console.WriteLine(reply.Reply);

The generated client also exposes synchronous unary methods and the normal gRPC streaming call shapes.

Streaming RPCs

Fory service definitions can use the same gRPC streaming shapes:

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
rpc Chat (stream HelloRequest) returns (stream HelloReply);
}

Generated C# service methods follow gRPC C# conventions:

IDL shapeServer methodClient method
rpc A (Req) returns (Res)Task<Res> A(Req request, ServerCallContext context)Res A(...) and AsyncUnaryCall<Res> AAsync(...)
rpc A (Req) returns (stream Res)Task A(Req request, IServerStreamWriter<Res> responseStream, ...)AsyncServerStreamingCall<Res> A(...)
rpc A (stream Req) returns (Res)Task<Res> A(IAsyncStreamReader<Req> requestStream, ...)AsyncClientStreamingCall<Req, Res> A(...)
rpc A (stream Req) returns (stream Res)Task A(IAsyncStreamReader<Req> requestStream, IServerStreamWriter<Res> ...)AsyncDuplexStreamingCall<Req, Res> A(...)

Server implementations can use the generated streaming method shapes directly:

using System.Collections.Generic;
using System.Threading.Tasks;
using Demo.Greeter;
using Grpc.Core;

public sealed class GreeterService : Greeter.GreeterBase
{
public override async Task LotsOfReplies(
HelloRequest request,
IServerStreamWriter<HelloReply> responseStream,
ServerCallContext context)
{
foreach (string reply in new[]
{
"Hello, " + request.Name,
"Welcome, " + request.Name,
})
{
await responseStream.WriteAsync(new HelloReply { Reply = reply });
}
}

public override async Task<HelloReply> LotsOfGreetings(
IAsyncStreamReader<HelloRequest> requestStream,
ServerCallContext context)
{
List<string> names = new();
while (await requestStream.MoveNext(context.CancellationToken))
{
names.Add(requestStream.Current.Name);
}

return new HelloReply { Reply = string.Join(", ", names) };
}

public override async Task Chat(
IAsyncStreamReader<HelloRequest> requestStream,
IServerStreamWriter<HelloReply> responseStream,
ServerCallContext context)
{
while (await requestStream.MoveNext(context.CancellationToken))
{
await responseStream.WriteAsync(new HelloReply
{
Reply = "Hello, " + requestStream.Current.Name,
});
}
}
}

Generated clients return the standard gRPC streaming call objects:

using System;
using System.Threading;
using System.Threading.Tasks;
using Demo.Greeter;
using Grpc.Core;

using AsyncServerStreamingCall<HelloReply> replies =
client.LotsOfReplies(new HelloRequest { Name = "Fory" });
while (await replies.ResponseStream.MoveNext(CancellationToken.None))
{
Console.WriteLine(replies.ResponseStream.Current.Reply);
}

using AsyncClientStreamingCall<HelloRequest, HelloReply> greetings =
client.LotsOfGreetings();
await greetings.RequestStream.WriteAsync(new HelloRequest { Name = "Ada" });
await greetings.RequestStream.WriteAsync(new HelloRequest { Name = "Grace" });
await greetings.RequestStream.CompleteAsync();
HelloReply summary = await greetings.ResponseAsync;
Console.WriteLine(summary.Reply);

using AsyncDuplexStreamingCall<HelloRequest, HelloReply> chat = client.Chat();
Task readTask = Task.Run(async () =>
{
while (await chat.ResponseStream.MoveNext(CancellationToken.None))
{
Console.WriteLine(chat.ResponseStream.Current.Reply);
}
});
await chat.RequestStream.WriteAsync(new HelloRequest { Name = "Fory" });
await chat.RequestStream.CompleteAsync();
await readTask;

The generated descriptors preserve the exact IDL service and method names for the gRPC path.

Generated Module Names

C# schema module names come from the source file stem. They do not come from csharp_namespace and they do not come from gRPC service names.

For example:

Schema inputModel fileSchema module
service.fdlService.csServiceForyModule
order-events.fdlOrderEvents.csOrderEventsForyModule
greeter.fdlGreeter.csGreeterForyModule
Greeter.fdlGreeter.csGreeterForyModule

A gRPC service named Greeter still generates the service companion GreeterGrpc.cs; it does not change the schema module name. This lets several schema files target the same C# namespace without colliding. No namespace-derived or service-derived module alias is generated.

Operations

The generated service code only replaces request and response serialization. All normal gRPC operational features still belong to your gRPC stack:

  • Deadlines and cancellations
  • TLS and authentication
  • Name resolution and load balancing
  • Client and server interceptors
  • Status codes and metadata
  • Channel pooling and lifecycle management

Troubleshooting

Missing Grpc.Core Types

Add Grpc.Core.Api or a server/client package that brings it transitively. Generated Fory service files import gRPC APIs, but Apache.Fory intentionally does not depend on gRPC.

Protobuf Clients Cannot Decode the Service

Fory gRPC companions do not use protobuf wire encoding for messages. Use a Fory-generated client for Fory-generated services, or expose a separate protobuf service endpoint for generic protobuf clients.