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:
| File | Purpose |
|---|---|
Demo/Greeter/Service.cs | Fory model types and schema module |
Demo/Greeter/GreeterGrpc.cs | gRPC service base, client, and descriptors |
ServiceForyModule in Service.cs | Fory registration module for generated types |
Greeter.GreeterBase in GreeterGrpc.cs | Base class for server implementations |
Greeter.GreeterClient in GreeterGrpc.cs | Client 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 shape | Server method | Client 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 input | Model file | Schema module |
|---|---|---|
service.fdl | Service.cs | ServiceForyModule |
order-events.fdl | OrderEvents.cs | OrderEventsForyModule |
greeter.fdl | Greeter.cs | GreeterForyModule |
Greeter.fdl | Greeter.cs | GreeterForyModule |
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.