gRPC is a lightweight, language agnostic, open-source, high-performant RPC framework for building applications that can work on multiple platforms. It takes advantage of Protocol Buffers by default and provides support for a fast, client-server bi-directional communication. gRPC leverages HTTP/2 to maximize transport efficiency and speed, which makes it ideal for use cases such as microservices and cloud applications. In this article, I'll look at the basics of gRPC, including how to create a simple service with gRPC in ASP.NET Core application and consume it from another .NET 6 application.

If you're to work with the code examples illustrated in this article, you must have the following installed on your computer:

  • Visual Studio 2022
  • .NET 6.0
  • ASP.NET 6.0 Runtime

If you don't already have Visual Studio 2022 installed on your computer, you can download it from here: https://visualstudio.microsoft.com/downloads/.

Note that the code snippets and listings shown in this article will work on .NET 7 as well.

What Are We Building Here?

In this article, you'll build two applications: a simple gRPC application that provides a head start to using gRPC in .NET 6 and another application that demonstrates how you can leverage gRPC in an OrderProcessing application. You'll use Protocol Buffers as the serialization format in the first application.

You'll name these two applications as gRPCDemo and gRPCOrderProcessingDemo. The former is composed of two applications, gRPCServerDemo and gRPCCleintDemo, and the latter is composed of two applications named gRPCOrderProcessingServerDemo and gRPCOrderProcessingClientDemo.

Introducing Protocol Buffers

Although gRPC supports multiple serialization formats, the most commonly used is Google's Protocol Buffers, a language and platform-neutral, fast, compact serialization format for serializing data. Protobuf.Net is a .NET library that allows you to serialize and deserialize data in the Google Protocol Buffers format.

You may serialize and deserialize data in the Google Protocol Buffers format using the .NET package protobuf.Net. You can specify the structure of the data you'd like to serialize or deserialize in a .proto file. A .proto file is represented as a text file and can contain one or more proto classes. A typical Protobuf-net class looks like this:

package demo;
message Author {
    required string Code = 1;
    required string FirstName = 2;
    required string LastName = 3;
}

Anatomy of a .proto File

You can take advantage of the protobuf compiler called protoc to generate code that can be used to read and write data using the programming language of your choice. Protobuf is supported by several languages such as C++, Java, C#, Python, Ruby, Objective-C, Go, JS, etc.

Any protobuf file should have a .proto extension. A typical .proto file is structured as follows:

syntax = "proto3";

option csharp_namespace = "GrpcServiceDemo.Protos";

message Author {
    uint64 id = 1;
    string firstname = 2;
    string lastname = 3;
    bool is_active = 4;
}

The first statement in a .pro file specifies the version of Protobuf in use. This is followed by an option statement that specifies the name of the namespace. To define your data elements, use the message keyword.

Field Rules

You can specify field rules in a .proto file. The three field rules you can use in a .proto file are required, optional, and repeated. Although the required keyword is used to specify a mandatory data item, the optional keyword specifies whether a data item should hold a value. You've had to specify this information before the data types of each field in the proto2 version. With proto3, this is optional and you can only use the repeated keyword.

Field Types

In a .proto file, the data types of a field can be one of the following:

  • Scalar data types: such as float, int32, int64, uint32, uint64, sint32, sint64, fixed32, fixed64, sfixed32, sfixed6, bool, string, and bytes.
  • Enumeration: enum data types

Note that field names should always be in lowercase.

Field Tags

These are integers that provide a numeric representation of each field in a message. Note that field tags must be unique in a message and they must be integers only.

What Is gRPC?

gRPC is built on the HTTP/2 protocol and serializes data using Protocol Buffers. HTTP/2 uses a single TCP connection for all client-server communications. Several streams, each of which consists of a bidirectional sequence of bytes, may be exchanged back and forth over a connection. Each RPC call in gRPC corresponds to a single stream in HTTP/2.

gRPC is an excellent alternative to REST for developing microservices. As a schema-first remote procedure call (RPC) framework, gRPC uses the HTTP/2 protocol for binary data transmission. gRPC takes advantage of protocol buffers, a binary serialization protocol developed by Google, to serialize and deserialize these messages, as illustrated in Figure 1.

Figure 1: Messages exchanged between a gRPC server and a gRPC client
Figure 1: Messages exchanged between a gRPC server and a gRPC client

It should be noted that gRPC leverages protocol buffers by default as its interface definition language. Protocol Buffer is a binary serialization protocol for fast data exchange. You can generate protocol buffer code from a schema definition language (SDL), which allows gRPC clients and servers to share type information about the messages exchanged over the wire in advance. Hence, you don't need to marshal or un-marshal data between client-server calls, thus improving performance.

gRPC supports two serialization formats, Protocol Buffers and MessagePack, which are both efficient binary formats that are easy to parse and generate at runtime. Many different programming languages are supported by gRPC, including C++, Java, C#, Python, and Ruby.

You can use gRPC to build distributed applications and services. This is done by allowing client applications to directly invoke methods on a server application running on the same or another computer. With gRPC, you define what methods can be called remotely, what arguments they will use, and how they will return information. A gRPC server handles client requests on the server, which implements this interface. On the client, you'll have a stub containing identical methods to the server, as shown in Figure 2.

Figure 2: A gRPC client sends a proto request to the gRPC Server
Figure 2: A gRPC client sends a proto request to the gRPC Server

Creating a .proto File in Visual Studio 2022

Protocol Buffers is a platform-neutral, language-independent, serialization format from Google that can create payloads that are much smaller in size than XML or JSON. To create a .proto file in Visual Studio 2022, follow the steps outlined below:

  1. In Visual Studio 2022, right-click on the Protos solution folder of the GrpcServerDemo project, as shown in Figure 3.
Figure 3: Add a New Item to the project in the Solution Explorer Window
Figure 3: Add a New Item to the project in the Solution Explorer Window
  1. Click Add > New Item…
  2. Select Protocol Buffers File as the project template, as shown in Figure 4.
Figure 4: Add a new Protocol Buffers file to the project
Figure 4: Add a new Protocol Buffers file to the project
  1. Specify a name for your .proto file.
  2. Click Add to complete the process.

This creates a new .proto file in your project.

Communication Patterns in gRPC

In gRPC, a communication pattern describes how messages are encoded and interpreted between the client and server. There are several communication patterns in gRPC, such as the following:

  • Unary RPC: A client submits only one request and receives only one response in unary RPC.
  • Server streaming: A client transmits a request to the server and receives streams of messages in response. The server provides a status message once all data is transmitted.
  • Client-streaming: Unlike server-streaming, client-streaming involves a client sending a stream of messages to a server. After the messages are sent, the client awaits the server's response.
  • Bidirectional-streaming: Bidirectional streaming allows clients and servers to exchange messages in any order.

Implementing gRPC Services in ASP.NET 6 Core

Now that you know the basics of a gRPC service in .NET 6, let's build a simple client-server application that leverages gRPC in .NET 6.

Create a New ASP.NET Core gRPC Service in Visual Studio 2022

You can create a project in Visual Studio 2022 in several ways. When you launch Visual Studio 2022, you'll see the Start window. You can choose “Continue without code” to launch the main screen of the Visual Studio 2022 IDE.

To create a new ASP.NET 6 Project in Visual Studio 2022:

  1. Start the Visual Studio 2022 IDE.
  2. In the Create a new project window, select gRPC Service from the list of available project templates and then click Next to move on, as illustrated in Figure 5.
Figure 5: Create a new project in Visual Studio 2022
Figure 5: Create a new project in Visual Studio 2022
  1. Specify the project name as GRPCServiceDemo and the path where it should be created in the Configure your new project window.
  2. If you want the solution file and project to be created in the same directory, you can optionally check the Place solution and project in the same directory checkbox. Click Next to move on.
  3. In the next screen, specify the target framework and leave the checkboxes unchecked because you won't use any of these in this example.
Figure 6: Specify additional information for your project.
Figure 6: Specify additional information for your project.
  1. Click Create to complete the process.

A new ASP.NET Core gRPC project is created. At a minimum, a gRPC service consists of two files: a code file for implementing the service and a .proto file for describing it. Figure 7 shows the solution structure of the gRPCServiceDemo project.

Figure 7 : The solution structure of the GrpcServiceDemo application
Figure 7 : The solution structure of the GrpcServiceDemo application

Note that because you've selected a gRPC project template, the following NuGet packages will be installed by default into your project:

  • Grpc.Tools: The Grpc.Tools package contains types required to provide protobuf file tooling support.
  • Grpc.AspNetCore.Server.ClientFactory: This package supports the integration of HTTPClientFactory for gRPC .NET clients in ASP.NET Core applications.
  • Google.Protobuf: Using the Google.Protobuf package, you can create protobuf files using the protobuf APIs.

You can also see that two solution folders named Protos and Services are created by default. At the same time, the Protos solution folder contains a greet.proto file by default, and the Services solution folder has a gRPC service named GreeterService created in it by default.

Listing 1 shows the complete source code of the greet.proto file in the server project.

Listing 1: The greet.proto file in the server project

syntax = "proto3";

option csharp_namespace = "GrpcServiceDemo";

package greet;

service Greeter {
    rpc SayHello (HelloRequest)
    returns (HelloReply);
}
message HelloRequest {
    string name = 1;
}

message HelloReply {
   string message = 1;
}

You can find the source code of the GreeterService in Listing 2.

Listing 2: The GreeterService Class

namespace GrpcServiceDemo.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)
        {
            return Task.FromResult(new HelloReply
            {
                Message = "Hello " + request.Name
            });
        }
    }
}

Create a gRPC Client Project in Visual Studio 2022

Let's create a console application project that you'll use for benchmarking performance. You can create a project in Visual Studio 2022 in several ways. When you launch Visual Studio 2022, you'll see the Start window. You can choose Continue without code to launch the main screen of the Visual Studio 2022 IDE.

To create a new Console Application Project in Visual Studio 2022:

  1. Start the Visual Studio 2022 IDE.
  2. In the Create a new project window, select Console App and click Next to move on.
  3. Specify the project name as gRPCClientDemo and the path where it should be created in the Configure your new project window.
  4. If you want the solution file and project to be created in the same directory, you can optionally check the Place solution and project in the same directory checkbox. Click Next to move on.
  5. In the next screen, specify the target framework you would like to use for your console application.
  6. Click Create to complete the process.

You'll use this application in the subsequent sections of this article.

Install NuGet Package(s)

The next step is to install the necessary NuGet Package(s). To install the required packages into your project, right-click on the solution and the select Manage NuGet Packages for Solution…. Now search the Grpc.Net.Client, Google.Protobuf, and Grpc.Tools packages in the search box and install them. Alternatively, you can type the commands shown below at the NuGet Package Manager command prompt:

PM> Install-Package Grpc.Net.Client
PM> Install-Package Google.Protobuf
PM> Install-Package Grpc.Tools

You can also add the packages from the Visual Studio 2022 command prompt using the following commands:

dotnet add gRPCClientDemo.csproj package Grpc.Net.Client
dotnet add gRPCClientDemo.csproj package Google.Protobuf
dotnet add gRPCClientDemo.csprojpackage Grpc.Tools

Consume the gRPC Service

To consume the gRPC service you've created, follow the steps given below:

  1. Copy the Protos folder from the gRPCServiceDemo project you created earlier to the gRPCClientDemo project.
  2. Update the namespace of the project in the .proto file, as shown in the code snippet:
option csharp_namespace = "GrpcClientDemo";
  1. Add the following statement inside the item group element in the project file of the client project:
<Protobuf Include="Protos\greet.proto" GrpcServices="Client"/>

The complete source code of the greet.proto file in the client project looks like this:

syntax = "proto3";

option csharp_namespace = "GrpcClientDemo";
package greet;

service Greeter {
    rpc SayHello (HelloRequest)
    returns (HelloReply);
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

Write the code given in Listing 3 in the Program.cs file of the client project.

Listing 3: Program.cs file of the client project

using Grpc.Net.Client;
using GrpcClientDemo;

var data = new HelloRequest { Name = "Joydip" };
var grpcChannel = GrpcChannel.ForAddress("https://localhost:7135");
var client = new Greeter.GreeterClient(grpcChannel);
var response = await client.SayHelloAsync(data);
Console.WriteLine(response.Message);
Console.ReadLine();

Execute the gRPC Server and Client Applications

When you execute both the gRPC server and client applications, two command windows will be launched: one for the server and the other for the client, as shown in Figure 8.

Figure 8: The gRPC Server and client applications in execution
Figure 8: The gRPC Server and client applications in execution

Using gRPC in a Real-life ASP.NET 6 Application

In this section, you'll examine how you can leverage gRPC in a real-life application. To keep things simple and easy to understand, you'll implement just one endpoint in this example. Because you'll be using ASP.NET 6 here again, you'll reuse the application you created earlier.

Create the .proto Files

To create a new .proto file, follow these steps:

  1. In the Solution Explorer window of the GrpcServiceDemo project, right-click on the Protos folder.
  2. Click Add-> New Item…
  3. Select Protocol Buffer File from the list of the templates displayed.
  4. Specify the name of the file as orders.proto.
  5. Click Add to complete the process.

Now replace the default generated code of the orders.proto file with the source code given in Listing 4.

Listing 4: The orders.proto file

syntax = "proto3";

option csharp_namespace = "GrpcServiceDemo";

package orders;

service OrderProcessing {
    rpc GetOrder (OrderRequest)
    returns (OrderResponse);
}

message OrderRequest {
    int32 order_id = 1;
}

message OrderResponse {
    Order order = 1;
}

message Order {
    int32 order_id = 1;
    int32 order_quantity = 2;
    double unit_price = 3;
    string ship_address = 4;
    string ship_city = 5;
    string ship_postal_code = 6;
}

Create the Repository Class

You'll take advantage of the Repository design pattern here to create a Repository class. The OrderRepository class will be implementing the IOrderRepository interface.

Create a new interface named IOrderRepository and replace the default code with the following code:

namespace GrpcServiceDemo.Repositories
{
    public interface IOrderRepository
    {
        public Task<Order> GetOrder();
    }
}

The OrderRepository class implements this interface, as shown in Listing 5.

Listing 5: The OrderRepository class

namespace GrpcServiceDemo.Repositories
{
    public class OrderRepository : IOrderRepository
    {
        public async Task<Order> GetOrder ()
        {
            return await Task.FromResult(new Order
            {
                OrderId = 1,
                ShipAddress = "Banjara Hills",
                ShipCity = "Hyderabad",
                ShipPostalCode = "500034",
                OrderQuantity = 100
            });
        }
    }
}

Build the OrderService Class

The OrderService takes advantage of the OrderRepository to return data. To keep things simple, I've used one Order record only. To create the OrderService class, right-click on the Services solution folder in the GrpcServiceDemo project, click Add > Class… to create a new .cs file named OrderService.cs and write the source code shown in Listing 6 in there.

Listing 6: The OrderService class

using Grpc.Core;
using GrpcServiceDemo;
using GrpcServiceDemo.Repositories;

namespace GrpcServiceDemo.Services
{
    public class OrderService : OrderProcessing.OrderProcessingBase
    {
        private readonly ILogger<OrderService> _logger;
        private readonly IOrderRepository _orderRepository;
        public OrderService (ILogger<OrderService> logger,
          IOrderRepository orderRepository)
        {
            _logger = logger;
            _orderRepository = orderRepository;
        }

        public override Task <OrderResponse> GetOrder
          (OrderRequest request, ServerCallContext context)
        {
            return Task.FromResult(new OrderResponse
            {
                Order = _orderRepository.GetOrder().Result
            });
        }
    }
}

Configure the OrderService Class

You can add gRPC services to the services container using the following code in the Program.cs file:

builder.Services.AddGrpc();

To configure the HTTP request pipeline and map the incoming requests to the OrderService type, you can write the following code snippet in the Program.cs file:

app.MapGrpcService<OrderService>();

The complete source code of the Program.cs file of the GrpcServiceDemo project is given in Listing 7 for your reference.

Listing 7: The Program.cs file of GrpcServiceDemo project

using GrpcServiceDemo.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddGrpc();
var app = builder.Build();

app.MapGrpcService<OrderService>();
app.MapGet("/", () => "Hello World!");
app.Run();

Consume the Order Service

Copy the orders.proto file from the Protos solution folder of the GrpcServiceDemo project to the Protos solution folder of the GrpcClientDemo project. Update the namespace in the orders.proto file of the client project similar to how you updated the namespace of the .proto file in the client project earlier.

Write the source code shown in Listing 8 in the Program.cs file of the client project to connect to the OrderService and invoke the GetOrderAsync method.

Listing 8: The Program.cs file of the GrpcClientDemo project

using Grpc.Net.Client;
using GrpcClientDemo;

var data = new OrderRequest{ OrderId = 1 };
var grpcChannel = GrpcChannel.ForAddress("https://localhost:7135");
var client = new OrderProcessing.OrderProcessingClient(grpcChannel);
var response = await client.GetOrderAsync(data);
Console.WriteLine("Order Quantity: {0}", response.Order.OrderQuantity);
Console.ReadLine();

Execute the Order Processing Server and Client Applications

When you execute the Order Processing Server and Client applications, the output will be similar to Figure 9.

Figure 9: Displaying the order quantity of the order having OrderId as 1
Figure 9: Displaying the order quantity of the order having OrderId as 1

The GetOrderAsync is called from the client application passing the value 1 as the OrderId. This method returns the OrderQuantity of the Order having OrderId as 1. The returned value is then displayed in the console window.

Create Integration Tests to Test the Order gRPC Service

Integration tests are used to test an application on a much broader or wider scope than the unit tests. Although the unit tests are typically used to test snippets of code in your application, integration tests are used to test the application together with its components.

First off, you should define a fixture that contains common code that is shared by your test methods. You can define your TestServer for running the integration tests here. Now create a new xUnit project in Visual Studio 2022 and change the name of the default test file to OrderServiceTests.cs.

You can now take advantage of the source code shown in Listing 9 to test the GetOrderAsync endpoint. Note that you can define the GrpcChannel and the client instances in a Fixture as well.

Listing 9: The GetOrderAsyncTest method

[Fact]public async Task GetOrderAsyncTest()
{
    // Arrange
    var data = new OrderRequest { OrderId = 1 };
    var client = new OrderProcessing.OrderProcessingClient(grpcChannel);

    // Act
    var response = await client.GetOrderAsync(data);

    // Assert
    Assert.NotNull(response);
}

Conclusion

In this example, you've used an ASP.NET 6 Core application to build the gRPC server and a .NET 6 Console client to consume data. You can use ASP.NET 6, Blazor, etc., as the client, i.e., the consumer, as well. Note that gRPC is several times faster than REST, due to which, it's a great choice for inter-service communication in microservice architectures primarily because it's high-performant.