dotnet new icerpc-protobuf-server -o MyProtobufServer
This command creates a new IceRPC server application in directory MyProtobufServer.

Let's examine each file:
This file holds the contract between our client and server applications, specified with the Protobuf language.
It's a simple greeter:
syntax = "proto3";
package visitor_center;
option csharp_namespace = "MyProtobufServer";
service Greeter {
rpc Greet (GreetRequest) returns (GreetResponse);
}
message GreetRequest {
string name = 1;
}
message GreetResponse {
string greeting = 1;
}
The csharp_namespace option instructs the protoc compiler to map package visitor_center to C# namespace MyProtobufServer (our project name) instead of the default (VisitorCenter).
If you use this code as the starting point for a new application, you should update this service to represent something meaningful for your application. For this tutorial, we just keep Greeter as-is.
Class Chatbot is a service that implements Protobuf service Greeter:
[Service]
internal partial class Chatbot : IGreeterService
{
public ValueTask<GreetResponse> GreetAsync(
GreetRequest message,
IFeatureCollection features,
CancellationToken cancellationToken)
{
Console.WriteLine($"Dispatching Greet request {{ name = '{message.Name}' }}");
return new(new GreetResponse { Greeting = $"Hello, {message.Name}!" });
}
}
The protoc-gen-icerpc-csharp generator generates C# interface IGreeterService from Protobuf service Greeter. This C# interface is a template: we implement Greeter by implementing this interface. As you can see above, the Greet operation becomes a GreetAsync method with two additional parameters.
Since we always fulfill Greet synchronously, the GreetAsync implementation is not marked async. We could write the return statement as:
return new ValueTask<GreetResponse>(
new GreetResponse { Greeting = $"Hello, {message.Name}!" });
However, it's more convenient to omit the type name, especially when this type is complicated.
We mark class Chatbot as partial because the Service attribute instructs the Service Generator (a C# source generator) to implement interface IDispatcher—in other words, make Chatbot an IceRPC service implementation.
The main program starts by creating and configuring a Router:
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
builder
.AddSimpleConsole()
.AddFilter("IceRpc", LogLevel.Information));
Router router = new Router()
.UseLogger(loggerFactory)
.UseDeadline()
.Map(new Chatbot());
This router corresponds to our dispatch pipeline: when we receive a request, we first give it to the Logger middleware, then to the Deadline middleware and finally we route this request based on its path.
The Map call means if the request's path is the default service path for the Chatbot instance, route it to the Chatbot instance. Otherwise, the router returns a response with status code NotFound.
The default service path for the Chatbot instance is /visitor_center.Greeter because Chatbot implements a single Protobuf service, Greeter. The Map call above is a shortcut for:
.Map("/visitor_center.Greeter", new Chatbot());
The main program then creates a Server that directs all incoming requests to router:
using X509Certificate2 serverCertificate = X509CertificateLoader.LoadPkcs12FromFile(
"certs/server.p12",
password: null,
keyStorageFlags: X509KeyStorageFlags.Exportable);
await using var server = new Server(
dispatcher: router,
serverAuthenticationOptions: CreateServerAuthenticationOptions(serverCertificate),
logger: loggerFactory.CreateLogger<Server>());
We don't specify a server address so this server uses the default server address (icerpc://[::0]). This means the new server uses the icerpc protocol and will listen for connections on all network interfaces with the default port for icerpc (4062).
We don't specify a transport either so we use the default multiplexed transport (quic). Setting serverAuthenticationOptions is required with quic.
At this point, the server is created but is not doing anything yet. A client attempting to connect would get a "connection refused" error.
The server starts listening on the next line:
Listen returns as soon as the server is listening (so almost immediately).
The main program then awaits until it receives a Ctrl+C. After it receives Ctrl+C, it shuts down the server gracefully:
await CancelKeyPressed;
await server.ShutdownAsync();
This file contains the CreateServerAuthenticationOptions helper method. It creates an SslServerAuthenticationOptions from an X509Certificate2.
This file contains a few lines of code that Program.cs uses to wait for Ctrl+C. It's not related to RPCs.
The project file is straightforward. It contains references to the required IceRpc NuGet packages:
- IceRpc.Protobuf - the IceRPC + Protobuf integration package
- IceRpc.Protobuf.Tools - the package that compiles
greeter.proto into generated/Greeter.cs and generated/Greeter.IceRpc.cs - IceRpc.Deadline and IceRpc.Logger - the packages with the two middleware we installed in the dispatch pipeline