July 2019

Volume 34 Number 7

[Cutting Edge]

ASP.NET Core gRPC Services

By Dino Esposito | July 2019

Dino EspositoOriginally developed at Google, gRPC today is a remote procedure call (RPC) framework that has emerged as an alternative to RESTful and HTTP-based interfaces to connect remote components and specifically microservices. The new RPC framework was created in part to work with modern technologies such as HTTP/2 and Protobuf.

The gRPC framework offers native bindings for a number of programming languages, including C#, and using it from within ASP.NET and ASP.NET Core microservices has never been an issue. It’s worth mentioning, though, that earlier .NET implementations of gRPC wrapped native DLLs and kept the service hosted on its own server. In .NET Core 3.0, however, a gRPC service is a full .NET implementation hosted on Kestrel, like other ASP.NET Web applications. This article explores the Visual Studio 2019 project template and the dedicated ASP.NET Core layer.

Creating a gRPC Service in Visual Studio 2019

When you choose to create a new ASP.NET Core Web application, Visual Studio 2019 gives you the opportunity to create a new kind of component—a gRPC service. If you go ahead and complete the wizard, you end up with a minimal ASP.NET Core project with the canonical pair of files, startup.cs and program.cs, plus a couple of unusual new folders named protos and services. The program.cs file is nothing special, but the startup.cs file is worth a look.

The Configure method of the Startup class contains the following line:

public void ConfigureServices(IServiceCollection services)
{
  services.AddGrpc();
}

As expected, AddGrpc is an extension method of the IServiceCollection class. If you’re a curious person and want to dig into the internals of the method, here’s a summary of what you’ll find (probably not information you’ll ever use, but it’s like having a look under the hood!):

services.AddRouting();
services.AddOptions();
services.TryAddSingleton<GrpcMarkerService>();
services.TryAddSingleton<ServiceMethodsRegistry>();
services.TryAddSingleton(typeof(ServerCallHandlerFactory<>));

The call to AddRouting is a bit more functional as it serves to enable the use of global routing (a new feature introduced with ASP.NET Core 2.2) for communication between gRPC clients and the gRPC endpoints in the ASP.NET Core service being built. The three singletons added to the service runtime environment take care of the overall management of the service lifecycle—factory, discovery and invocation.

In the Configure method of the application’s startup class, you’ll find declared the use of the global routing system and the necessary endpoints, as follows:

app.UseRouting();
app.UseEndpoints(endpoints =>
{
  endpoints.MapGrpcService<GreeterService>();
});

A gRPC service isn’t based on controllers, rather it uses a service class that’s constructed for every request to process the client call. This service class is called GreeterService in the sample code that the template generates. The MapGrpcService<T> method creates a binding of the URL of the gRPC call to a call handler that gets invoked when processing the request. The handler factory is retrieved and used to create a new instance of the T class for the requested action to take place. This is a key difference between the ASP.NET Core 3.0 implementation of gRPC and the existing C# implementation. Note, however, that it’s still possible that an instance of the service class is resolved as a singleton from the DI container.

The Prototype of the Service Class

In the sample template-based project, the service class is defined as follows:

public class GreeterService : Greeter.GreeterBase
{
  ...
}

At first, you may think that implementing a gRPC service is, much like plain controller classes, all about writing a class with a bunch of public action methods. Well, not exactly. Sure, the Visual Studio project contains a file with a class defined as shown in the previous code. However, there’s no place in the same project where the base class of the service class in the snippet—the Greeter.GreeterBase class—is defined. How is that possible? The answer lies in the source code of another file—a small text file—placed in the protos folder (see Figure 1).

Solution Folder of a gRPC Project
Figure 1 Solution Folder of a gRPC Project

The protos folder contains one or more text files with the .proto extension, known as Protocol Buffer definition files or Protobuf files for short. Usually, there’s one for each service found in the ASP.NET Core service, but there are no actual constraints. In fact, you can define a service in one file and the messages it uses in another file, or define multiple services in the same file. The .proto text file provides the interface definition of the service. Here’s the content of the .proto file for the sample greeter service:

syntax = "proto3";
package Greet;service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
  string name = 1;
}
message HelloReply {
  string message = 1;
}

