title: gRPC Support sidebar_position: 12 id: grpc_support license: | Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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.
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-dev" /> <PackageReference Include="Grpc.AspNetCore" Version="2.71.0" /> </ItemGroup>
Client project:
<ItemGroup> <PackageReference Include="Apache.Fory" Version="1.2.0-dev" /> <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.
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 |
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.
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.
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.
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.
The generated service code only replaces request and response serialization. All normal gRPC operational features still belong to your gRPC stack:
Grpc.Core TypesAdd 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.
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.