bloggrammer logo

John Ansa

C# .NET | MERN | DevOps | LLMs
Founder @AlgoCollab
Contact
Skills
Share: 

Implementing gRPC Services in .NET

This blog post will walk you through the process of implementing gRPC services in C# .NET, providing a clear, step-by-step approach.


What is gRPC?

Before we get our hands dirty with code, let's grasp the core concepts of gRPC. 

gRPC Service

gRPC (Google Remote Procedure Call) is a high-performance, open-source framework for building internal microservices or distributed systems.

It offers features like bidirectional streaming, multiplexing, language-agnosticism, and efficient communication between services using Protocol Buffers (Protobuf) for data serialization and HTTP/2 for transport.

Protobuf offers better performance and smaller message sizes compared to JSON or XML, making it ideal for communication between services.


The Benefits of gRPC Services

gRPC services offer several advantages over traditional communication protocols like REST.

Here are some of the key benefits:

1. Efficiency: gRPC uses HTTP/2 as its transport protocol, allowing it to multiplex multiple requests over a single connection. This reduces latency and improves efficiency, especially for applications with a large number of small requests.

2. Performance: With HTTP/2's binary framing, gRPC reduces the amount of data transmitted between clients and servers, resulting in faster communication compared to text-based protocols like REST, which use JSON or XML.

3. Language Agnostic: gRPC supports multiple programming languages, including but not limited to C++, C#, Java, Go, Python, and JavaScript. This enables developers to build polyglot applications where different services can be written in different languages, enhancing flexibility and allowing teams to leverage their expertise in various languages.

4. Strong Typing: gRPC uses Protocol Buffers (protobuf) as its default interface description language. Protobuf offers a concise and language-neutral way to define the data format for requests and responses. This ensures type safety and reduces the chance of errors during communication between server and client.  Additionally, protobuf's binary format is more compact and efficient than text-based formats like JSON or XML. 

5. Streaming Support: gRPC supports both unary (single request, single response) and streaming RPCs (multiple requests or responses over a single connection). This enables developers to implement efficient communication patterns such as server streaming (one request, multiple responses) and bidirectional streaming (both client and server can send multiple messages independently).

6. Error Handling: gRPC provides rich support for error handling, including standard status codes and metadata, making it easier for developers to diagnose and troubleshoot issues in distributed systems.

7. Security: gRPC supports transport-level security (TLS/SSL) by default, ensuring that communication between clients and servers is encrypted and secure. It also provides pluggable authentication mechanisms, allowing developers to integrate with existing identity providers such as OAuth or JWT.

8. Code Generation: gRPC generates client and server code from protobuf service definitions, reducing boilerplate code and making it easier to maintain consistency between different parts of an application.

9. Interoperability: While gRPC is primarily associated with HTTP/2, it can also be used with other transport protocols such as TCP or WebSocket. This allows gRPC services to communicate with existing systems or devices that may not support HTTP/2.


How gRPC Works

Here's a detailed overview of how gRPC works:

gRPC Architecture
Image Credit: DotNetCurry

Server Initialization:
The server initializes and starts listening for incoming connections on a specific network address (e.g., IP address and port).

Service Registration:
The server registers one or more gRPC services, each containing one or more RPC methods, defined in .proto files. These services define the operations that clients can invoke remotely.

Client Connection:
When a client wants to interact with the server, it establishes a connection to the server's network address using a gRPC client stub. This stub abstracts the underlying networking details and provides a convenient interface for making RPC calls.

RPC Invocation:
The client invokes a RPC method on the stub, passing any required parameters. This invocation triggers the client-side serialization of the request message into binary format, as defined by the Protocol Buffers schema.

Transport via HTTP/2:
The serialized request message is sent over an HTTP/2 connection to the server. HTTP/2 provides features like multiplexing, header compression, and flow control, enabling efficient communication between the client and server.

Server Reception:
The server receives the incoming request message and performs the necessary deserialization to extract the request parameters. It then routes the request to the appropriate RPC handler method based on the method name specified in the request.

Execution of RPC Method:
The server executes the server-side implementation of the invoked RPC method, which performs the required business logic or data processing. This method may interact with external resources, perform computations, or access databases as needed.

Response Generation:
Once the server-side method execution is complete, the server generates a response message based on the method's return value (if any) and any data it needs to return to the client. This response message is serialized into a binary format using Protocol Buffers.

Transmission to Client:
The serialized response message is sent back to the client over the same HTTP/2 connection. Again, HTTP/2 features like multiplexing ensure efficient transmission of the response alongside other concurrent requests.

Client Reception:
The client receives the response message and deserializes it back into its original format using the Protobuf schema. If the RPC method returns a value, the client-side stub provides this value to the application code that invoked the RPC.

Completion of RPC Call:
The client-side stub notifies the application code that the RPC call has been completed. If the call was successful, the application can now use the returned data or perform any necessary error handling if the call failed.


How to Implement gRPC Services in C# .NET - A Step-by-Step Guide

To begin, ensure you have the necessary tools installed. You'll need the latest version of Visual Studio and the .NET SDK.

gRPC Service

 

Prerequisites:

Creating a gRPC Service in Visual Studio 2022

Step 1: Open Visual Studio

create new project visual studio

 

Step 2: Choose the Project Template

grpc project template

 

Step 3: Name Your Project

Step 4: Choose the Project Location

set grpc project name

 

Step 5: Create the Project

select-framework-visual-studio

 

Let's Build the Arithmetic Service

We'll create a basic gRPC service that performs arithmetic calculations. Here's a breakdown of the steps involved:

1. Define the Service Contract (Proto file):

Define your gRPC service using Protobuf. Protocol Buffers (Protobuf) define the structure of data exchanged between gRPC services.

