---
title: "Writing your first IceRPC + Protobuf client in C#"
description: "Learn how to write your first IceRPC + Protobuf client."
url: https://docs.icerpc.dev/getting-started/icerpc-protobuf-tutorial/client-tutorial
docs_index: https://docs.icerpc.dev/llms.txt
---

> For an index of every page, see [llms.txt](https://docs.icerpc.dev/llms.txt).

# Writing your first IceRPC + Protobuf client in C#

Learn how to write your first IceRPC + Protobuf client.

This tutorial is the second part of a two part series that shows how to create a complete application with IceRPC + Protobuf for C#. We start from scratch—you just need to have the .NET 10 SDK installed on your computer.

The first part of this tutorial showed how to [create the server](https://docs.icerpc.dev/getting-started/icerpc-protobuf-tutorial/server-tutorial.md). This part shows how to create the client.

## Create the client

```shell
dotnet new icerpc-protobuf-client -o MyProtobufClient
```

This command creates a new IceRPC client application in directory `MyProtobufClient`.

![MyProtobufClient in Visual Studio Code](https://docs.icerpc.dev/images/MyProtobufClient.png)

Let's examine each file:

### proto/visitor\_center/greeter.proto - the contract

This file is (and must be) identical or almost identical to the `greeter.proto` we used for the server:

```proto
syntax = "proto3";

package visitor_center;
option csharp_namespace = "MyProtobufClient";

// Represents a simple greeter.
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;
}
```

The only difference with our server's `greeter.proto` is the `csharp_namespace` option. That's fine: options don't change the contract. Here, the Protobuf compiler generates the C# code in namespace `MyProtobufClient` and contract-wise, it doesn't matter that the server uses a different namespace.

### Program.cs - the client

The main program starts by creating a connection to the server:

```csharp
// Create a simple console logger factory and configure the log level for category IceRpc.
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
    builder
        .AddSimpleConsole()
        .AddFilter("IceRpc", LogLevel.Information));

// Path to the root CA certificate.
using X509Certificate2 rootCA =
    X509CertificateLoader.LoadCertificateFromFile("certs/cacert.der");

// Create a client connection that logs messages to a logger with category
// IceRpc.ClientConnection.
await using var connection = new ClientConnection(
    new Uri("icerpc://localhost"),
    clientAuthenticationOptions: CreateClientAuthenticationOptions(rootCA),
    logger: loggerFactory.CreateLogger<ClientConnection>());
```

This connection naturally matches our server configuration:

- the server address is `icerpc://localhost`, meaning we connect with the `icerpc` protocol to `localhost` on the default port for `icerpc` (4062)
- we don't specify a transport so we use the default multiplexed transport (`quic`)
- Setting `clientAuthenticationOptions` is required with `quic`

> **Note**
>
> Creating the connection instance does not establish the connection.

Then, the client program creates an invocation pipeline that flows into the connection:

```csharp
Pipeline pipeline = new Pipeline()
    .UseLogger(loggerFactory)
    .UseDeadline(defaultTimeout: TimeSpan.FromSeconds(10))
    .Into(connection);
```

When we send a request, the request goes through the [Logger](https://code.icerpc.dev/csharp/0.6.x/api/reference/IceRpc.Logger.html) interceptor and the [Deadline](https://code.icerpc.dev/csharp/0.6.x/api/reference/IceRpc.Deadline.html) interceptor before being sent over the connection. The `Deadline` interceptor we install here ensures the request times out after 10 seconds without a response.

> **Note**
>
> The Deadline interceptor communicates with the Deadline middleware via a request field sent alongside each request; this allows the middleware to enforce the deadline created by the interceptor. On the other hand, the Logger interceptor and middleware are totally independent.

Next, the client program creates a `Greeter` client with this invocation pipeline:

```csharp
var greeter = new GreeterClient(pipeline);
```

`GreeterClient` is a struct that the `protoc-gen-icerpc-csharp` generator generates from Protobuf service `Greeter`. This struct allows us to send requests to a remote service that implements `Greeter`.

With this code, the address of the target service (or [service address](https://docs.icerpc.dev/icerpc/invocation/service-address.md)) is the default for `Greeter`, namely `icerpc:/visitor_center.Greeter`. It matches the route we created in the server. We could also create the same client with an explicit service address:

```csharp
var greeter = new GreeterClient(pipeline, new Uri("icerpc:/visitor_center.Greeter"));
```

Finally, the client sends a `Greet` request, awaits the response (the greeting), prints the greeting and shuts down the connection gracefully:

```csharp
var request = new GreetRequest { Name = Environment.UserName };

GreetResponse response = await greeter.GreetAsync(request);

Console.WriteLine(response.Greeting);
await connection.ShutdownAsync();
```

When we call `greeter.GreetAsync`, the connection to the server is not yet established: it's the `GreetAsync` call that triggers the connection establishment.

### Program.Authentication.cs - AuthenticationOptions helper

This file contains the `CreateClientAuthenticationOptions` helper method. It creates an `SslClientAuthenticationOptions` from the root CA.

### MyProtobufClient.csproj - the project file

The project file is identical to the server's project file, with references to 4 separate IceRpc NuGet packages:

- [IceRpc.Protobuf](https://www.nuget.org/packages/IceRpc.Protobuf) - the IceRPC + Protobuf integration package
- [IceRpc.Protobuf.Tools](https://www.nuget.org/packages/IceRpc.Protobuf.Tools) - the package that compiles `greeter.proto` into `generated/Greeter.cs` and `generated/Greeter.IceRpc.cs`
- [IceRpc.Deadline](https://www.nuget.org/packages/IceRpc.Deadline) and [IceRpc.Logger](https://www.nuget.org/packages/IceRpc.Logger) - the packages with the two interceptors we installed in our invocation pipeline

## Run the full application

### Start the server

```shell
cd MyProtobufServer
dotnet run
```

The server is now listening for new connections from clients.

### Start the client

```shell
cd MyProtobufClient
dotnet run
```

The client sends a single `Greet` request to the service hosted by our server:

```
info: IceRpc.Logger.LoggerInterceptor[0]
      Sent request Greet to icerpc:/visitor_center.Greeter over [::1]:59405<->[::1]:4062 and received a response with status code Ok
Hello, jose!
```

### Shutdown the server

Press Ctrl+C on the server console to shut it down.
