Zhangpeng Chen, Technical Lead

avatar

gRPC services with .NET Core 3.1

The term "Microservice Architecture" has sprung up over the last few years to describe a particular way of designing software applications as suites of independently deployable services.

When we build multiple microservices with different technologies and programming languages, it is important to have a standard way to define service interfaces and underlying message interchange formats. gRPC offers a clean and powerful way to specify service contracts using protocol buffers. Therefore, gRPC is probably the most viable solution for building communication between internal microservices.

This article shows how to get started with gRPC services using .NET Core 3.1.

Prerequisites

Create a new gRPC service

The best place to start using gRPC for .NET Core 3.1 is the gRPC template that comes with .NET Core 3.1.

In Visual Studio Code, you can open an integrated terminal, run the following command:

dotnet new grpc --name ProductService

The command creates a new gRPC service in the ProductService folder, with a sample in it.

Run the created gRPC service

In the terminal, run the following command:

dotnet run

This command provides a convenient option to run the application from the source code with one command.

But unfortunately an error has occurred: HTTP/2 over TLS is not supported on macOS due to missing ALPN support.

To work around this issue, configure Kestrel and the gRPC client to use HTTP/2 without TLS. You should only do this during development. Not using TLS will result in gRPC messages being sent without encryption.

Kestrel must configure an HTTP/2 endpoint without TLS in `Program.cs`:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    	Host.CreateDefaultBuilder(args)
    		.ConfigureWebHostDefaults(webBuilder =>
    		{
    			webBuilder.ConfigureKestrel(options =>
    			{
    				// Setup a HTTP/2 endpoint without TLS.
    				options.ListenLocalhost(5001, o => o.Protocols = HttpProtocols.Http2);
    			});
    
    			webBuilder.UseStartup<Startup>();
    		});

In the terminal, run the following command again:

dotnet run

You should see the following result.

Examine the project files

  • 'greet.proto' – The 'Protos/greet.proto' file defines the 'Greeter' gRPC and is used to generate the gRPC server assets. For more information, see Introduction to gRPC.
  • 'Services' folder: Contains the implementation of the 'Greeter' service.
  • 'appSettings.json' – Contains configuration data, such as protocol used by Kestrel. For more information, see Configuration in ASP.NET Core.
  • 'Program.cs' – Contains the entry point for the gRPC service. For more information, see .NET Generic Host.
  • 'Startup.cs' – Contains code that configures app behavior. For more information, see App startup.

The sample project is well explained in the following page: https://docs.microsoft.com/en-us/aspnet/core/grpc/basics?view=aspnetcore-3.1

Invoke the gRPC service

'gRPCurl' is a command-line tool that lets you interact with gRPC services. It's basically 'curl' for gRPC services.

In the terminal, run the following command:

grpcurl -plaintext -proto greet.proto -d '{"name": "World"}' localhost:5001 greet.Greeter/SayHello

In the above example, the supplied body must be in JSON format. The body will be parsed and then transmitted to the server in the protobuf binary format.

You should see the following result.

{   
"message": "Hello World" 
}

See https://github.com/fullstorydev/grpcurl for more information.

Implement the gRPC service

Defining the contract

gRPC uses a contract-first approach to API development. Services and messages are defined in `*.proto` files:

So, move into the 'ProductService/Protos' folder and remove the 'greet.proto' file. Then, add a new file named 'product_service.proto' in the same folder and put the following content into it:

syntax = "proto3";

option csharp_namespace = "ProductService";

package catalog;

service ProductService {
  rpc CreateProduct(CreateProductRequest) returns (Product);

  rpc ListProducts(ListProductsRequest) returns (ListProductsResponse);
}

message CreateProductRequest {
  string name = 1;
  string description = 2;
}

message ListProductsRequest {
}

message ListProductsResponse {
  repeated Product products = 1;
}

