How to implement gRPC in ASP.NET Core

How to implement gRPC in ASP.NET Core
gRPC is a Remote Procedure Call protocol developed by Google which is up to 6 times faster than REST APIs. In this tutorial you will learn everything to build a gRPC service in ASP.NET Core. I covered the following types of calls:
  • Unary
  • Server streaming
  • Client streaming
  • Bi-directional streaming
The full source codes are given at the bottom of this tutorial. So enjoy learning gRPC.
gRPC is a modern open source high performance RPC framework that can run in any environment. It is developed by Google and is included from ASP.NET Core 3.0 version. In this tutorial you will learn how to create a .NET Core gRPC client and an ASP.NET Core gRPC Server that will communicate with one another.

gRPC has different types of methods and this tutorial will teach you all this in details. The gRPC method types are:

  • Unary
  • Server streaming
  • Client streaming
  • Bi-directional streaming

Create a gRPC service

  • First start the Visual Studio and select Create a new project.
  • In the Create a new project dialog, select gRPC Service and select Next. Create gRPC Service
  • Name the project as GrpcService.

Once the project is created examine the different project files, these are:

  • greet.proto – This file is created inside the Protos folder. It is the protobuf file that defines the contract (like models, methods) exposed by this service. Protobuf is just a serialization/deserialization tool like JSON but up to 6 times faster than JSON.
  • GreeterService.cs – This file is created inside the ‘Services’ folder and contains the implementation of the gRPC service.
  • appsettings.json – Contains configuration data, such as protocol used by Kestrel.
  • Program.cs – Contains the entry point for the gRPC service.
  • Startup.cs – Contains code that configures app behavior.

Let us discuss each of these files one by one.

greet.proto

In the greet.proto file you will define your gRPC service and all the methods which this service will contain. Since the contracts are defined in non C# syntaxes therefore in order to communicate with it, the dot net framework converts it to a C# based file.

You may ask how? The answer is when you build your solution in Visual Studio then a new file called Greet.cs is created at ‘GrpcService\obj\Debug\netcoreapp3.1\Greet.cs’. This file is auto generated by the compiler and contains implementation of greet.proto methods in C# syntaxes. I have shown the screenshot of this Greet.cs file below:

greet.cs

Now open the greet.proto file and find the default implementation of the service as:

// 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;
}
Notice the gRPC service is named as Greeter and contains just one method by the name of SayHello.
rpc SayHello (HelloRequest) returns (HelloReply);

This SayHello method accepts a parameter of HelloRequest type and returns data of HelloReply type.

The HelloRequest and HelloReply types are defined as:

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
HelloRequest has just one member called ‘name’ and of type string while HelloReply also has a single member called ‘message’ and of type string.

This makes it quite clear that when you add a new method to the gRPC service make sure to add it inside the gRPC service and define it’s parameter and return type.

The numbered tags used after the members are used to match fields when serializing and deserializing the data. The client will also have greet.proto file and the numbered tags used there should match with those of the greet.proto file of the service.

The code to add a new method called ‘DoWork’ is shown below:

service Greeter {
  rpc DoWork (SomeInput) returns (SomeOutput);
}

message SomeInput {
  …
}

message SomeOutput {
  …
}

You will understand more about this once you will create gRPC methods like Unary, Server and client streaming, Bi-directional streaming later in this tutorial.

What to learn how to create REST APIs in ASP.NET Core then check my 3 tutorials that are made for beginners to advanced professionals –

GreeterService.cs

As stated earlier this class is created inside the ‘Services’ folder and contains the implementation of the gRPC service in C#. Whatever methods you define in the greet.proto file, you need to implement them in this class in C#.

Open this to find the SayHello method whose code is shown below:

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

It’s just a simple method which returns a simple message.

Now suppose you need to implement a new method called ‘DoWork’. You do this by adding the below code to this class as shown below:

public override Task<SomeOutput> DoWork(SomeInput request, ServerCallContext context)
{
    //… return SomeOutput type
}

I will explain all about this later in the tutorial.

appsettings.json

Right now gRPC service can only be hosted in Kestrel and requires HTTP/2 protocol secured with TLS. In appsettings.json you define the Kestrel configuration.

In development you don’t have to modify anything in the appsettings.json file. But remember – when you host your gRPC service (that is in Production) then the Kestrel endpoints used for gRPC should be secured with TLS. So the productionappsettings.json file will look something like shown below:

{
  "Kestrel": {
    "Endpoints": {
      "HttpsInlineCertFile": {
        "Url": "https://localhost:5001",
        "Protocols": "Http2",
        "Certificate": {
          "Path": "<path to .pfx file>",
          "Password": "<certificate password>"
        }
      }
    }
  }
} 

