gRPC for .NET: Creating a gRPC Server Application
Follow this tutorial for a step-by-step guide to creating a gRPC server application that can aid in inter-service communication.
Join the DZone community and get the full member experience.
Join For FreeWhile working with Protobuf message format for one of my client projects, I recently came across a gRPC framework. After doing my analysis, gRPC seems pretty promising for inter-service communication, particularly in microservices architectures. gRPC is a language-agnostic, high-performance Remote Procedure Call (RPC) framework.
gRPC is built on top of the HTTP/2 transport layer and therefore can support four types of gRPC methods (unary, client streaming, server streaming, and bi-directional streaming). It uses Protobuf for message exchange.
In this post, I will be explaining how we can quickly set up a gRPC Server Application.
Getting Started With gRPC Server Application
Step 1: Add a New Project
Go to gRPC Server Project File/Folder Organization and pick the Asp.net Core gRPC service project template in Visual Studio.
Step 2: Add CommandProcessor.proto in the Protos Root Folder
CommandProcessor.proto contains the following gRPC method types are:
- Unary
- Server streaming
- Client streaming
- Bi-directional streaming
syntax = "proto3";
option csharp_namespace = "SampleApplication.Grpc.Server";
package SampleCommandProcessor;
// The service definition.
service CommandProcessor {
// Unary method
rpc CommandUnary (CommandRequest) returns (CommandResponse);
// Server streaming method
rpc CommandServerStreaming (CommandRequest) returns (stream CommandResponse);
// Client streaming method
rpc CommandClientStreaming (stream CommandRequest) returns (CommandResponse);
// Bi-directional streaming method
rpc CommandBiDirectionalStreaming (stream CommandRequest) returns (stream CommandResponse);
}
// The request message containing the command's name.
message CommandRequest {
string name = 1;
}
// The response message containing the message.
message CommandResponse {
string message =2;
}
Step 3: Set CommandProcessor.proto File Properties
Set Build Action to Protobuf Compiler & gRPC Stub Classes to Server Only.
Step 4: Build the gRPC Server Application to Generate C# Code From the CommandProcessor.proto File
Location of generated C# code: \obj\Debug\net5.0\Protos
Generated C# code contains:
- An abstract base type (CommandProcessorBase abstract class) for each service
- Classes for any messages (CommandRequset & CommandRequest class)
Step 5: Create a Concrete Implementation of CommandProcessorBase Abstract Class
Add CommandProcessorService.cs class in Services root folder. This service class will inherit from CommandProcessorBase abstract class, will override & provide the implementation for the unimplemented methods of CommandProcessorBase abstract class.
A. Unary method
- Return single response after processing of a single request
- Call completes when the response is returned
- Simple request/response
CommandUnary method:
public override async Task<CommandResponse> CommandUnary(CommandRequest request, ServerCallContext context)
{
// TODO:: Write your custom "Command Processing Logic" here and return response.
await Task.Delay(1000);
return new CommandResponse { Message = $"{request.Name}:: Processed Successfully" };
}
B. Server streaming method
- Multiple messages can be streamed back to the caller during the processing of a single request
- Call completes when the method returns
- Server pushing updates to clients periodically
CommandServerStreaming method:
public override async Task CommandServerStreaming(CommandRequest request, IServerStreamWriter<CommandResponse> responseStream, ServerCallContext context)
{
// Multi-step request processing & sending out intermediate/partial responses until request is processed completely.
// TODO:: Write your custom "Command Processing Logic" here and write response to stream.
await Task.Delay(1000);
await responseStream.WriteAsync(new CommandResponse { Message = $"{request.Name}:: Step 1 Completed" });
await Task.Delay(1000);
await responseStream.WriteAsync(new CommandResponse { Message = $"{request.Name}:: Step 2 Completed" });
await Task.Delay(1000);
await responseStream.WriteAsync(new CommandResponse { Message = $"{request.Name}:: Step 3 Completed" });
await Task.Delay(1000);
await responseStream.WriteAsync(new CommandResponse { Message = $"{request.Name}:: Processed Successfully" });
}
C. Client Streaming method
- Request stream to the server & single response returned from the server
- Call completes when a response message is returned
- File upload from client-side
CommandClientStreaming method:
public override async Task<CommandResponse> CommandClientStreaming(IAsyncStreamReader<CommandRequest> requestStream, ServerCallContext context)
{
var response = new CommandResponse { };
await foreach (var request in requestStream.ReadAllAsync())
{
// Multi request processing & single combined response at the end.
// TODO:: Write your custom "Command Processing Logic" here and combine responses.
await Task.Delay(1000);
var res = new CommandResponse { Message = $"{request.Name}:: Processed Successfully" };
response.Message += res.Message;
response.Message += Environment.NewLine;
}
return response;
}
Bi-directional streaming method
- Client and service can send messages to each other at any time
- Call completes when the method returns
- Client and server sharing messages like a chat application.
CommandBiDirectionalStreaming method:
91public override async Task CommandBiDirectionalStreaming(IAsyncStreamReader<CommandRequest> requestStream, IServerStreamWriter<CommandResponse> responseStream, ServerCallContext context)
2{
3await foreach (var request in requestStream.ReadAllAsync())
4{
5// TODO:: Write your custom "Command Processing Logic" here and write response to stream.
6await Task.Delay(1000);
7await responseStream.WriteAsync(new CommandResponse { Message = $"{request.Name}:: Processed Successfully" });
8}
9}
Full Sample code CommandProcessorService.cs service
x1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Threading.Tasks;
5using Grpc.Core;
6using Microsoft.Extensions.Logging;
7
8namespace SampleApplication.Grpc.Server.Services
9{
10public class CommandProcessorService : CommandProcessor.CommandProcessorBase
11{
12private readonly ILogger<CommandProcessorService> logger;
13
14public CommandProcessorService(ILogger<CommandProcessorService> logger)
15=> this.logger = logger;
16
17public override async Task<CommandResponse> CommandUnary(CommandRequest request, ServerCallContext context)
18{
19// Simple request & response.
20return await ProcessCommand(request);
21}
22
23public override async Task CommandServerStreaming(CommandRequest request, IServerStreamWriter<CommandResponse> responseStream, ServerCallContext context)
24{
25// Multi-step request processing & sending out intermediate/partial responses until request is processed completely.
26await ProcessIntermediateSteps(request, responseStream, 1);
27await ProcessIntermediateSteps(request, responseStream, 2);
28await ProcessIntermediateSteps(request, responseStream, 3);
29await ProcessCommand(responseStream, request);
30}
31
32public override async Task<CommandResponse> CommandClientStreaming(IAsyncStreamReader<CommandRequest> requestStream, ServerCallContext context)
33{
34var response = new CommandResponse { };
35
36await foreach (var request in requestStream.ReadAllAsync())
37{
38// Multi request processing & single combined response at the end.
39var res = await ProcessCommand(request);
40response.Message += res.Message;
41response.Message += Environment.NewLine;
42}
43
44return response;
45}
46
47public override async Task CommandBiDirectionalStreaming(IAsyncStreamReader<CommandRequest> requestStream, IServerStreamWriter<CommandResponse> responseStream, ServerCallContext context)
48{
49await foreach (var request in requestStream.ReadAllAsync())
50{
51await ProcessCommand(responseStream, request);
52}
53}
54
55private static async Task<CommandResponse> ProcessCommand(CommandRequest request)
56{
57// TODO:: Write your custom "Command Processing Logic" here and return response.
58await Task.Delay(1000);
59return new CommandResponse { Message = $"{request.Name}:: Processed Successfully" };
60}
61
62
63private static async Task ProcessIntermediateSteps(CommandRequest request, IServerStreamWriter<CommandResponse> responseStream, int stepCount)
64{
65// TODO:: Write your custom "Command Processing Logic" here and write response to stream.
66await Task.Delay(1000);
67await responseStream.WriteAsync(new CommandResponse { Message = $"{request.Name}:: Step {stepCount} Completed" });
68}
69
70private static async Task ProcessCommand(IServerStreamWriter<CommandResponse> responseStream, CommandRequest request)
71{
72// TODO:: Write your custom "Command Processing Logic" here and write response to stream.
73await Task.Delay(1000);
74await responseStream.WriteAsync(new CommandResponse { Message = $"{request.Name}:: Processed Successfully" });
75}
76}
77}
Step 6: Add CommandProcessorService Service to the Routing Pipeline With the MapGrpcService Method in the Startup.cs
41app.UseEndpoints(endpoints =>
2{
3endpoints.MapGrpcService<CommandProcessorService>();
4});
That's it. gRPC Server Application is ready!
Published at DZone with permission of Kapil Khandelwal, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments