This is an automated email from the ASF dual-hosted git repository. chaokunyang pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/fory-site.git
commit b35eb606f8f9e623439ad48074ac4692d2704e2d Author: chaokunyang <[email protected]> AuthorDate: Sat Jun 13 07:36:07 2026 +0000 🔄 synced local 'docs/guide/' with remote 'docs/guide/' --- docs/guide/csharp/grpc-support.md | 311 +++++++++++++++++++++++++++++++++++ docs/guide/csharp/index.md | 1 + docs/guide/csharp/troubleshooting.md | 22 ++- 3 files changed, 333 insertions(+), 1 deletion(-) diff --git a/docs/guide/csharp/grpc-support.md b/docs/guide/csharp/grpc-support.md new file mode 100644 index 0000000000..138b87b45a --- /dev/null +++ b/docs/guide/csharp/grpc-support.md @@ -0,0 +1,311 @@ +--- +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 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--- + +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: + +```xml +<ItemGroup> + <PackageReference Include="Apache.Fory" Version="1.2.0-dev" /> + <PackageReference Include="Grpc.AspNetCore" Version="2.71.0" /> +</ItemGroup> +``` + +Client project: + +```xml +<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. + +## Define a Service + +Service definitions can come from Fory IDL, protobuf IDL, or FlatBuffers +`rpc_service` definitions. A Fory IDL service looks like this: + +```protobuf +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`: + +```bash +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: + +```csharp +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: + +```csharp +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: + +```protobuf +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: + +```csharp +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: + +```csharp +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. diff --git a/docs/guide/csharp/index.md b/docs/guide/csharp/index.md index 24b33e2819..fee0e7fa9d 100644 --- a/docs/guide/csharp/index.md +++ b/docs/guide/csharp/index.md @@ -96,6 +96,7 @@ User decoded = fory.Deserialize<User>(payload); | [Schema Evolution](schema-evolution.md) | Compatible mode behavior | | [Supported Types](supported-types.md) | Built-in and generated type support | | [Thread Safety](thread-safety.md) | `Fory` vs `ThreadSafeFory` usage | +| [gRPC Support](grpc-support.md) | Generated Fory-backed gRPC service companions | | [Troubleshooting](troubleshooting.md) | Common errors and debugging steps | ## Related Resources diff --git a/docs/guide/csharp/troubleshooting.md b/docs/guide/csharp/troubleshooting.md index 8ac2a414ee..e5d6bb6bd6 100644 --- a/docs/guide/csharp/troubleshooting.md +++ b/docs/guide/csharp/troubleshooting.md @@ -1,6 +1,6 @@ --- title: Troubleshooting -sidebar_position: 12 +sidebar_position: 13 id: troubleshooting license: | Licensed to the Apache Software Foundation (ASF) under one or more @@ -84,6 +84,25 @@ Fory fory = Fory.Builder().TrackRef(true).Build(); **Fix**: Use `BuildThreadSafe()`. +## Generated gRPC Compile Errors + +**Symptom**: Generated `*Grpc.cs` files cannot find `Grpc.Core` types. + +**Cause**: gRPC packages are application dependencies. The `Apache.Fory` +package does not add gRPC as a hard dependency. + +**Fix**: Add `Grpc.Core.Api` and your chosen gRPC server or client package, such +as `Grpc.AspNetCore` for server hosting or `Grpc.Net.Client` for clients. See +[gRPC Support](grpc-support.md). + +## Protobuf Client Cannot Decode a Fory gRPC Service + +**Cause**: Fory gRPC companions use gRPC transports with Fory-encoded message +bodies. They do not send protobuf message bytes. + +**Fix**: Use a Fory-generated client and server for the Fory endpoint, or expose +a separate protobuf endpoint for generic protobuf clients. + ## Validation Commands Run C# tests from repo root: @@ -96,5 +115,6 @@ dotnet test Fory.sln -c Release ## Related Topics - [Configuration](configuration.md) +- [gRPC Support](grpc-support.md) - [Schema Evolution](schema-evolution.md) - [Thread Safety](thread-safety.md) --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