Program.cs

If you want to run the gRPC service in macOS then you do the additions configurations in this file. For windows the default configuration will work properly in most of the time.

Startup.cs

Your Startup.cs class comes auto configured for gRPC service. Open this file and see the –

  • In the ConfigureServices method the gRPC is enabled with the AddGrpc method.
  • In the Configure method the gRPC service is added to the routing pipeline through the MapGrpcService method.

See the below code where I have shown these method:

public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // after app.UseRouting();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapGrpcService<GreeterService>();

        endpoints.MapGet("/", async context =>
        {
            await context.Response.WriteAsync("Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
        });
    });
}

After this let us start with the real building of the gRPC.

The gRPC Service project overview
Here in this tutorial I will be building is a gRPC service that will be returning random JOKES to the client. It will have 4 methods of types:
  • Unary Call
  • Server streaming call
  • Client streaming call
  • Bi-directional streaming call
This gRPC service will be called from a client project. The client project is an ASP.NET Core Web Application. It’s working will be discussed as I go along with the calls.

Unary call Example

A simplest gRPC method type is a Unary call which starts with the client sending a request message. A response message is returned when the service finishes.

Let us create a gRPC Unary Call method that will send a requested JOKE to the client. Here the client will send the JOKE no to the gRPC service.

So in your GrpcService project, go to the greet.proto file and add a new service method called SendJoke and define it’s parameters and return type like:

// The service definition.
service Greeter {
  rpc SendJoke (JRequest) returns (JResponse);
}

// Joke request
message JRequest {
  int32 no = 1;
}

// Joke response
message JResponse {
  repeated Joke Joke = 1;
}

// Joke
message Joke {
  string author = 1;
  string description = 2;
}

 The work of the SendJoke gRPC method is to send a JOKE to the client based on the JOKE no which the client sends to the service. So I have defined the JOKE no as a member of the JRequest.

message JRequest {
  int32 no = 1;
}
The ‘SendJoke’ method sends a JResponse type which is one or many Jokes. So I used repeated keyword for the Joke type. Note that the Joke type is a member of JResponse and Joke must also be defined. This is done as:
// Joke response
message JResponse {
  repeated Joke Joke = 1;
}

// Joke
message Joke {
  string author = 1;
  string description = 2;
}

Now build your project by pressing the F6 key so that Greet.cs is auto updated by the compiler, and the SendJoke method is generated in that file.

Next, open the GreeterService.cs file and implement the SendJoke method which you just defined. The code which you need to add is given below:

public override Task<JResponse> SendJoke(JRequest request, ServerCallContext context)
{
    List<Joke> jokeList = JokeRepo();

    JResponse jRes = new JResponse();
    jRes.Joke.AddRange(jokeList.Skip(request.No-1).Take(1));

    return Task.FromResult(jRes);
}

public List<Joke> JokeRepo()
{
    List<Joke> jokeList = new List<Joke> {
        new Joke { Author = "Random", Description = "I ate a clock yesterday, it was very time-consuming"},
        new Joke { Author = "Xeno", Description = "Have you played the updated kids' game? I Spy With My Little Eye ... Phone"},
        new Joke { Author = "Jak", Description = "A perfectionist walked into a bar...apparently, the bar wasn’t set high enough"},
        new Joke { Author = "Peta", Description = "To be or not to be a horse rider, that is equestrian"},
        new Joke { Author = "Katnis", Description = "What does a clam do on his birthday? He shellabrates"}
    };

    return jokeList;
}
I defined that SendJoke() method that has 2 parameters:
  • 1. JRequest request
  • 2. ServerCallContext context – The ServerCallContext argument passed to each gRPC method and provides access to some HTTP/2 message data, such as the method, host, header, and trailers.
I access the joke no from the JRequest object and fetch it from a Joke repository. I simply return this joke to the client by first adding it to a JResponse object and then return it at the end of the code:
List<Joke> jokeList = JokeRepo();

JResponse jRes = new JResponse();
jRes.Joke.AddRange(jokeList.Skip(request.No-1).Take(1));

return Task.FromResult(jRes);
Creating the Client project
Let us now make a client project that will call the gRPC service. The client project will be an ASP.NET Core Web Application. Here I have named my project as GrpcCore.
ASP.NET Core Web Application
The gRPC client project requires the following packages: You can install these packages by running these 3 powershell commands:
Install-Package Grpc.Net.Client
Install-Package Google.Protobuf
Install-Package Grpc.Tools	
After this you edit the project file (by right clicking the project name on the Solution explorer and select ‘Edit Project File’), and then add an item group with a element that refers to the greet.proto file:
<ItemGroup>
  <Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>

