Playing With gRPC and .NET 6: Client Side
Create a gRPC client in .NET.
Join the DZone community and get the full member experience.
Join For FreeIn my previous article, I focused on how we can create a gRPC server using .NET. Now, I want to show you how we can create a gRPC client using .NET.
In the next image, I mark the items that we will focus on this tutorial.
Prerequisites
I'm using macOS, and the next commands are specific to this OS:
- gRPC compiler: To install Protobuf compiler, you can execute this in a terminal:
brew install protobuf
- .NET 6 SDK: Here you can find the links to download and install the .NET 6 SDK
- Visual Studio Code or IDE of your choice
- grpcurl: A command-line tool that provides interaction with gRPC services
brew install grpcurl
- grpcui: builds on top of gRPCurl and adds an interactive web UI for gRPC, similar to tools like Postman and Swagger UI
brew install grpcui
Steps
In a general manner, we should follow the next steps:
- Create a project.
- Add the gRPC dependencies to the project.
- Add the proto file on your project.
- Register the created proto file and compile the project.
- Implement the business logic.
CountryGrpcClient
that calls to the server to search, create or get a list of countries. The CountryGrpc.proto
file (described below) declares the remote procedures.
1. Create a Project
To create a gRPC template .NET project, open a terminal and execute the next command:
dotnet new console -o grpc.country.client -n CountryGrpcClient
The output is something like this:
Here:
-o
parameter is used to define the project directory name:grpc.country.client
.-n
parameter is used to define the project name:CountryGrpcClient
.
2. Add the gRPC Dependencies to the Project
dotnet add CountryGrpcClient.csproj package Grpc.Net.Client --version '2.47.0'
dotnet add CountryGrpcClient.csproj package Grpc.Tools --version '2.47.0'
dotnet add CountryGrpcClient.csproj package Google.Protobuf --version '3.21.5'
You can see that the entry ItemGroup
in your CountryGrpcClient.csproj
has changed to something like this:
3. Add the Proto File in the Project
In your project directory, create a folder called Protos
with a file CountryGrpc.protos
in it.
mkdir ./Protos
touch ./Protos/CountryGrpc.proto
In this step, I'll use the same proto file created in the previous article in which we created a gRPC server.
Copy the next lines in the CountryGrpc.proto
file created previously.
syntax = "proto3";
/*The Proto file that has Empty message definition*/
import "google/protobuf/empty.proto";
// Defining the namespace in which the generate classes will be
option csharp_namespace = "Sumaris.Grpc.Services";
// The service name will be used by the compiler when generate the base classes
// Here I declare five procedure
service CountryService{
//Server streaming RPC
rpc getAllCountries(google.protobuf.Empty)
returns (stream Country);
// Unitary RPC
rpc listAllCountries(google.protobuf.Empty)
returns ( CountryList);
// Unitary RPC
rpc findCountryByName( FindCountryByNameRequest )
returns (FindCountryByNameResponse);
// Unitary RPC
rpc createCountry (CountryCreateRequest)
returns (CountryCreateRespopnse);
// Bidrectional streaming RPC
rpc findCountriesByNames( stream FindCountryByNameRequest)
returns (stream Country);
}
message Country{
string name=1;
string capitalCity=2;
float area=3;
}
message CountryList{repeated Country country = 1;}
message FindCountryByNameRequest{string name=1;}
message FindCountryByNameResponse{Country country=1;}
message CountryCreateRequest{ Country country=1;}
message CountryCreateRespopnse{bool created=1;}
4. Register the Created Proto File and Compile the Project
Add the next lines in your configuration project file, CountryGrpcClient.csproj
:
<ItemGroup>
<Protobuf Include="./Protos/CountryGrpc.proto" GrpcServices="Client" />
</ItemGroup>
Open a terminal, move into the project, and execute the next command to compile the project:
dotnet build
The build process creates two files, CountryGrpc.cs
and CountryGrpcGrpc.cs
, in the obj/Debug/net6.0/Protos
path. The file CountryGrpcGrpc.cs
contains the class that we will use as a client to interact with the gRPC server.
5. Implement the Business Logic
Open an IDE to edit the Program.cs
file and add the code to call the server:
using System.Threading.Tasks;
using Grpc.Net.Client;
using Sumaris.Grpc.Services;
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
var countryNames = new string[] { "Perú", "Ecuador", "Chile", "Brasil", "Argentina", "Venezuela" };
// See https://aka.ms/new-console-template for more information
using var channel =
GrpcChannel.ForAddress("http://localhost:5000");
CountryService.CountryServiceClient? client =
new CountryService.CountryServiceClient(channel);
// Consume unitary
// rpc listAllCountries(google.protobuf.Empty) returns ( CountryList);
var reply = await client.listAllCountriesAsync(new Empty());
Console.WriteLine("Hello, World!");
Console.WriteLine("Greeting: " + reply.Country);
// Consume stream server
//rpc getAllCountries(google.protobuf.Empty) returns (stream Country);
printServerStream();
// Consume unitary
//rpc findCountryByName(FindCountryByNameRequest ) returns(FindCountryByNameResponse);
// Consume client streaming server streaming
//rpc findCountriesByNames( stream FindCountryByNameRequest) returns (stream Country);
var asyncDuplexStreamingCall = client.findCountriesByNames();
callStreamFromClient(asyncDuplexStreamingCall.RequestStream);
getStreamFromServer(asyncDuplexStreamingCall.ResponseStream);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
async void printServerStream()
{
var reply2 = client.getAllCountries(new Empty()).ResponseStream;
Console.WriteLine("Greeting2: ");
while (await reply2.MoveNext())
{
Console.Write(reply2.Current);
}
Console.WriteLine();
}
async void callStreamFromClient(IClientStreamWriter<FindCountryByNameRequest> request)
{
foreach (var countryName in countryNames)
{
var d = new FindCountryByNameRequest();
d.Name = countryName;
await request.WriteAsync(d);
}
await request.CompleteAsync();
}
async void getStreamFromServer(IAsyncStreamReader<Country> response)
{
await foreach(var p in response.ReadAllAsync())
{
Console.Write($">>{response.Current}");
}
Console.WriteLine("Terminando ...");
}
In the next images, I want to explain a little more about how you consume the remote procedure.
The step marked as 1 lets you create the "client" object. You need only one client to interact with the server.
From this point, you use the "client" to call each one of the remote procedures as you can see in step 2. In this case, the client is calling a server streaming RPC called getAllCountries
. The server sends the data to the client asynchronously and in streaming fashion (step 3). The client reads the data in streaming fashion too until the server finishes the sending (step 4).
Now that we saw how you can call a streaming server gRPC, I will show you how you can call a bidirectional streaming gRPC.
In this case, we use the same "client" object created previously, step 1, and call the remote bidirectional streaming gRPC. In this example, it is findCountriesByName
, which returns an object AsyncServerStreamingCall
(step 2). This object wraps two objects: the RequestStream
that is used to send streaming data to the server (step 3), and the ResponseStream
that is used to get the streaming data returned by the server (step 5).
The server processes each incoming object sent by the client in the transmission stream, applies its business logic, and asynchronously writes its response to the transmission return stream as we can see in step 4. The client reads the incoming responses sent by the server using the IAsyncStreamReader
, as you can see in step 5.
When the client has no more data to send to the server, it must notify the server (red box in step 3), so that the server finishes its process of reading the request asynchronously and can leave the foreach
in step 4. In this case, the server ends its process and notifies the client that there is no more data to read. At this point, the client exits the foreach
in step 5.
This completes bidirectional transmission.
Now that I have shown you a bidirectional streaming gRPC, you can use it to implement a client streaming gRPC.
Conclusion
We use the Protocol buffer files to generate the client implementation in .NET and the async/await to manage streaming server or client. We saw how we can call a streaming server and bidirectional streaming gRPC.
Feel free to let me know if you have any questions or feedback.
Thank you!
Opinions expressed by DZone contributors are their own.
Comments