September 2019

Volume 34 Number 9

[Cutting Edge]

Streaming Methods in ASP.NET Core gRPC Services

By Dino Esposito

Dino EspositoIn the previous installment of Cutting Edge, I walked through building a new type of service based on the gRPC framework that (although available to C# developers for a while) in ASP.NET Core 3.0 debuts as a native service hosted directly by Kestrel. The gRPC framework is suited for peer-to-peer binary communication between connected endpoints—mostly, but not necessarily, microservices. It also supports up-to-date technical solutions such as Google Proto­buf for serialization of content and HTTP/2 for transportation.

Visual Studio 2019 comes with an ASP.NET Core 3.0 project template for creating the skeleton of a gRPC service in just a few clicks. For a primer both about gRPC and the starter kit generated by Visual Studio, you can check out my July column at msdn.com/magazine/mt833481. This month I take my exploration of gRPC one step further. First, I’ll cover the underlying tooling in a bit more detail. In fact, some tooling is necessary to parse the content of the .proto file to C# classes to be used as the foundation of both client and service implementations. In addition, I’ll touch on streaming methods and complex message classes. Finally, I’ll focus on how to integrate streamed gRPC methods in the UI of a Web client application.

Building the gRPC Service

The built-in Visual Studio project template places the service inter­face definition file (the .proto file) in a subfolder named protos located in the same service project. In this article, though, I’ll take a different approach and start by adding a brand new .NET Standard 2.0 class library to the initially empty solution.

The proto class library doesn’t explicitly contain any C# class. All it contains is one or more .proto files. You can organize .proto files in folders and subfolders at your leisure. In the sample application, I have a single .proto file for a sample service located under the protos project folder. Here’s an excerpt from the service block of the sample .proto file:

service H2H {
  rpc Details (H2HRequest) returns (H2HReply) {}
}

The H2H sample service is expected to retrieve some sport-related information from some remote location. The Details method passes a head-to-head request and receives the score of the past matches between specified players or teams. Here’s what the H2HRequest and H2HReply messages may look like:

message H2HRequest {
  string Team1 = 1;
  string Team2 = 2;
}
message H2HReply {
  uint32 Won1 = 1;
  uint32 Won2 = 2;
  bool Success = 3;
}

The first message type passes information about the teams to process, whereas the latter receives the history of past matches and a Boolean flag that denotes success or failure of the operation. So far so good. Everything in the messages is defined as we’ve seen in the previous article. Using the gRPC jargon, the Details method is a unary method, meaning that every request receives one (and only one) response. This is the most common way of coding a gRPC service, however. Let’s add streaming capabilities, like so:

rpc MultiDetails (H2HMultiRequest) returns (stream H2HMultiReply) {}

The new MultiDetails method is a server-side streaming method, meaning that for each request it gets from some gRPC client it may return multiple responses. In this example, the client might send an array of head-to-head requests and receive individual head-to-head responses in an asynchronous way as they’re elaborated on the service end. For this to happen, the gRPC service method must be labeled with the stream keyword in the returns section. A stream method may require ad hoc message types, too, as shown here:

message H2HMultiRequest {
  string Team = 1;
  repeated string OpponentTeam = 2;
}

As mentioned, the client may ask for a head-to-head record between one particular team and an array of other teams. The repeated keyword in the message type just denotes that the OpponentTeam member may appear more than once. In pure C# terms, the H2HMultiRequest message type is conceptually equivalent to the following pseudocode:

class H2HMultiRequest
{  string Team {get; set;}  IEnumerable<string> OpponentTeam {get; set;}}

However, note that the code generated by the gRPC tooling is slightly different, as shown here:

public RepeatedField<string> OpponentTeam {get; set;}

Note, in fact, that any class generated from a gRPC message type T implements the members of the Google.ProtoBuf.IMessage<T> interface. The response message type should be designed to describe the actual data returned at each step of the streaming phase. So each reply must refer to an individual head-to-head response, between the primary team and one of the opponent teams specified in the array, like so:

message H2HMultiReply {
  H2HItem Team1 = 1;
  H2HItem Team2 = 2;
}
message H2HItem {
  string Name = 1;
  uint32 Won = 2;
}

The H2HItem message type indicates how many matches the given team has won against the other team specified in the request.

Before I move forward to look into the implementation of a stream method, let’s have a look at the dependencies required by the shared class library that embeds the proto definition. The Visual Studio project must reference the NuGet packages in Figure 1.

The NuGet Dependencies of the Proto Shared Class Library
Figure 1 The NuGet Dependencies of the Proto Shared Class Library

The project that includes the source .proto file must reference the Grpc.Tools package, as well as the Grpc.Net.Client (added in .NET Core 3.0 Preview 6) and Google.Protobuf packages required by any gRPC project (whether client, service or library). The tooling package is ultimately responsible for parsing the .proto file and generating any necessary C# classes at compile time. An item group block in the .csproj file instructs the tooling system on how to proceed. Here’s the code:

<ItemGroup>
  <Protobuf Include="Protos\h2h.proto"
            GrpcServices="Server, Client"
            Generator="MSBuild:Compile" />
  <Content Include="@(Protobuf)" />
  <None Remove="@(Protobuf)" />
</ItemGroup>

The most relevant part of the Item­Group block is the Protobuf node and in particular the GrpcServices attribute. The Server token in its assigned string value indicates that the tooling must generate the service class for the prototyped interface. The Client token indicates that it’s also expected to create the base client class to invoke the service. With this done, the resulting DLL contains C# classes for the message types, base service class and client class. Both the service project and the client project (whether console, Web or desktop) only need to reference the prototype DLL in order to deal with the gRPC service.

Implementing the Service

The gRPC service is an ASP.NET Core project with some special configuration done in the startup class. In addition to the ASP.NET Core server platform and the prototype assembly, it references also the ASP.NET Core gRPC framework and the Google.Protobuf package. The Startup class adds the gRPC runtime service in the Configure method and appends gRPC endpoints in the ConfigureServices method, as shown in Figure 2.

Figure 2 Configuring the gRPC service

public void ConfigureServices(IServiceCollection services)
{
  services.AddGrpc();
}
public void Configure(IApplicationBuilder app)
{  // Some other code here   ...
  app.UseRouting();
  app.UseEndpoints(endpoints =>
  {
    endpoints.MapGrpcService<H2HService>();
  });
}

The service class inherits from the base service class the tooling created based on the content of the .proto file, like so:

public class H2HService : Sample.H2H.H2HBase
{
  // Unary method Details
  public override Task<H2HReply> Details(
              H2HRequest request, ServerCallContext context)
  {
    ...
  }
  ...
}

Unary methods like the Details method have a simpler signature than stream methods. They return a Task<TReply> object and accept a TRequest object plus an instance of ServerCallContext to access the nitty-gritty details of the incoming request. A server-­side stream method has an additional response stream parameter used by the implementation code to stream packets back. Figure 3 presents the implementation of the MultiRequest stream method.

Figure 3 A Server-Side Stream gRPC Service Method

public override async Task MultiDetails(      H2HMultiRequest request,
      IServerStreamWriter<H2HMultiReply> responseStream,
      ServerCallContext context)
{  // Loops through the batch of operations embedded   // in the current request
  foreach (var opponent in request.OpponentTeam)
  {
    // Grab H2H data to return
    var h2h = GetHeadToHead_Internal(request.Team, opponent);
    // Copy raw data into an official reply structure    // Raw data is captured in some way: an external REST service
    // or some local/remote database    var item1 = new H2HItem {
     Name = h2h.Id1, Won = (uint) h2h.Record1};
    var item2 = new H2HItem {
     Name = h2h.Id2, Won = (uint) h2h.Record2};
    var reply = new H2HMultiReply { Team1 = item1, Team2 = item2 };
    // Write back via the output response stream
    await responseStream.WriteAsync(reply);
  }
  return;
}