You also have to provide the client with the greet.proto file which should be exactly same to that of the gRPC service project. Simply copy the ‘Protos’ folder that contains the greet.proto file from the gRPC service project and paste it to the client project.

With this the client is ready to make the gRPC call. So go to the controller (I am using HomeController.cs in my case) and add the following namespaces.

using Grpc.Net.Client;
using GrpcGreeter;
Then add an action method called Unary, and a function called ChangetoDictionary. Check the below code:
public async Task<IActionResult> Unary()
{
    var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var client = new Greeter.GreeterClient(channel);
    var reply = await client.SendJokeAsync(new JRequest { No = 3 });

    return View("ShowJoke", (object)ChangetoDictionary(reply));
}

private Dictionary<string, string> ChangetoDictionary(JResponse response)
{
    Dictionary<string, string> jokeDict = new Dictionary<string, string>();
    foreach (Joke joke in response.Joke)
        jokeDict.Add(joke.Author, joke.Description);
    return jokeDict;
} 
A gRPC client is created from a channel. The method called GrpcChannel.ForAddress is used to create a channel, and then this channel is used to create a gRPC client: After that the call to the service is made asynchronously as shown below:
var reply = await client.SendJokeAsync(new JRequest { No = 3 });

The work of the ChangetoDictionary method is to convert the JResponse object sent by the gRPC service to the Dictionary() type.

In the last line I returned this dictionary that contains my jokes to the view called ‘ShowJoke’.

return View("ShowJoke", (object)ChangetoDictionary(reply));
Next, create ShowJoke view inside the Views/Shared folder and add the following code to it:
@model Dictionary<string, string>

<table>
    <thead>
        <tr>
            <th>Author</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var p in Model)
        {
            <tr>
                <td>@p.Key</td>
                <td>@p.Value</td>
            </tr>
        }
    </tbody>
</table>

It’s time to run and see how the client performs. So first make sure your gRPC service project is running. Then run your ASP.NET Core client project and go to the URL to initiate the ShowJoke view. The URL in my case is – https://localhost:44304/Home/Unary

You will see the Joke no 3 is fetched and displayed as shown in the below image:

grpc unary

Congrats you now learned how to create Unary gRPC method and call it from your ASP.NET Core application. Coming next are the:

  • Server streaming call
  • Client streaming call
  • Bi-directional streaming call

Server streaming call Example

The server streaming call works like this way:
  • The client sends a request to the server and receives a stream of messages in return.
  • The number of messages which will be streamed is determined by the server.
  • The gRPC guarantees that the client receives the messages in the same order as they are sent from the server.
To understand it let us create a method for implementing the Server streaming call. So go to the greet.proto file of the GrpcService project and add a new method called SendJokeSS.
service Greeter {
  rpc SendJoke (JRequest) returns (JResponse);
  rpc SendJokeSS (JRequest) returns (stream JResponse);
}

Note – since it is a server streaming case so I added the stream keyword for the JResponse return type.

Next, go to the GreeterService.cs file and do this method’s implementation by adding the following code to it.

public override async Task SendJokeSS(JRequest request, IServerStreamWriter<JResponse> responseStream, ServerCallContext context)
{
    List<Joke> jokeList = JokeRepo();
    JResponse jRes;

    var i = 0;
    while (!context.CancellationToken.IsCancellationRequested)
    {
        jRes = new JResponse();
        jRes.Joke.Add(jokeList.Skip(i).Take(request.No));
        await responseStream.WriteAsync(jRes);
        i++;
        // Gotta look busy
        await Task.Delay(1000);
    }
}
This method has 3 parameters which are:
  • JRequest – which will be send by the client.
  • IServerStreamWriter – A writable stream of messages that is used in server-side and will be send to the client.
  • ServerCallContext – to provides access to some HTTP/2 message data, such as the method, host, header, and trailers
The main part is inside the while loop which loops until the cancellation has been requested by the client. The while loop examines the CancellationToken on the ServerCallContext object.

This token represents the state of the call. If the client were to cancel their request they signal that they no longer plan to read from the stream, and this cause the while loop to break.

Inside the while loop the code will send all the jokes from joke no 1 till the joke no send by the client. It does this by writing the jokes to the responseStream object one by one:

await responseStream.WriteAsync(jRes);

Now let us call this Server Streaming method from the client so go to the client project and in it’s greet.proto file first define this method:

service Greeter {
  rpc SendJoke (JRequest) returns (JResponse);
  rpc SendJokeSS (JRequest) returns (stream JResponse);
}

Next, go to the controller and add the following namespaces:

using System.Threading;
using Grpc.Core;
Then add a new action method called ServerStreaming in which the Server Streaming call to the service will be performed. The code for this method is given below:
public async Task<IActionResult> ServerStreaming()
{
    var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var client = new Greeter.GreeterClient(channel);

    Dictionary<string, string> jokeDict = new Dictionary<string, string>();

    var cts = new CancellationTokenSource();
    cts.CancelAfter(TimeSpan.FromSeconds(5));

    using (var call = client.SendJokeSS(new JRequest { No = 5 }, cancellationToken: cts.Token))
    {
        try
        {
            await foreach (var message in call.ResponseStream.ReadAllAsync())
            {
                jokeDict.Add(message.Joke[0].Author, message.Joke[0].Description);
            }
        }
        catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.Cancelled)
        {
            // Log Stream cancelled
        }
    }

    return View("ShowJoke", (object)jokeDict);
}

In this method I created a cancellation token that will be automatically cancelled after 5 seconds. I called the SendJokeSS method of the gRPC service and passed 2 things to it –

  • 1. Joke no 5
  • 2. The cancellation token
using (var call = client.SendJokeSS(new JRequest { No = 5 }, cancellationToken: cts.Token))
{
//…
}
I then used the await foreach syntax to read the response given by the service.

Test it by invoking the URL of the ServerStreaming action method which in my case is – https://localhost:44304/Home/ServerStreaming

You will see 5 jokes displayed on the View. This is shown by the below image:

grpc server streaming

Client streaming call Example

In Client Streaming Call the the client writes a sequence of messages and sends them to the server via a stream. Once the client has finished writing the messages, it waits for the server to read them and return a response.

In the greet.proto file of the GrpcService project add a new method called SendJokesCS and make sure this method has a stream type parameter.

service Greeter {
  // other methods
  rpc SendJokesCS (stream JRequest) returns (JResponse);
}

Next implement this method in the GreeterService.cs file as shown below.

public override async Task<JResponse> SendJokesCS(IAsyncStreamReader<JRequest> requestStream, ServerCallContext context)
{
    List<Joke> jokeList = JokeRepo();
    JResponse jRes = new JResponse();

    await foreach (var message in requestStream.ReadAllAsync())
    {
        jRes.Joke.Add(jokeList.Skip(message.No - 1).Take(1));
    }
    return jRes;
}

The parameter of this method includes an IAsyncStreamReader object to read a stream of messages sent by the client. In my case the client will send some joke no and this method will send back the Joke to the client.

Now let us call this method from the client. So in the greet.proto file of the client project add a new method called SendJokesCS.

service Greeter {
  // other methods
  rpc SendJokesCS (stream JRequest) returns (JResponse);
}

Then in the controller add a new action method and name it ‘ClientStreaming’. It’s code is given below:

public async Task<IActionResult> ClientStreaming()
{
    var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var client = new Greeter.GreeterClient(channel);

    Dictionary<string, string> jokeDict = new Dictionary<string, string>();
    int[] jokes = { 3, 2, 4 };

    using (var call = client.SendJokesCS())
    {
        foreach (var jT in jokes)
        {
            await call.RequestStream.WriteAsync(new JRequest { No = jT });
        }
        await call.RequestStream.CompleteAsync();

        JResponse jRes = await call.ResponseAsync;

        foreach (Joke joke in jRes.Joke)
            jokeDict.Add(joke.Author, joke.Description);
    }

    return View("ShowJoke", (object)jokeDict);
}

This method is fairly simple and start with adding 3, 2, 4 to a int array:

int[] jokes = { 3, 2, 4 };

The client can choose to send messages with RequestStream.WriteAsync. So I send these joke no to the gRPC service in client streaming way like:

foreach (var jT in jokes)
{
    await call.RequestStream.WriteAsync(new JRequest { No = jT });
}

When the client has finished sending messages, RequestStream.CompleteAsync should be called to notify the service. The call is finished when the service returns a response message. So I closes the stream as:

await call.RequestStream.CompleteAsync();

Finally adding the Jokes returned by the service in a dictionary object:

foreach (Joke joke in jRes.Joke)
    jokeDict.Add(joke.Author, joke.Description);
Security is a most important thing and you can secure your APIs with JWT. Check my 2 important tutorials on this subject –

Test it by invoking the URL of the ClientStreaming action method which in my case is – https://localhost:44304/Home/ClientStreaming