If the first piece of content isn’t a syntax element, the proto compiler will assume an older proto2 syntax. The newest .proto syntax is proto3 instead. Next, you find the name of the package to be created.

The service section indicates the application programming interface, namely the list of callable endpoints. Note that SayHello is a unary method, namely a method that works as a normal function call—the client sends a single request to the server and gets a single response back. Also, gRPC supports client, server and bi-directional streaming methods. These enable the client to write messages to a stream and get one response, or send one message and get a stream of return messages, or share a stream with a server to read and write. You describe streaming methods in a Protobuf file using the keyword stream, like so:

rpc SayHello1 (HelloRequest) returns (stream HelloReply) {}
rpc SayHello2 (stream HelloRequest) returns (HelloReply) {}
rpc SayHello3 (stream HelloRequest) returns (stream HelloReply) {}

The message sections in the proto file refer to message types being used by the methods. In other words, the message section defines any of the custom data-transfer object types being used by the public methods. In the sample snippet, the service named Greeter is made of one RPC method called SayHello. The method takes an input parameter of type HelloRequest and returns an output value in the form of an instance of the type HelloReply. Both types are made by a single string property. The integer value (in this case, 1) assigned to the properties of both messages indicates the field number and determines the position of the specific content in the message binary format being transferred across the wire. Hence, those values shouldn’t be changed once the service is deployed.

Details of Message Elements

In the definition of the message types in the proto file, you can use a number of primitive types including bool, int32, sint32, double, float and a long list of other numeric variations (int64, fixed32, uint32 and more). You can also use the bytes type for any arbitrary sequence of bytes. In particular, the sint32 type is ideal for signed integers as this format results in a more efficient encoding of negative values. Note that the aforementioned types are those defined in the proto3 syntax. Each supported language (such as C#) will then turn them into language-specific types. In C#, you can have long, bool, int, float and double. Properties are given a default value that coincides (at least in C#) with the default value of the language type.

The schema of a message is variable to some extent, in the sense that any declared property is optional. If marked with the repeated keyword, however, the same property is still optional, but can be repeated more than once. This explains why the field number is relevant, as it’s used as the placeholder for the actual content, as shown here:

message ListOfCitiesResponse {
  repeated string city = 1;
}

In addition to primitive scalar types, the proto syntax also supports enums. Enum types can be defined in the proto file or even inline in the body of the message type, like so:

enum Department {
  Unknown = 0;
  ICT = 1;
  HR = 2,
  Accounting = 3;
}

Note that the first element is required to take the value of 0. You’re allowed to have members with the same value as long as you declare it through the allow_alias option, as shown here:

enum Department {
  option allow_alias = true;
  Unknown = 0;
  ICT = 1;
  HR = 2,
  Accounting = 3;
  Finance = 3;
}

Without the allow_alias option, you’ll get a compile error in case of repeated enum values. If the enum is defined globally in the proto file, you can just use it by name in the various message types. If it’s defined in the body of a message type, you can still prefix its name with the name of the message type. Figure 2 shows this.

Figure 2 Embedded Definition of Enum Types

message EmployeeResponse {
  string firstName = 1;
  string lastName = 2;
  enum Department {
    Unknown = 0;
    ICT = 1;
    HR = 2;
  }
  Department currentDepartment = 3;
}
message ContactResponse {
  ...  EmployeeResponse.Department department = 3;
}

You can freely reference all message types in the same proto file, and also those defined in external proto files, as long as you import them first. Here’s the code:

import "protos/another.proto";

Properties of a message type aren’t limited to scalar types and enums. A message element can refer another message type, whether its definition is embedded in the message, global in the proto file, or imported from another proto file.

If during the lifetime of the service you need to update one of the message types, just pay attention not to reuse the field number. You can always add new elements, as long as the code knows how to deal with clients that may send packets devoid of the extra element. You’re also welcome to remove fields. In this case, though, it’s crucial that the number of the removed field isn’t reused. This, in fact, may cause confusion and conflicts. To protect against this, you can declare the critical field number as reserved, like so:

message PersoneResponse {
  reserved 1, 2, 5 to 8;
  reserved "gender", "address";
  ...
}

You can reserve field numbers (also using the extended syntax N to M), as well as field names. The same applies to entries in an enum type. This said, however, most of the time you’re better off just renaming the field with some prefix like NOTUSED_.

This explanation doesn’t exhaust all the possible variations of the proto3 syntax. For more information, refer to bit.ly/2Hz5NJW.

The Actual Service Class

The source code of the .proto file is silently processed to generate a base class—the missing Greeter.GreeterBase class—that provides the plumbing for the gRPC client/server communication to take place. You’ll find the actual source code of the base in the \Debug\netcoreapp3.0 folder of the project. Figure 3 shows an excerpt.

Figure 3 Auto-Generated rRPC Service Class

public static partial class Greeter
{
  public abstract partial class GreeterBase
  {
    public virtual Task<Greet.HelloReply> SayHello(
      Greet.HelloRequest request,
      ServerCallContext context)
    {
      throw new RpcException();
    }
    ...
  }
}

This file is generated after you run build and won’t exist before then. Also note that your project must have a reference to the Grpc.Tools package.

Aside from the .proto text file and the under-the-hood work to compile it to a base class, the resulting service class isn’t really different from a plain MVC controller. As you can see, the class is made of a few overridden public methods and their actual implementation:

public override Task<HelloReply> SayHello(
  HelloRequest request, ServerCallContext context)
{
  return Task.FromResult(new HelloReply
  {
    Message = "Hello " + request.Name
  });
}

In the body of the method, you can do whatever makes sense for the specific task, calling a database or external service, or performing any due calculation. For the service class to receive calls, you must start the gRPC server to listen over a configured port. Let’s have a look at a gRPC client now.

Writing a gRPC Client

The ASP.NET Core server application has dependencies on the ASP.NET Core gRPC package, as well as the core Google.ProtoBuf protocol. It also has a dependency on the Grpc.Tools package, but not for runtime action. The package is responsible for processing the content of the proto text file. A client application can be a console application with dependencies on the Grpc.Core package alone, the Google.ProtoBuf package and the Grpc.Tools.

To add a dependency on the actual service, you have two options. One is adding a reference to the proto file and let the tooling do the job. The other is creating a third (class library) project that only contains the proto file. Next, you link the resulting assembly to both a client and a server project. To reference the proto file, you copy the protos folder from the server project in the client project and edit the CSPROJ file, adding the following code:

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

Next, you write the code to open a channel and place calls. It’s worth noting that the gRPC framework uses the ProtoBuf binary protocol for remote calls, which in turn uses HTTP/2 as the transport. (Note that ProtoBuf is the default setting, but theoretically you can use other serialization/deserialization stacks.) Here’s the necessary setup for calling a gRPC service. Note that by the time ASP.NET Core 3.0 ships, there will be a managed gRPC client that will change the previous code for building a client:

var channel = new Channel(serviceUrl, ChannelCredentials.Insecure);
var client = new Greeter.GreeterClient(channel);

By default, the service URL is localhost and the port (as configured in the server project) is 50051. From the client reference you call prototyped methods as if it were a local call, as shown in the following code:

var request = new HelloRequest { Name = name };
var response = client.SayHelloAsync(request);
Console.WriteLine(response.Message);

You can see the resulting output in Figure 4.

Client and Server Applications in Action
Figure 4 Client and Server Applications in Action

In the end, gRPC has a noticeable analogy with old-fashioned Distributed COM of the 1990s. Like DCOM, it allows you to call remote objects as if they were local, and it does this over a binary and super-fast protocol leveraging HTTP/2.

gRPC isn’t REST and it’s not perfect, but it’s another option and fully open-sourced. It’s probably premature to say whether gRPC will replace REST in the heart of developers. For sure, some concrete and realistic microservices scenarios already exist where gRPC is really beneficial. You can find a useful comparison between gRPC and REST here: bit.ly/30VB7do.


Dino Esposito has authored more than 20 books and 1,000-plus articles in his 25-year career. Author of “The Sabbatical Break,” a theatrical-style show, Esposito is busy writing software for a greener world as the digital strategist at BaxEnergy. Follow him on Twitter: @despos.

Thanks to the following Microsoft technical experts for reviewing this article: John Luo, James Newton-King


Discuss this article in the MSDN Magazine forum