gRPC and C#: Revolutionize Service Communication

When people talk about APIs, 90% refer to a REST API, which today is the most common and popular way to connect applications, whether from the user interface, from a third-party application, or another system.

 

But REST is not the only way—previously we looked at GraphQL and its features, and today, we are going to see another method of communication: gRPC.

 

 

 

 

1 - What is gRPC?

 

According to the official description (on their website), gRPC is a universal, open-source high-performance RPC framework.

 

Let's break down what this means:

 

  • High performance refers to the way communication or calls are serialized and deserialized.
  • Universal framework means it's language-agnostic—it works with C#, Java, C++, Python, etc.
  • RPC stands for Remote Procedure Call, which means one service or program requests another to perform a certain operation. It's the equivalent of a request in the REST world, which you are likely more familiar with.

And the "g"? Well, if you go to the website, they'll tell you it's like the "L" in Linux—the "g" stands for gRPC.

The unofficial truth is that the "g" stands for Google, because that's the company that created it.

 

 

1.1 - gRPC Features

 

When working with gRPC, we're talking about binary communication. We no longer send text as we do with REST or even GraphQL, but instead, we send binary data, meaning the packets sent over the network are as small as possible.

 

Communication in gRPC is based on contracts. What does this mean? Very simply, to serialize and deserialize correctly, both the client consuming the service and the server providing it must have the same contract, otherwise deserialization will fail.

These contracts are the gRPC definition files with the .proto extension. As mentioned, they need to be kept updated when changes occur.

 

HTTPS is required. gRPC only works with secure HTTPS calls. Authentication can be added, but that's optional; HTTPS is mandatory.

Note: it works over HTTP/2.

 

Bidirectional streaming: If you're used to REST, everything works with Request/Response. gRPC has the capability to send and receive data simultaneously; for example, a request doesn't need to finish for data to start returning.

 

A simple example is a video conference, where all participants are sending and receiving data at the same time.

 

 

2 - Where to use gRPC?

 

Based on the first point, you might think we should use gRPC everywhere, right? It's faster and lighter than any alternative, so it would make sense.

 

The reality is, that's not the case. The reasons are simple: from the front end, especially in JavaScript, working with gRPC isn't as straightforward as the alternatives, so most front ends avoid it. Maintaining the contract from the front end is not something they enjoy, since when they make a call, they can't visibly see what they're sending to the server while developing 😅.

 

Moreover, serializing to binary is more costly than to JSON, and considering many of our clients use computers that are 15 or 20 years old, it just doesn't pay off.

 

In my opinion, communication between front end and back end should be done with REST/GraphQL.

 

Where gRPC excels is in system-to-system communication.

 

If you have an API that calls another API or another service, that's where gRPC shines and does a great job.

grpc explanation

In summary, anywhere the user doesn't have access.

As you can see in this image, there is a call between the orders API and the products API. When we just said that if the user has access we use REST or GraphQL, yes and no. We use REST for the part the user accesses, but if we need to query something from another API, we can use gRPC.

 

Note: in this image, we have a fully coupled system—in other words, a distributed monolith. If you want to implement distributed systems the right way, I recommend you check out my free course on distributed systems.

 

When an API is public to users but also used for internal access like in the example, you shouldn't always use gRPC. You need to weigh development and maintenance costs versus the benefits. The main point is that both systems, a REST API and gRPC, are completely compatible in the same application.

 

As an additional note, Linkedin has an article on their engineering blog about how they migrated from REST to gRPC and improved latency—in other words, response time—by 60%.

As a side effect of saving 60% in latency, you also save costs—not just in bandwidth but also in CPU, memory, etc., which is hugely important in the serverless world we live in today.

 

 

3 - gRPC Contracts

 

gRPC contracts deserve a section of their own. Even though I could include them in the implementation section since they're language-agnostic, I think it's important to separate them.

 

Here's an example of a contract you can find in the C# gRPC template:

syntax = "proto3";option csharp_namespace = "GrpcService1";package greet;// The greeting service definition.service Greeter {  // Sends a greeting  rpc SayHello (HelloRequest) returns (HelloReply);}// The request message containing the user's name.message HelloRequest {  string name = 1;}// The response message containing the greetings.message HelloReply {  string message = 1;}

This is a contract—in other words, the protobuf file, which defines the interface (hence it's called a contract) that both our server and client will use, and this file is completely language-independent.

 

 

Now let's break down this file.

 

  • The first line defines the schema the file will use, in our case proto3. It's basically the version and doesn't need to be changed.
  • The next line, option csharp_namespace = "GrpcService1";, defines the namespace where the code generated from this file will be located in our codebase. Usually, we'll pick a namespace within our server project, and this is unique to C#. Other languages may use different options, for example, in Ruby it's ruby_package GrpcService1.
  • The line package greet is used to organize protobufs; it's unrelated to the implementing language.
  • The next section defines the service and the methods or functions that this file will generate and support:
service Greeter {  // Sends a greeting  rpc SayHello (HelloRequest) returns (HelloReply);}// The request message containing the user's name.message HelloRequest {  string name = 1;}// The response message containing the greetings.message HelloReply {  string message = 1;}

As you can see, it contains both the request and response defined below.

  • The numbers =1 in the messages identify each element used for serialization and deserialization. If you have multiple properties, just number them sequentially; there's no point in skipping numbers.

 

 

4 - Implementing gRPC in C#

 

In today's post, we'll see how to implement both the client and server sides using C#.

 

4.1- Implementing a gRCP Server in C#

 

In this post, we'll look at the server-side using the default gRPC project template provided by Visual Studio / Rider. You can absolutely set up gRPC on an existing project. In any case, we'll explain all the necessary steps.

 

First, you need the NuGet package Grpc.AspnetCore.

 

Next, create your proto file, which in the template's case is greet.proto as we discussed earlier, and it typically goes in a folder called protos.

 

With just this, we can't do anything yet. You can't directly reference a .proto file. This is where the package we added comes in, because now, when you build the project, a C# file is auto-generated containing both the types and methods included in the proto file.

protobuf build c#

This file is autogenerated, and although you technically could edit it, don't—the next time you build, it will be overwritten.

 

Previously unmentioned: this file works differently from the rest, as it has its own tag in the csproj, which is important since it defines whether your app will be a server or client:

<ItemGroup>    <Protobuf Include="Protos\greet.proto" GrpcServices="Server"/></ItemGroup>

 

Once that's clear, the autogenerated C# file contains not only the types and methods. These methods are in an abstract class called GreeterBase, which is named after the service in our proto file.

 

The important thing is this class has all the methods we specified, but their implementations throw exceptions:

/// <summary>Base class for server-side implementations of Greeter</summary>[grpc::BindServiceMethod(typeof(Greeter), "BindService")]public abstract partial class GreeterBase{    /// <summary>    /// Sends a greeting    /// </summary>    /// <param name="request">The request received from the client.</param>    /// <param name="context">The context of the server-side call handler being invoked.</param>    /// <returns>The response to send back to the client (wrapped by a task).</returns>    [global::System.CodeDom.Compiler.GeneratedCode("grpc_csharp_plugin", null)]    public virtual global::System.Threading.Tasks.Task<global::GrpcService1.HelloReply> SayHello(global::GrpcService1.HelloRequest request, grpc::ServerCallContext context)    {    throw new grpc::RpcException(new grpc::Status(grpc::StatusCode.Unimplemented, ""));    }}

 

But that's not a problem, because what we'll do is create a class that implements this abstract class along with its methods. In this class, we can inject any services we want or perform whatever operations we need. In short, it's the entry point to your application when using gRPC.

public class GreeterService : Greeter.GreeterBase{    private readonly ILogger<GreeterService> _logger;    public GreeterService(ILogger<GreeterService> logger)    {        _logger = logger;    }    public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)    {        return Task.FromResult(new HelloReply        {            Message = "Hello " + request.Name        });    }}

 

And as with nearly every feature in .NET, we need to include gRPC in the dependency container and include the service in the request pipeline:

builder.Services.AddGrpc();...app.MapGrpcService<GreeterService>();

 

Now our application is ready to receive calls—but not all types of calls, only gRPC calls, as we've only enabled Http2 in the configuration:

"Kestrel": {    "EndpointDefaults": {        "Protocols": "Http2"    }}

 

If you want to receive calls in a "normal" REST API, you should change the protocol to Http1AndHttp2. This way, your API will support both gRPC and standard HTTP calls.

 

 

 

4.2 - Consuming a gRPC Service from C#

 

As a service consumer, you can use any type of project—it can be a C# project or any language—since what matters is using the proto file created on the server application.

 

As we discussed before, the proto file is known to both the server application (which creates it) and the client application. In our case, we'll create an API with an endpoint that simply calls the gRPC service we just created.

 

For now, we're just going to create an endpoint called get-greetings, where the call will take place:

app.MapGet("/get-greetings", ()=> "TODO");

 

To set up our code, we need to do two things.

First, we need to install several NuGet packages:

  • Grpc.Net.Client
  • Google.Protobuf
  • Grpc.Tools

 

Then we need to copy the proto file from the server application to this new one and ensure in the csproj that it's marked as Client:

<ItemGroup>    <Protobuf Include="Protos\greet.proto" GrpcServices="Client" /></ItemGroup>

This is important when accessing the information generated by the build. Just as server-side creates a base service for you to add your logic, client-side auto-generates a client for you.

 

To use this client in our endpoint, we'll do a few steps: first, create a GrpcChannel indicating the URL where our service is hosted.

With that channel, we'll use the auto-generated code from our proto file to instantiate the client and finally call the desired client method:

app.MapGet("/get-greetings", () =>{    GrpcChannel channel = GrpcChannel.ForAddress("https://localhost:7171");    Greeter.GreeterClient client = new Greeter.GreeterClient(channel);    HelloReply reply = client.SayHello(new HelloRequest { Name = "example name" });    return reply.Message;});

 

And that's all it takes (provided you also do the next step in the blog, 4.2.1):

grpc result

 

4.2.1 - Test gRPC locally with certificates

 

Previously, I mentioned gRPC only works via https—but not only that, when using HTTP/2, you also need TLS enabled, which can be a pain as you need a valid certificate.

 

There are two ways to solve this locally: the first is by creating an HTTP handler that skips certificate validation:

var handler = new HttpClientHandler();handler.ServerCertificateCustomValidationCallback =     HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; 👈var channel = GrpcChannel.ForAddress("https://localhost:7171",    new GrpcChannelOptions { HttpHandler = handler }); 👈var client = new Greet.GreeterClient(channel); 

Obviously, this option is risky, just in case you forget to remove it before going to production.

 

The second is to trust the local dotnet certificate, which you do by running the following command:

dotnet dev-certs https --trust

And in my opinion, this is what you should use when working locally.

 

 

4.2.2 - Add gRPC to the dependency container

 

Our endpoint looks a bit messy, instantiating the channel and then the client—it honestly doesn't look very elegant. What we can do is add this client to the dependency container. To do that, simply add gRPC to the container, then register the client:

builder.Services.AddGrpc();builder.Services.AddGrpcClient<Greeter.GreeterClient>(x =>{    x.Address = new Uri("https://localhost:7171");});

 

Then, in the endpoint (or use case), just inject the client:

app.MapGet("/get-greetings", (Greeter.GreeterClient client) =>{    HelloReply reply = client.SayHello(new HelloRequest { Name = "example name" });    return reply.Message;});

 

As you can see, when we added the client to the dependency container, we used AddGrpcClient<T>, which means you can have as many clients as you want.

 

This post was translated from Spanish. You can see the original one here.
If there is any problem you can add a comment bellow or contact me in the website's contact form

Uso del bloqueador de anuncios adblock

Hola!

Primero de todo bienvenido a la web de NetMentor donde podrás aprender programación en C# y .NET desde un nivel de principiante hasta más avanzado.


Yo entiendo que utilices un bloqueador de anuncios como AdBlock, Ublock o el propio navegador Brave. Pero te tengo que pedir por favor que desactives el bloqueador para esta web.


Intento personalmente no poner mucha publicidad, la justa para pagar el servidor y por supuesto que no sea intrusiva; Si pese a ello piensas que es intrusiva siempre me puedes escribir por privado o por Twitter a @NetMentorTW.


Si ya lo has desactivado, por favor recarga la página.


Un saludo y muchas gracias por tu colaboración

© copyright 2025 NetMentor | Todos los derechos reservados | RSS Feed

Buy me a coffee Invitame a un café