You will see jokes no 3, 2 and 4 displayed on the View. This is shown by the below image:

gRPC Client Streaming

Bi-directional streaming call Example

During a bi-directional streaming call, the client and service can send messages to each other at any time. So both the request and response should be of stream type.

I will create a bi-directional streaming call method for exchanging jokes by joke no.

Start by adding a new method, by the name of SendJokesBD, on the greet.proto file of both service and client projects.

service Greeter {
  // other methods
  rpc SendJokesBD (stream JRequest) returns (stream JResponse);
}

Notice the use of stream keyword for both request and response objects.

Next, in the GreeterService.cs file add the implementation of this bi-directional call method:

public override async Task SendJokesBD(IAsyncStreamReader<JRequest> requestStream, IServerStreamWriter<JResponse> responseStream, ServerCallContext context)
{
    List<Joke> jokeList = JokeRepo();
    JResponse jRes;

    await foreach (var message in requestStream.ReadAllAsync())
    {
        jRes = new JResponse();
        jRes.Joke.Add(jokeList.Skip(message.No - 1).Take(1));
        await responseStream.WriteAsync(jRes);
    }
}

The bi-directional streaming method has 3 parameters which are:

  • IAsyncStreamReader – for reading a stream of messages sent by the client.
  • IServerStreamWriter – a writable stream of messages to be sent to the client.
  • ServerCallContext – provides access to some HTTP/2 message data, such as the method, host, header, and trailers
Note: In bi-directional streaming the reading of the requests is done the same way as in the client-side streaming method and writing of the responses is done the same way as in the server-side streaming method.

The main work is done inside the for loop where I read the entire joke no send in the stream and writing them in the response stream one by one.

await foreach (var message in requestStream.ReadAllAsync())
{
    jRes = new JResponse();
    jRes.Joke.Add(jokeList.Skip(message.No - 1).Take(1));
    await responseStream.WriteAsync(jRes);
}

Now let us make the call to this method from the client. So go to the controller of your client project and add a new action method by the name of BiDirectionalStreaming. It’s full code is given below:

public async Task<IActionResult> BiDirectionalStreaming()
{
    var channel = GrpcChannel.ForAddress("https://localhost:5001");
    var client = new Greeter.GreeterClient(channel);

    Dictionary<string, string> jokeDict = new Dictionary<string, string>();

    using (var call = client.SendJokesBD())
    {
        var responseReaderTask = Task.Run(async () =>
        {
            while (await call.ResponseStream.MoveNext())
            {
                var response = call.ResponseStream.Current;
                foreach (Joke joke in response.Joke)
                    jokeDict.Add(joke.Author, joke.Description);
            }
        });

        int[] jokeNo = { 3, 2, 4 };
        foreach (var jT in jokeNo)
        {
            await call.RequestStream.WriteAsync(new JRequest { No = jT });
        }

        await call.RequestStream.CompleteAsync();
        await responseReaderTask;
    }
    return View("ShowJoke", (object)jokeDict);
}

In this case, I write the request to RequestStream and receive the responses from ResponseStream. When the connection is opened I perform an async operation to wait for the response stream from the gRPC service.

var responseReaderTask = Task.Run(async () =>
{
    while (await call.ResponseStream.MoveNext())
    {
        var response = call.ResponseStream.Current;
        foreach (Joke joke in response.Joke)
            jokeDict.Add(joke.Author, joke.Description);
    }
});

The for loop is just taking the joke no 3,2, 4 and sends them to the gRPC bi-directional call as a stream.

foreach (var jT in jokeNo)
{
    await call.RequestStream.WriteAsync(new JRequest { No = jT });
}

Test it by invoking the URL of the BiDirectionalStreaming action method which in my case is – https://localhost:44304/Home/BiDirectionalStreaming

You will see jokes no 3, 2 and 4 displayed on the View. This is shown by the below image:

grpc bi-directional streaming

You can download the full source codes from the below links:

Download

Conclusion

This was all about creating your gRPC Service using C# and ASP.NET Core. You can now create any type of gRPC service – simple to complex without any external help. I covered a fair amount of code in this post. Hope you enjoyed reading it so please share it on your facebook, twitter and linked account for your friends. Remember gRPC is upto 6 times faster than REST APIs.

Share this article -

yogihosting

ABOUT THE AUTHOR

This article has been written by the Technical Staff of YogiHosting. Check out other articles on "ASP.NET Core, jQuery, EF Core, SEO, jQuery, HTML" and more.

Leave a Reply

Your email address will not be published. Required fields are marked *