message Product {
  string id = 1;
  string name = 2;
  string description = 3;
}

  • 'product_service.proto' defines a 'ProductService' service.
  • The 'ProductService' service defines two rpc calls 'CreateProduct' and 'ListProducts'.
    - 'CreateProduct' sends a 'CreateProductRequest' message and receives a `'Product' message.
    - 'ListProducts' sends a 'ListProductsRequest' message and receives a 'ListProducstsResponse' message.

The 'product_service.proto' file is included in the 'ProductService.csproj' by adding it to the <Protobuf> item group:

<ItemGroup> 		
<Protobuf Include="Protos\greet.proto" GrpcServices="Server" />
<Protobuf Include="..\Protos\product_service.proto" GrpcServices="Server" />
</ItemGroup>

It's a good recommendation to place 'Protos' folder outside of your gRPC service.

Implementing the service

So, move into the 'Services' folder and remove the 'GreeterService.cs' file. Then, add a new file named 'ProductServiceImpl.cs' in the same folder and put the following content into it:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Extensions.Logging;

namespace ProductService
{
    public class ProductServiceImpl : ProductService.ProductServiceBase
    {
        private readonly static List<Product> _products = new List<Product>();

        public override Task<Product> CreateProduct(CreateProductRequest request, ServerCallContext context)
        {
            var product = new Product
            {
                Id = Guid.NewGuid().ToString(),
                Name = request.Name,
                Description = request.Description
            };
            _products.Add(product);

            return Task.FromResult(product);
        }

        public override Task<ListProductsResponse> ListProducts(ListProductsRequest request, ServerCallContext context)
        {
            var response = new ListProductsResponse();
            response.Products.Add(_products);

            return Task.FromResult(response);
        }
    }
}

'CreateProduct' receives a 'CreateProductRequest' message, map the message to a product then store in a products collection.

'ListProducts' simply return the products collection.

Configure gRPC

In 'Startup.cs' add the gRPC service to the routing pipeline through the 'MapGrpcService' method.

app.UseEndpoints(endpoints =>
{
		endpoints.MapGrpcService<GreeterService>();
		endpoints.MapGrpcService<ProductServiceImpl>();
		
		endpoints.MapGet("/", async context =>
		{
				await context.Response.WriteAsync("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");
		});
});

Invoke the gRPC service

In the terminal, run the following command to create a AirPods Pro product:

grpcurl -plaintext -proto product_service.proto -d '{"name": "AirPods Pro", "description": "Magic like yo
u have never heard"}' localhost:5001 catalog.ProductService/CreateProduct

See the response in the following screenshot:

Run the following command to list products:

grpcurl -plaintext -proto product_service.proto localhost:5001 catalog.ProductService/ListProducts

See the response in the following screenshot:

Communication between gRPC services

Let's say that we have a 'CartService' gRPC service, would like to communicate with 'ProductService' gRPC service to retrieve product information using a product ID.

You can include the 'product_service.proto' file in the 'CartService.csproj' by adding it to the '<Protobuf>' item group and change 'GrpcServices' attribute from 'Server' to 'Client':

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

Then register a gRPC client, the generic 'AddGrpcClient' extension method can be used within 'Startup.ConfigureServices', specifying the gRPC typed client class and service address:

services.AddGrpcClient<ProductService.ProductServiceClient>(configureClient =>
{
		configureClient.Address = new Uri("http://localhost:5001");
});

The gRPC client type is registered as transient with dependency injection (DI). The client can now be injected and consumed directly in types created by DI.

See https://docs.microsoft.com/en-us/aspnet/core/grpc/clientfactory?view=aspnetcore-3.1 for more information.

In the end

gRPC is an open source remote procedure call (RPC) system initially developed at Google. So, Public interface definitions of Google APIs is probably a hightly recommend place for you to look at.

The repository contains the original interface definitions of public Google APIs that support both REST and gRPC protocols. Reading the original interface definitions can provide a better understanding of Google APIs and help you to utilize them more efficiently. You can also use these definitions with open source tools to generate client libraries, documentation, and other artifacts.

Let's discuss GraphQL API Gateway with Apollo Server meets gRPC services in a separate article.