Microservices
.NET .NET Core ASP.NET Core C# gRPC Visual Studio

How to Implement gRPC Services with .NET Core

Welcome to today’s post.

In today’s post I will be discussing gRPC services and how to implement a basic gRPC service and client using .NET Core.

What is gRPC?

gRPC is an open-source RPC (remote procedure call) framework developed by Google that allows services to connect and communicate remotely.

What is a gRPC service?

A gRPC service is a language-independent technology that allows clients to connect to the service, execute methods, then return responses to the client in a binary format. This is equivalent to the legacy .NET Remoting and WCF Service technologies that were present in the development libraries within the .NET 2.0-.NET 4.7 frameworks. Unlike technologies like Web API and XML Web Services that return formats such as XML SOAP, JSON, and HTML which are larger in size, the gRPC technology produces an optimal, structured, serialized binary format for response messages. gRPC is designed to be an efficient, lightweight technology that works optimally and efficiently to cater for inter-server RPC communication.  

The starting point for creation of gRPC services is from the project templates. In Visual Studio 2022, after selecting the cerate new project menu option from the IDE, search for projects with names containing “rpc”. You will see a result with the name gRPC Service, which is a template for creating a gRPC ASP.NET Core Service project using .NET Core.

I will go through the steps to implement gRPC services in Visual Studio 2022.

These will involve:

  1. Creation of a gRPC .NET Core Service Project in Visual Studio.
  2. Define gRPC ProtoBuf Messages file(s).
  3. Implement a gRPC Service class.
  4. Configuring the gRPC Service.
  5. Implementation of a gRPC .NET client

In the first section, we will start off by creating a gRPC .NET Core Project in Visual Studio.

Creation of a gRPC ASP.NET Core Service Project

In Visual Studio 2022 select the create new project option. The following dialog will open:

I then select the Windows platform option. Next, enter a project/solution name and source folder.

Next select the .NET Framework. I proceed to select .NET 7.0.

When the project skeleton is generated, you will see folders Protos, Services, and a Program.cs file in the Solution Explorer.

In the Connected Services tab, you will notice a Service Reference panel which contains a generated ProtoBuf (Protocol Buffers) messages file (with .proto extension) in the \Protos sub-folder. I will discuss the purpose of the ProtoBuf file a little later.

On expanding the folders in the Solution, you will see the generated ProtoBuf file, greet.proto and a generated service source file, GreeterService.cs.

The generated Protobuf (shorthand for Protocol Buffers) and service class are a starting point for a basic gRPC service.

I will now add my own service class and Protobuf to the project and explain as we go.

The gRPC ProtoBuf Messages file

In the Protos sub-folder I create a new source file, library.proto.

I then add the following code into the source file:

syntax = "proto3";

option csharp_namespace = "GrpcBookLoanService";

package library;

// The library service definition.
service Library {
  // Returns a matching library
  rpc GetLibrary(LibraryRequest) returns (LibraryReply);
}

// The request message containing the library name.
message LibraryRequest {
  int32 library_id = 1;
}

// The response message containing the library details.
message LibraryReply {
  string library_name = 1;
  string library_location = 2;
}

The Protobuf (protocol buffer) file is an interface definition language used to define the service contract, data contract, and messages for our RPC service. 

I will now explain the constituent sections of the Protobuf file.

The syntax keyword is used to set the version of the Protobuf language that is used to define the service. The current version is proto3.

The option csharp_namespace keyword is used to set the namespace for the service.

The package keywordis used the set the name of the service that will be running the RPC service. In this case, I have chosen the name library.

The service keyword is used to define a service interface for the RPC library. A service created by an RPC client as an instance before any of the service methods can be accessed. These are equivalent to WCF Service Contracts. The methods within each service are the equivalent of WCF Operation Contracts.

The message keyword is used to declare messages that the RPC service returns to an RPC client via an RPC method call. Messages are the equivalent of WCF Data Contracts. Each message property parameter has a scalar type (string, bool, int32, float, bytes etc.) and assigned a field number 1,2 … that identifies the position of the field parameter in the binary format of the library.

The gRPC Service class

The LibraryService service class is implemented below:

using Grpc.Core;
using GrpcBookLoanService;

namespace GrpcBookLoanService.Services
{
    public class LibraryService : Library.LibraryBase
    {
        private readonly ILogger<LibraryService> _logger;
        public LibraryService(ILogger<LibraryService> logger)
        {
            _logger = logger;
        }