As you can see, compared to classic unary methods, the stream method takes an additional parameter of type IServerStreamWriter<TReply>. This is the output stream the method will use to stream back results as they’re ready. In the code in Figure 3, the method enters in a loop for each of the operations requested (in this case, an array of teams to get past matches). It then streams back results as the query against a local/remote database or Web service returns. Once complete, the method returns and the underlying runtime environment closes the stream.

Writing a Client for a Stream Method

In the sample code, the client application is a plain ASP.NET Core 3.0 application. It holds references to the Google.Protobuf package and the Grpc.Net.Client package, plus the shared prototype library. The UI presents a button with some JavaScript attached that posts to a controller method. (Note that nothing stops you from using a classic HTML Form except that using Ajax to post might make it easier to receive notification of replies and update the UI in a smoother way.) Figure 4 presents the code.

Figure 4 Calling the gRPC service

[HttpPost]
public async Task<IActionResult> Multi()
{
  // Call the RPC service
  var serviceUrl = "https://localhost:50051";
    AppContext.SetSwitch(
                "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport",
                true);
    var httpClient = new HttpClient() {BaseAddress = new Uri(serviceUrl) };
  var client = GrpcClient.Create<H2H.H2HClient>(httpClient);
  var request = new H2HMultiRequest() { Team = "AF-324" };
  request.OpponentTeam.AddRange(new[] { "AW-367", "AD-683", "AF-510" });
  var model = new H2HMultiViewModel();
  using (var response = client.MultiDetails(request))
  {
    while (await response.ResponseStream.MoveNext())
    {
      var reply = response.ResponseStream.Current;      // Do something here ...
    }  }
  return View(model);
}

