Overview
Today, efficiency, scalability, and interoperability are paramount in modern software development. Enter gRPC, Google's open-source framework for RPCs (Remote Procedure Calls). In this article, we'll delve into the fundamentals of gRPC and explore how it integrates seamlessly with .NET 8 and C# 12 to empower developers in building robust, distributed systems.
What is gRPC?
The gRPC RPC framework, which stands for "gRPC Remote Procedure Call," is a language-agnostic, modern RPC framework that uses HTTP/2 for transport and Protocol Buffers (protobuf) for interface definition. There are several advantages to using gRPC over traditional HTTP APIs, including:
- Efficiency: gRPC reduces latency and bandwidth consumption through HTTP/2 multiplexing, binary serialization with protobuf, and efficient compression.
- Interoperability: It supports multiple programming languages, allowing seamless communication between services written in different languages.
- Strongly Typed Contracts: Protobuf is used to define services, ensuring a clear contract that can be shared across teams and across organizational boundaries.
Key Components of gRPC
Service Definition
gRPC services are defined using Protocol Buffers (protobuf), which define methods that can be called remotely.
Understanding Protocol Buffers (Protobuf)
Protocol Buffers (Protobuf) are the basis of gRPC. They are a language-neutral, platform-neutral, extensible method for serializing structured data. They are smaller, faster, and easier to use than XML or JSON.
Using Protobuf, we define gRPC services and the structure of requests and responses. This definition is contained in .proto files.
Defining gRPC Services
The .proto file contains the definitions of the services and the messages they use. Each service definition specifies the remote procedure calls (RPCs) that the service supports.
We will be creating a folder named Protos in the root folder where the solution file is kept, and in that folder, we will create a file named greet.proto. Since we will be using the Protos folder and greet.proto for both the client and the server, we are creating them in the root folder.
Inside the greet.proto file please copy the code example I have given we below and save it.
syntax = "proto3";
option csharp_namespace = "GreeterServer";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
In this example above we have the:
- Syntax: The syntax = "proto3"; line indicates that the file uses Protobuf version 3.
- Option: csharp_namespace = "MyApp.GRPC"; specifies the generated code's C# namespace.
- Package: The package myapp; line defines the Protobuf package, which avoids naming conflicts.
- Service: A service named Greeter is defined in the service Greeter block, and it has only one RPC method named SayHello.
- Messages: Requests and responses are defined by the HelloRequest and HelloResponse messages.
Remote Procedure Calls (RPCs)
There are several RPCs in the service definition. The above example defines a simple unary RPC.
- Unary RPCs: It is the most common type of RPC to have a client send a single request and receive a single response.
- Server Streaming RPCs: An RPC server streams responses to a client in response to a single request.
- Client Streaming RPCs With Streaming RPCs, the client sends a stream of requests and receives only one response.
- Bidirectional Streaming RPCs: Clients and servers send a stream of messages in bidirectional streaming RPCs.
Generating Code from Protobuf
It is necessary to generate the corresponding code once we have defined the service in the .proto file. The gRPC tools for .NET automatically generate C# classes from the Protobuf definitions, which include:
- Service Base Classes: Server-side logic can be implemented by extending service base classes.
- Client Stubs: Stub classes provide client-side methods for calling RPCs.
- Message Classes: Request and response messages are represented by message classes.
In .NET projects, this is usually handled automatically by the build process using tools like Grpc.Tools, which use the protoc compiler with the appropriate gRPC plugin.
Implementing the Service
Using the generated code, we extend the generated base classes to implement the Greeter service. As the code example below shows us.
using Grpc.Core;
namespace GreeterServer.Services;
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)
{
try
{
var reply = new HelloReply
{
Message = $"Hello, {request.Name}"
};
_logger.LogInformation($"GRPC Request Send and Recived");
return Task.FromResult(reply);
}
catch (Exception ex)
{
_logger.LogError($"Error while handing your request {ex.Message}");
throw;
}
}
}
As a result, this implementation extends the generated GreeterBase class. The SayHello method has been overridden to implement the SayHello RPC on the server side.
Using the Service in a Client Application
We invoke the RPC methods from a client application by creating a gRPC channel and a client stub.
We will need to install the following NuGet Packages
- Grpc.AspNetCore
- Grpc.Net.Client
- Google.Protobuf
- Grpc.Tools
using GreeterServer;
using Grpc.Net.Client;
var channel = GrpcChannel.ForAddress("https://localhost:7273");
var client = new Greeter.GreeterClient(channel);
Console.WriteLine("Hello from Ziggy Rafiq");
Console.WriteLine("Please Enter Your Name:");
string name = Console.ReadLine()?? "Missing Your Name !!";
var response = await client.SayHelloAsync(new HelloRequest{ Name = name });
Console.WriteLine(response);
Console.ReadLine();
In this client code:
- To communicate with the gRPC server, a GrpcChannel is created.
- From the channel, a GreeterClient is created.
- Invoking the SayHello RPC on the server requires the SayHelloAsync method.
In .NET, defining services and messages using Protobuf is a fundamental component of gRPC. By defining these services and messages in .proto files, we can leverage gRPC's powerful features to build efficient, scalable, and cross-platform APIs. In order to perform remote procedure calls, client applications must define services and messages, generate code, and implement the service logic.
gRPC in .NET 8 with C# 12
The .NET 8 framework now supports gRPC natively, making it easier than ever for .NET developers to get started using this modern RPC framework.
Setting Up a gRPC Service
We go to our Visual Studio 2022 click on Create a new project and we search for gRPC template name ASP.net Core gPRC service. This will create for us a boilerplate project which has the following setup for our gRPC Server.
We have the following folders and files
- Protos
- Services
- Program.cs
- Navigate to "Connected Services" by right-clicking on "Add" and selecting "Connected Service."
- In the "Connected Services" dialog, choose "Service References (OpenAPI, gRPC, WCF Web Service)" from the second section.
- Click the green plus icon, select "gRPC," and click "Next."
- Choose the radio button for "File," then click the browse button.
- Navigate to the "Protos" folder in the root directory (not the project folder) and select the "greet.proto" file.
- Ensure to select "Server" from the drop-down list to connect with your proto file.
Implementing the Server
We now go to our project name GreeterServer and we go to the folder name Services and open up the file name GreeterService.cs and we copy the following code below.
using Grpc.Core;
namespace GreeterServer.Services;
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)
{
try
{
var reply = new HelloReply
{
Message = $"Hello, {request.Name}"
};
_logger.LogInformation($"GRPC Request Send and Recived");
return Task.FromResult(reply);
}
catch (Exception ex)
{
_logger.LogError($"Error while handing your request {ex.Message}");
throw;
}
}
}
Make sure the following NuGet Packages are installed on the Server project. By default it should installed however there are times when it is not.
- Grpc.AspNetCore
- Grpc.AspNetCore.Server
We have now created a simple gRPC Server Project and we build it and now we look at building the gRPC client project.
Creating a gRPC Client
In this section we will create a simple Console Application and install the following NuGet Packages in our Console Application.
- Google.Protobuf
- Grpc.AspNetCore
- Grpc.Net.Client
- Grpc.Net.ClientFactory
- Grpc.Tools
We also need to create a folder name Protos and follow the following steps below to add the reference to the greet.proto file.
- Navigate to "Connected Services" by right-clicking on "Add" and selecting "Connected Service."
- In the "Connected Services" dialog, choose "Service References (OpenAPI, gRPC, WCF Web Service)" from the second section.
- Click the green plus icon, select "gRPC," and click "Next."
- Choose the radio button for "File," then click the browse button.
- Navigate to the "Protos" folder in the root directory (not the project folder) and select the "greet.proto" file.
- Ensure to select "Server" from the drop-down list to connect with your proto file.
We will now copy the code below that I have given here which will allow us to connect to the gRPC Server and we then need to set up our solution to run multiple projects selecting both of our projects GreeterServer and GreeterClient.
using GreeterServer;
using Grpc.Net.Client;
var channel = GrpcChannel.ForAddress("https://localhost:7273");
var client = new Greeter.GreeterClient(channel);
Console.WriteLine("Hello from Ziggy Rafiq");
Console.WriteLine("Please Enter Your Name:");
string name = Console.ReadLine()?? "Missing Your Name !!";
var response = await client.SayHelloAsync(new HelloRequest{ Name = name });
Console.WriteLine(response);
Console.ReadLine();
Benefits of Using gRPC
Here are some of the key advantages of gRPC that make it an appealing choice for building efficient, scalable, and robust distributed systems:
Performance
Binary Serialization
Binary serialization with Protocol Buffers (Protobuf) is one of the unique features of gRPC. Binary serialization is more compact and faster to parse than text-based serialization formats like JSON or XML. Therefore, payload sizes will be reduced and communication between services will be faster.
Compact Message Format: A protobuf message is smaller because it is serialized in a binary format, which eliminates the need for verbose tags and data representations used in text-based formats.
Faster Parsing: Compared to JSON or XML, the binary format allows for faster serialization and deserialization since it requires fewer computational resources.
HTTP/2 Multiplexing
The HTTP/2 transport protocol used by gRPC offers several performance advantages over HTTP/1.1.
- Multiplexing: Over the same connection, HTTP/2 supports multiplexing, which allows multiple messages to be sent and received simultaneously. By reducing overhead associated with setting up and tearing down connections, latency is reduced and network resources are better utilized.
- Header Compression: HTTP/2's header compression reduces HTTP header sizes, further optimizing communication.
- Server Push: Through HTTP/2's server push feature, resources can be sent proactively to the client, potentially reducing gRPC round trips.
gRPC's performance optimizations make it an excellent choice for high-performance, low-latency communication in microservice architectures and other distributed systems.
Strongly Typed Contracts
Protocol Buffers (Protobuf)
In gRPC, the service contracts are defined using Protocol Buffers (Protobuf), which provides type safety, maintainability, and clarity.
- Type Safety: As a result of Protobuf's type safety, clients and servers can validate the structure of messages at compile time, reducing runtime errors.
- Clear Definitions: The .proto files used in gRPC define the service methods and the structure of the request and response messages explicitly, and serve as a contract between the services, making it easier to understand and maintain.
- Automatic Code Generation: By automatically generating client and server code from .proto definitions, gRPC tools ensure consistency between services and reduce the amount of boilerplate code developers need to write.
Example of a .proto file:
syntax = "proto3";
option csharp_namespace = "GreeterServer";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Interoperability
Multi-language Support
This allows teams to choose the best tools and languages for their specific needs without worrying about integration concerns. gRPC supports a wide range of programming languages.
- Polyglot Development: Teams can develop different components of their system using different languages. For example, a C# service can easily communicate with a Go or Python service using gRPC.
- Standardized Communication: In addition to ensuring consistent message encoding and decoding across different languages, gRPC uses Protobuf for message serialization. By standardizing data formats, the integration process becomes simpler and potential issues related to data format discrepancies are reduced.
- Rich Ecosystem :The gRPC ecosystem includes not only the core gRPC libraries for different languages, but also plugins and extensions for authentication, load balancing, and monitoring tasks.
In terms of performance, strongly typed contracts, and interoperability, gRPC is a great choice for building modern, efficient, and scalable distributed systems. Using binary serialization and HTTP/2, gRPC ensures fast, resource-efficient communication. Protocol Buffers ensure that service definitions are clear, maintainable, and type-safe. Finally, its language-agnostic nature allows for seamless integration across different programming environments, allowing teams to choose the best tools for their specific needs. Combined, these features contribute to the increasing adoption of gRPC for APIs and microservices.
Summary
Modern, scalable microservices and distributed systems can be built using gRPC in .NET 8 and C# 12. In addition to its efficient communication, strong typing, and cross-language support, it is an ideal choice for developers seeking to optimize performance and interoperability.
Take advantage of the power of gRPC and unleash new possibilities in our .NET applications regardless of whether we explore microservices architecture or enhance our existing distributed systems.
I have uploaded this article's source code on my GitHub Repository https://github.com/ziggyrafiq/grpc-dotnet8-csharp12 please do not forget to follow me on LinkedIn https://www.linkedin.com/in/ziggyrafiq/ as your support means the world to me and if you have found this article useful please click the like button also I will be writing further articles on gRPC with .net with more complex examples and will be sharing with you best practices and standards in building the gRPC services and will be explaining in detail where we can use them and how.