        public override Task<LibraryReply> GetLibrary(LibraryRequest request, ServerCallContext context)
        {
            string slibrary_name = String.Empty;
            string slibrary_location = String.Empty;

            switch (request.LibraryId)
            {
                case 1:
                    slibrary_name = "NSW State Library";
                    slibrary_location = "Sydney";
                    break;
                case 2:
                    slibrary_name = "Eastwood Library";
                    slibrary_location = "Eastwood";
                    break;
                case 3:
                    slibrary_name = "Chatswood Library";
                    slibrary_location = "Chatswood";
                    break;
                default:
                    slibrary_name = "N/A";
                    slibrary_location = "N/A";
                    break;
            }

            return Task.FromResult(new LibraryReply
            {
                LibraryName = slibrary_name,
                LibraryLocation = slibrary_location
            });
        }
    }
}

The library class is derived from a build-in gRPC base class, Library.LibraryBase. The GetLibrary() method is a unary method call, which is equivalent to a WCF request/reply RPC call.

A unary call has one parameter for each request and response.  

The above diagram shows that after the channel and RPC library connection are established, an initial request starts, and the server library method sends a reply (response) to the client. The request and response messages do not have to be a single parameter and can consist of any number of parameters or more complex nested structures that contain messages within messages.

Configuring the gRPC Service

Configuring the gRPC service is quite straightforward as much of the code for the gRPC ASP.NET Core service is mostly pre-generated. The code looks as follows (omitting some pre-generated comments):

using GrpcBookLoanService.Services;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddGrpc();

var app = builder.Build();

// Configure the HTTP request pipeline.
app.MapGrpcService<LibraryService>();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");

app.Run();

The only change from the generated code is adding the library service to the service collection:

app.MapGrpcService<LibraryService>();

If the above is not added, then the following exception error will be generated as soon as we attempt to call a method in the gRPC service library:

info: Grpc.AspNetCore.Server.Internal.ServerCallHandlerFactory[1]
      Service 'library.Library' is unimplemented.

Another common exception error that occurs is:

No connection could be made because the target machine actively refused it.

This occurs when either the gRPC service is not running or when we specify an invalid port from the gRPC client application.

When the gRPC ASP.NET Core library service is run, the debug console shows two ports, a HTTP and a HTTPS port waiting for in-coming requests.

Now that we have implemented the backend components, I will show how to implement a gRPC client in the next section.

Implementation of a gRPC .NET Client

To make use of a running gRPC service we will require a gRPC client to connect and make request calls to the gRPC service methods. I will show the steps required to implement and configure a basic .NET C# console application into a .NET gRPC client.

Once you have created a skeleton .NET C# console application, open the NuGet package manager and add the following packages:

The CSPROJ file will contain the following references:

  <ItemGroup>
    <PackageReference Include="Google.Protobuf" Version="3.24.0" />
    <PackageReference Include="Grpc.Net.Client" Version="2.55.0" />
    <PackageReference Include="Grpc.Tools" Version="2.57.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
  </ItemGroup>

Next, create a new Proto sub-folder in the project folder, then copy the proto file from the gRPC server project, and paste it into the corresponding Proto sub-folder within the client console application.

Once you have added the proto file to the project, you will see the ProtoBuf reference in the CSPROJ file. It will have the GrpcServices attribute set to “Server”

To ensure the client application ProtoBuf can act as a client, set the GrpcServices attribute as shown:

  <ItemGroup>
    <Protobuf Include="Protos\library.proto" GrpcServices="Client" />
  </ItemGroup>

Save the project file.

The gRPC NuGet tools package auto-generates the client stub interfaces and plumbing code to allow the client to act as an gRPC client, then make calls to a running gRPC service. The generated gRPC client library code is saved in \obj\[Environment]\net7.0\Protos project sub-folder.

To make calls to the gRPC client, we do the following:

  1. Include the import Grpc.Net.Client.
  2. Obtain the channel connection to the port of the gRPC service with GrpcChannel.ForAddress().
  3. Create a client connection to the gRPC service contracts using the channel.
  4. Make a request to the library method.
  5. Read the properties from the response.

We add the following code to Program.cs:

using Grpc.Net.Client;
using GrpcBookLoanService;

Console.WriteLine("Book Loan gRPC Service Client");

var channel = GrpcChannel.ForAddress("http://localhost:5262");
var libraryClient = new Library.LibraryClient(channel);

var response = libraryClient.GetLibrary(new LibraryRequest { LibraryId = 1 });

Console.WriteLine("Library Name: " + response.LibraryName);
Console.WriteLine("Library Location: " + response.LibraryLocation);

After building and running the client application, the debug console will show the output as follows:

The above output shows that the gRPC client has successfully executed a response/reply call to the method GetLibrary() within the running gRPC service.

We have managed to successfully implement and configure an gRPC service and gRPC client. This is the starting point for gRPC services and clients. In a future post I will explore some more complex scenarios for gRPC client and server applications.

That is all for today’s post.

I hope you have found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial