Overview
A Protobuf service specifies an abstraction—a group of abstract RPC methods. A client application calls these methods while a server application hosts an implementation of this service and all its methods.
For example:
syntax = "proto3";
package visitor_center;option csharp_namespace = "VisitorCenter";
// A simple Greeter service.service Greeter { // Creates a personalized greeting. rpc Greet (GreetRequest) returns (GreetResponse);}
// The request contains the name of the person to greet.message GreetRequest { string name = 1;}
// The response contains the greeting.message GreetResponse { string greeting = 1;}
All RPC methods are abstract at the Protobuf level: you can't implement a method in the Protobuf language.
In order to implement a Protobuf service, you need an RPC framework that provides support for Protobuf. Here, we describe IceRPC's support for Protobuf.
C# mapping
The protoc-gen-icerpc-csharp
code generator generates code only for Protobuf services.
For Protobuf service Name, it creates two C# interfaces (IName and INameService) and one C# struct (NameClient). The identifiers of the generated interfaces and struct are always in Pascal case, per the usual C# naming conventions, even when Name is not in Pascal case.
IName
Interface IName provides the client-side API that allows you to call the remote IceRPC service that implements the the associated Protobuf service. It's a minimal interface with an abstract method for each RPC method defined in this Protobuf service.
For example:
syntax = "proto3";package example;option csharp_namespace = "Example";
service Widget { rpc Spin (SpinConfig) returns (google.protobuf.Empty)}
namespace Example;
public partial interface IWidget{ // One C# method per rpc method Task<WellKnownTypes.Empty> SpinAsync( SpinConfig message, IFeatureCollection? features = null, CancellationToken cancellationToken = default);}
NameClient
The generated record struct NameClient implements IName by sending requests to a remote service with IceRPC.
In order to call a remote service, you need to construct a client struct using one of its "invoker" constructors:
public readonly partial record struct WidgetClient : IWidget, IProtobufClient{ public WidgetClient( IInvoker invoker, ServiceAddress? serviceAddress = null, ProtobufEncodeOptions? encodeOptions = null) { ... }
public WidgetClient( IInvoker invoker, System.Uri serviceAddressUri, ProtobufEncodeOptions? encodeOptions = null) : this(invoker, new ServiceAddress(serviceAddressUri), encodeOptions) { }}
The invoker
parameter represents your invocation pipeline, the serviceAddress
or serviceAddressUri
parameter corresponds to the address of the remote service, and the encodeOptions
parameter allows you to customize the encoding of your Protobuf messages. See ProtobufEncodeOptions
for details.
A null
service address is equivalent to an icerpc service address with the default service path of the associated Protobuf service.
The default service path of a Protobuf service is /
followed by its fully qualified name. For example, the default service path of Protobuf service visitor_center.Greeter
is /visitor_center.Greeter
.
INameService
Interface INameService is a server-side helper: it helps you create a service (a C# class) that implements Protobuf service Name.
The principle is straightforward: your service class must be a partial class that implements INameService. It must also carry the ProtobufService
attribute.
The ProtobufService
attribute instructs the Protobuf Service source generator to implement interface IDispatcher
by directing incoming requests to INameService methods based on the Protobuf RPC method names.
For example:
syntax = "proto3";package example;option csharp_namespace = "Example";
service Widget { rpc Spin (SpinConfig) returns (google.protobuf.Empty);}
namespace Example;
// Generated codepublic partial interface IWidgetService{ // One method per operation ValueTask<WellKnownTypes.Empty> SpinAsync( SpinConfig message, IFeatureCollection features, CancellationToken cancellationToken);}
// Application code[ProtobufService]internal partial class MyWidget : IWidgetService{ // implement SpinAsync ...}
Even though INameService is an interface, it's not used as an abstraction: you shouldn't make calls to this interface or create decorators for this interface. It's just a model that your service class must implement.
Note that the same service class can implement any number of Protobuf services, provided their RPC methods have unique names. For example:
syntax = "proto3";package example;option csharp_namespace = "Example";
service Widget { rpc Spin (SpinConfig) returns (google.protobuf.Empty);}
service Counter { rpc GetCount (google.protobuf.Empty) returns (Count);}
// A service class that implements 2 Protobuf// services[ProtobufService]internal partial class MyWidget : IWidgetService, ICounterService{ // implements SpinAsync and GetCountAsync.}
deprecated option
If you set the deprecated
option to true
on a Protobuf service, the 2 interfaces and the struct generated by protoc-gen-icerpc-csharp
are marked Obsolete.