gRPC has different types of methods and this tutorial will teach you all this in details. The gRPC method types are:
Page Contents
Once the project is created examine the different project files, these are:
Let us discuss each of these files one by one.
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:
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 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.
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.
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>" } } } } }
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.
Your Startup.cs class comes auto configured for gRPC service. Open this file and see the –
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.
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:
List<Joke> jokeList = JokeRepo(); JResponse jRes = new JResponse(); jRes.Joke.AddRange(jokeList.Skip(request.No-1).Take(1)); return Task.FromResult(jRes);
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 <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
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:
Congrats you now learned how to create Unary gRPC method and call it from your ASP.NET Core application. Coming next are the:
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:
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 –
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:
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);
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:
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:
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:
You can download the full source codes from the below links:
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 -