Introduction
gRPC is an open source remote procedure call system developed at Google in 2015. It uses HTTP/2 to transport binary messages and by default protocol buffers as Interface Definition language (IDL) for describing service interface and the structure of messages.
// The greeter 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;
}
gRPC defines four types of service methods.
- Unary RPCs where client send a single request to the server and get a single response back.
- Server streaming RPCs where client sends request to the server and gets a stream to read a sequence of messages back.
- Client Streaming RPCs where client writes a sequence of messages and sends them to the server, again using a provided stream.
- Bidirectional Streaming RPCs where both sides send a sequence of messages using read-write stream.
If we compare gRPC to Web API , following are the differences.
- Web API is based on REST architecture where as gRPC proposes RPC model , a model where as client invokes remote procedure on the server.
- Web API uses HTTP for transport whereas gRPC uses HTTP/2.
- Data exchanged by Web API is human readable format(typically JSON), while gRPC uses compact binary format.
Prerequisites
Before creating gRPC Services make sure .NET Core 3.1 SDK is installed on your local, this can be checked by typing command : 'dotnet --version' in console window. If it's not installed, download Download .NET Core 3.1 (Linux, macOS, and Windows) (microsoft.com) and install on your machine.
Creating a gRPC Service
The application we are going to build is a microservice that calculates a discount based on the type of customer -- gold or platinum or silver -- which can be further extended. Start by creating a new folder, grpc-dotnet-microservice, and adding both client and service applications.
Navigate to this folder and create the server project by typing the following command in the console window,
The above command creates a new sample .NET Core gRPC project in folder created CalculateDiscountService.
Defining the Contract
The first step is to define the contract which is an interface that tell you the functionality or functions exposed by the service. In gRPC framework this interface is defined through Protocol buffer or protobuf. In particular this interface is defined in .proto file.
So move to ~\CalculateDiscountService\Protos folder , rename the default proto file to discount-calculate-service.proto file and do the following changes.
syntax = "proto3";
option csharp_namespace = "CalculateDiscountService";
package CalculateDiscount;
service CalculateDiscountAmount {
rpc AmountCalculate (CalculateRequest) returns (CalculateReply);
}
message CalculateRequest {
string customertype = 1;
}
message CalculateReply {
double customerdiscount = 1;
}
Let's understand line by line. The first two rows tell the syntax of proto buff version in use and C# namespace. The next line tells package name in this case = CalculateDiscount.
Next block of code is similar in C# as follows,
public abstract class CalculateDiscountAmount {
public abstract CalculateReply AmountCalculate(CalculateRequest calculateRequest);
}
Here CalculateReply is return type, CalculateRequest is input parameter, AmountCalculate is an abstract function and CalculateDiscountAmount is C# class. In proto file the CalculateRequest and CalculateReply messages' unique number is assigned in fields defined inside, this orders the order of data when serialization and deserialization is done by protocol buffer.
Once the contract is defined, you need to make sure the application is aware of the new proto file. Update the CalculateDiscountService.csproj file, below.
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Protobuf Include="Protos\discount-calculate-service.proto" GrpcServices="Server" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.27.0" />
</ItemGroup>
</Project>
Here .proto file name is specified along with GrpcServices attribute set to 'Server'.
Service Implementation
Navigate to services folder, rename the GreeterService.cs file to CalculateDiscountService.cs and replace the contents with below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Extensions.Logging;
namespace CalculateDiscountService
{
public class CalculateDiscountAmountService : CalculateDiscountAmount.CalculateDiscountAmountBase
{
private readonly ILogger<CalculateDiscountAmountService> _logger;
public CalculateDiscountAmountService(ILogger<CalculateDiscountAmountService> logger)
{
_logger = logger;
}
public override Task<CalculateReply> AmountCalculate(CalculateRequest request, ServerCallContext context)
{
return Task.FromResult(new CalculateReply
{
Customerdiscount = ReturnDiscount(request.Customertype)
});
}
private double ReturnDiscount(string customertype)
{
double discount = 0.0;
if (customertype == "GOLD")
{
discount = 15.6;
}
else if (customertype == "PLATINUM")
{
discount = 20.6;
}
else if (customertype == "DIAMOND")
{
discount = 25.6;
}
return discount;
}
}
}
Here we are implementing the CalculateDiscountAmountService class which inherits from CalculateDiscountAmount.CalculateDiscountAmountBase class. This base class is generated from data contained in .proto file ( discount-calculate-service.proto ) at build time.
It contains AmountCalculate function which is implementation of rpc definition in .proto file and CalculateReply and CalculateRequest types as return type and request parameter defined as message types. Also update Startup.cs file to map CalculateDiscountAmountService class as gRPC service, below,
Now navigate to root folder of project, run the project by typing command in command prompt 'dotnet run',
The above command will start the gRPC service. Now to interact with this service we will be creating a console client application.
Creating a gRPC Client
Navigate to folder `\grpc-dotnet-microservice and create a new project by typing the following command in cmd,
The above command will create a console application in folder created DiscountCalculateClient. Now move to this folder and add the required dependencies by typing the below commands,
dotnet add DiscountCalculateClient.csproj package Grpc.Net.Client
dotnet add DiscountCalculateClient.csproj package Google.Protobuf
dotnet add DiscountCalculateClient.csproj package Grpc.Tools
Grpc.Net.Client library for .NET core client, Google.Protobuf - C# runtime library for managing protocol buffers and Grpc.Tools - complier that converts proto files into C# code.
Now client application needs to be aware of details about how to invoke server application, what parameters to pass and what will be the possible return type.
This is done by adding the already defined proto file. First create a proto folder inside DiscountCalculateClient and copy the discount-calculate-service.proto from gRPC service and update the DiscountCalculateClient.csproj project file by adding the reference of .proto file.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.14.0" />
<PackageReference Include="Grpc.Net.Client" Version="2.33.1" />
<PackageReference Include="Grpc.Tools" Version="2.34.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Protobuf Include="Protos\discount-calculate-service.proto" GrpcServices="Client" />
</ItemGroup>
</Project>
Note here Protobuf element has GrpcServices attribute set to client. Now to call gRPC service from client , edit the Program.cs file,
using System;
using CalculateDiscountService;
using Grpc.Net.Client;
using System.Net.Http;
namespace DiscountCalculateClient
{
class Program
{
static void Main(string[] args)
{
var httpHandler = new HttpClientHandler();
httpHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
string customerType = "GOLD";
var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions { HttpHandler = httpHandler });
var client = new CalculateDiscountAmount.CalculateDiscountAmountClient(channel);
var request = new CalculateRequest { Customertype = customerType };
var reply = client.AmountCalculate(request);
Console.WriteLine($"Discount for customer type {customerType} is {reply.Customerdiscount}");
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
Here we are creating a channel which points to the address where the service is running along with adding the required configuration to ignore invalid certificate or certificate is not installed and next passed as constructor to gRPC client CalculateDiscountAmountClient class. Finally its function is called by passing required parameter.
Below is the output in client application after launching through command: 'dotnet run',
Some of the new features introduced in .NET 5 for gRPC,
- It is supported on browser with gRPC-Web which makes its compatible with browser API.
- It can be hosted on HTTP.sys and IIS on Windows with supported version of Windows installed.
Conclusion
In this article we discussed about gRPC, its implementation in .NET core 3.1 and new features introduced in .NET 5
Reference