Right-click on the Protos folder and click the Add. Then click on Add >> New Item (or press Ctrl + Shift + A on Windows or Cmd + Shift + A on Mac).

add-new-item-visual-studio

Create a new .proto file (arithmetic.proto) in your project inside the Protos folder and define your service message types.

grpc-proto

 

grpc-proto

For our arithmetic service, we'll define a AddRequest,   AddResponse,  SubtractRequest,   SubtractResponse message, along with a Arithmeticthat contains our calculation methods.


  
  // arithmetic.proto

  syntax = "proto3";

  package arithmetic;
  
  service Arithmetic {
    rpc AddAsync(AddRequest) returns (AddResponse);
    rpc SubtractAsync(SubtractRequest) returns (SubtractResponse);
  }
  
  message AddRequest {
    int32 a = 1;
    int32 b = 2;
  }
  
  message AddResponse {
    int32 result = 1;
  }
  
  message SubtractRequest {
    int32 a = 1;
    int32 b = 2;
  }
  
  message SubtractResponse {
    int32 result = 1;
  }

In Protocol Buffers (Protobuf) syntax, field numbers are assigned to each field within the message types. These field numbers are used by Protobuf to serialize and deserialize messages efficiently. See here for more details

Also Read: Protobuf Best Practices

Step 2: Generate the gRPC Service Stub

Once you've defined your service contact in the .proto file, use the Protobuf compiler (protoc) to generate C# classes from the .proto file. These generated classes represent your service and message types in C#. 

We'll generate the gRPC service stub using Visual Studio build action.

So right-click on the arithmetic.proto file and click on the Properties (or press Alt + Enter).

 

grpc-proto

Set the following properties:

grpc-proto

 

grpc-proto

Build the project to generate C# classes from the .proto file.

The generated C# code can be found by navigating to the definition of Arithmetic.ArithmeticBase

Step 3: Implement the gRPC Service

Create a new C# class (e.g., ArithmeticService) that inherits from the generated service stub, ArithmeticBase class, and overrides the methods defined in your arithmetic.proto file.

Implement the arithmetic calculation logic in these methods.

grpc-methods
// ArithmeticService.cs

  public class ArithmeticService : Arithmetic.ArithmeticBase
  {
      public override Task<AddResponse> AddAsync(AddRequest request, ServerCallContext context)
      {
          var result = request.A + request.B;
          return Task.FromResult(new AddResponse { Result = result });
 
      }
 
      public override Task<SubtractResponse> SubtractAsync(SubtractRequest request, ServerCallContext context)
      {
          var result = request.A - request.B;
          return Task.FromResult(new SubtractResponse { Result = result });
 
      }
  }

Register the gRPC Service in the Program.cs file.

// Program.cs

using GrpcArithmeticService.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddGrpc(o => {
    o.EnableDetailedErrors = true;
});

var app = builder.Build();

// Configure the HTTP request pipeline.
app.MapGrpcService<ArithmeticService>();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client.");

app.Run();

Let's Build the Arithmetic Client

Step 4: Implement the gRPC Client:

Once your service is up and running, you can create clients to communicate with it.

gRPC provides first-class support for client generation in various languages, including C#. You use the generated client code to make calls to your service from other applications.

You can create a separate project to implement a gRPC client that calls the ArithmeticService methods on the server. The process involves defining a channel to connect to the server, creating a client stub, and calling the desired methods. 

In this tutorial, we'll create a C# .NET Console application to implement the gRPC client.

create-console-app

 

Step 5: Add Required NuGet Packages

The gRPC client project requires the following NuGet packages:

grpc-nuget

 

Step 6: Create a .proto File

You can create a separate .proto file for the client app and define the service contract. The process involves defining the structure of data exchanged between the client and the server, creating a client stub, and calling the desired methods.

Since the arithmetic.proto file definition has to be the same between the server and the client, we will just copy the file from the server project and paste it onto the client project.

grpc-client

After you copy the file and paste it into the GrpcArithmeticClient project, right-click on the file and modify the file gRPC Stub Classes property to Client only

grpc-client2

Click the "Apply" and "OK" button to effect changes.

Step 7: Compile the .proto File

Rebuild the project to generate C# classes from the .proto file on the client-side.

Step 8: Write the Client-side Logic

//Program.cs

  using Grpc.Net.Client;
  using GrpcArithmeticService.Protos;
  
  // The port number must match the port of the gRPC server.
  var channel = GrpcChannel.ForAddress("http://localhost:5275");
  
  var client = new Arithmetic.ArithmeticClient(channel);
  
  var addRequest = new AddRequest() { A = 20, B = 60 };
  var subtractRequest = new SubtractRequest() { A = 320, B = 160 };
  
  var addResponse = client.AddAsync(addRequest);
  var substractResponse = client.SubtractAsync(subtractRequest);
  
  Console.WriteLine($"Addition: {addResponse.Result}");
  Console.WriteLine($"Subtraction: {substractResponse.Result}");
  Console.WriteLine("Press any key to exit...");
  Console.ReadKey();
  
  channel.Dispose();
  await channel.ShutdownAsync();

 

Step 9: Testing and Debugging Your Service:

Testing and debugging are crucial steps in any development process.

grpc-result

There are various tools available for testing gRPC services. You can use tools like gRPCurl and gRPCui in ASP.NET Core to manually test your service endpoints. For debugging, leverage the built-in debugging tools in Visual Studio to step through your code and diagnose any issues.

Conclusion:

Implementing gRPC services in C# .NET offers a powerful and efficient way to build microservices-based applications. By leveraging the simplicity of Protobuf and the performance benefits of gRPC, you can create robust and scalable services that meet the demands of modern distributed systems.

, ,