It’s worth recalling that the port of the gRPC service depends on the Visual Studio project, while the client caller class is defined in the prototype library. To prepare the request to a server-side streaming method, you don’t have to do anything more than just populate the input message type. As mentioned, the OpponentTeam collection is an enumerable .NET type and can be populated with AddRange or repeated calls to Add. The actual type isn’t one of the .NET Core collection types, but it’s still a collection type despite being implemented in the Google Protobuf package.

As a server-side method streams packets back until the end of the stream, the actual call to the method returns a stream object. Next, the client code enumerates the packets waiting for the end of the response. Each iteration of the while loop in Figure 4 captures a single reply packet from the gRPC service. What happens next depends on the client application. Overall, there are three distinct situations.

One is when the client application has its own UI, but can wait to collect the entire response before showing something fresh to the user. In this case, you load the data carried by the current reply object into the view model returned by the controller method. The second scenario is when there’s no UI (such as if the client is a working microservice). In this case, the received data is processed as soon as it’s available. Finally, in the third scenario the client application has its own responsive UI and is able to present data to users as it comes from the server. In this case, you can attach a SignalR Core endpoint to the client application and notify the UI in real time (see Figure 5).

The Sample Application in Action
Figure 5 The Sample Application in Action

The following code snippet shows how the client code changes when a SignalR hub is used on top of the gRPC call:

var reply = response.ResponseStream.Current;
await _h2hHubContext.Clients
                    .Client(connId)
                    .SendAsync("responseReceived",
        reply.Player1.Name,
        reply.Player1.Won,
        reply.Player2.Name,
        reply.Player2.Won);

You can check the source code for full details of the solution. Speaking of SignalR, there are a couple of points worth exploring. First, SignalR code is used only by the client application that connects to the gRPC service. The hub is injected in the controller of the client application, not in the gRPC service. And second, as far as streaming is concerned, it’s worth noting that SignalR Core also has its own streaming API.

Other Types of gRPC Streams

In this article I focused on server-side gRPC streaming methods, but that isn’t the only option. The gRPC framework also supports client-side streaming methods (multiple requests/one response) and bidirectional streaming (multiple requests/multiple responses). For client-side streaming, the only difference is the use of a IAsyncStreamReader as the input stream in the service method, as shown in this code:

public override async Task<H2HReply> Multi(
         IAsyncStreamReader<H2HRequest> requestStream,
         ServerCallContext context){  while (await requestStream.MoveNext())
  {
    var requestPacket = requestStream.Current;   
      // Some other code here
      ...  } }

A bidirectional method will return void and take no parameters as it’ll be reading and writing input and output data through input and output streams.

In summary, gRPC is a complete framework to connect two endpoints (client and server) over a binary, flexible and open source protocol. The support you get for gRPC from ASP.NET Core 3.0 is amazing and will improve over time, making now a great time to get started and experiment with gRPC, especially in microservice-to-microservice communication.


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
James Newton-King is an engineer on the ASP.NET Core team and works on gRPC for .NET Core

John Luo is an engineer on the ASP.NET Core team and works on gRPC for .NET Core


Discuss this article in the MSDN Magazine forum