Blog post .NET, Technical

gRPC in .Net Core 3.0

Introduction

gRPC is nothing but Remote Procedure Call (RPC). But, why is it prefixed with a “g”? Because … Google; it’s made by Google.

“gRPC is a modern open source high performance RPC framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.– grpc.io

gRPC Structure

gRPC is a method of web communication between services – it’s kind of like an API. It responds to requests across the web and gives the caller back the requested information. The differences are in how it’s set up and how it transports data.

  1. gRPC relies on a known configuration that is shared between the client and the server. This connection is something like a contract. These contracts are called Protocol Buffers.

  2. gRPC communicates using a binary stream. This is much more efficient than a web server that can use JSON or XML.

There are other differences, but those two are the most important. Let’s build a gRPC server and a client, so we can see the other differences in the context of how they’re used.

Building application

1. Building the server

We’ll create a new project – gRPC Service since this is a .Net Core 3.0 item. We should have only .Net core 3.0 installed.

Creating a new project - gRPC Service

First, we’ll create a gRPC Server Project named gRPCServer.

Project and Solution name configuration

So, this is the server-side of gRPC. It’s equivalent to a web API where it sits and listens for calls.
Let’s talk through the various pieces of this.

#Proto file


The most important file is greet.proto in the Protos folder. gRPC proto files are the way we define the contract between the server and the client. Let’s look at different pieces of this file.

syntax = "proto3";

option csharp_namespace = "gRPCServer";

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;
}

1. It says, “Use the latest proto syntax or schema.”. That’s all.

syntax = "proto3";

2. It specifies the namespace for this particular proto.

option csharp_namespace = "gRPCServer";

3. You can add an optional package to prevent name clashes between protocol message types.

package Greet;

4. Message – think of it kind of like a definition for a model in C#. We’re just defining a file or a class that has a bunch of properties in it. Inside the message, we can specify zero or more properties.

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings.
message HelloReply {
  string message = 1;
}

String name = 1; - One stands for the order in which this property goes. Usually, we specify two messages, where the first one is for the parameter that we pass in, and the other one stands for the response.

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply);
}

5. Service – This is the service that this particular proto is going to talk about. Essentially it is a method SayHello where we pass HelloRequest message and which returns HelloReply message. It’s possible to pass and return ONLY one parameter – message. Because of that, we can create a nested object (message) to work with complex data, just like a model in C#. In C# we might write service in this way:

public HelloReply SayHello(HelloRequest input)
{
    //Do something
}

#Service

We have the GreeterService, which inherits from Greeter.GreeterBase. We pull this from service Greeter in the proto file. But where does Greeter.GreeterBase come from? If we press F12 (Go to Definition) here, we get GreetGrpc.cs file.

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
            });
        }
    }
Greeter Service Solution Explorer

So, what is this file? If we look at where the file is stored, we can see that it's not in the project folder! It’s stored in the generated obj file. So this is a generated file, and every time we build the project, this file will be regenerated.

Obj folder path

Because of that, it is very important not to change this file manually. This creates all of the setup stuff for us behind the scenes.

GreeterBase class

In the GreeterService.cs file, we focus on one piece of code that is the most important for us. We have a public override SayHello method. That’s because it’s implemented in GreeterBase already, but in GreeterBase, we notice that method throws a new RPC exception if we try to use it. It says, “Hey, I’m not implemented.” And because of that, we need to override each method in our GreeterService from the proto file. When the method SayHello is called, actually this method runs. The result is HelloReply object (HelloReply message in the proto file).

public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
        {
            return Task.FromResult(new HelloReply
            {
                Message = "Hello " + request.Name
            });
        }

If we run this project, we’ll get a console application. At the first info line, we see that the server is running on localhost:5001 (which is the default).

Running application

2. Building the client

We’ll build the Console Application. Be aware that anything can be the client: web project, console application, or android application.
First up, we need to add new NuGet Packages. There are three packages we need to add:

  • Google.Protobuf - Allows us to work with proto files;
  • Grpc.Net.Client - Allows to talk with gRPC Services;
  • Grpc.Tools – Allows us to have a visual studio tooling that works with our settings.

The next thing that we need to do is add a proto file. In the newly created Protos folder, we should add the file greet.proto. This is the same file from the server. We can tell those two are shared contract between the server and the client. This way, they can recognize each other. We can copy this file from the server or create a new with the same content.

Proto file properties

Here we should set the properties for this file.
We can leave all default except gRPC Stub Classes. We should set up this for the client only. But there is an error when we try to do that. So, this can be done manually by changing GrpcServices value on Client from Protobuf item group in gRPCClient.csproj file.

Another important thing to do is rebuild the client project because it creates a new file that has client settings in the obj folder. So now we have that setup, and we can code.
Next, we should create a channel. We need to set the channel to have an address that is on our server. In this case, that is localhost with port 5001.

var channel = GrpcChannel.ForAddress("https://localhost:5001");

The next thing to do is to create the client.

var client = new Greeter.GreeterClient(channel);

This process is a little bit tricky. Here we create an instance of Greeter object which comes from the Server project. But we are using ONLY using statement for this; there is no reference in the Server project. They don’t know each other before connection. It looks like we're instantiating a Greeter object from a proto file from the server, but that’s not the case. We are creating a client that will communicate via channels to the server. And only then will they know that the server exists.

The last thing we will do is get a response from the server and write it in the client console.

var input = new HelloRequest() { Name = "Stefan" };
var reply = client.SayHello(input);
Console.WriteLine(reply.Message);

We’re creating HelloRequest type input with property Name = Stefan. After that, we’re calling SayHello method passing in input, and at the end, we’re writing that response in the client console.

Running the application

Before running the application, we need to start both applications. This is very simple. On right-click, on our solution, we choose “Set up StartUp Projects,” then select Multiple Startup projects and for action on both projects choose “Start.

When we run the project, we’re getting what we’re expected. The server is started and listening on port 5001. The client connects to the server, calls the method, and gets the right response which, is written in console “Hello Stefan.”

Console application result

Conclusion

First-class support for gRPC in the latest ASP.NET Core 3.0 release is great news for .NET developers. They will get easier than ever access to a framework that provides efficient, secure, and cross-language/platform Remote Procedure Calls between servers.

While it has a major drawback in the lack of browser support, there is no shortage of scenarios in which it can shine or at least become a worthy addition to your toolset. Building Microservices, native mobile apps, or the Internet of Things (IoT) are just a few of the examples where gRPC would be a good fit.

Contact us to discuss your project.
We're ready to work with you.
Let's talk