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:
public override async Task CommandBiDirectionalStreaming(IAsyncStreamReader<CommandRequest> requestStream, IServerStreamWriter<CommandResponse> responseStream, ServerCallContext context)
{
await foreach (var request in requestStream.ReadAllAsync())
{
// 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}:: Processed Successfully" });
}
}
Full Sample code CommandProcessorService.cs service
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.Extensions.Logging;
namespace SampleApplication.Grpc.Server.Services
{
public class CommandProcessorService : CommandProcessor.CommandProcessorBase
{
private readonly ILogger<CommandProcessorService> logger;
public CommandProcessorService(ILogger<CommandProcessorService> logger)
=> this.logger = logger;
public override async Task<CommandResponse> CommandUnary(CommandRequest request, ServerCallContext context)
{
// Simple request & response.
return await ProcessCommand(request);
}
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.
await ProcessIntermediateSteps(request, responseStream, 1);
await ProcessIntermediateSteps(request, responseStream, 2);
await ProcessIntermediateSteps(request, responseStream, 3);
await ProcessCommand(responseStream, request);
}
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.
var res = await ProcessCommand(request);
response.Message += res.Message;
response.Message += Environment.NewLine;
}
return response;
}
public override async Task CommandBiDirectionalStreaming(IAsyncStreamReader<CommandRequest> requestStream, IServerStreamWriter<CommandResponse> responseStream, ServerCallContext context)
{
await foreach (var request in requestStream.ReadAllAsync())
{
await ProcessCommand(responseStream, request);
}
}
private static async Task<CommandResponse> ProcessCommand(CommandRequest request)
{
// TODO:: Write your custom "Command Processing Logic" here and return response.
await Task.Delay(1000);
return new CommandResponse { Message = $"{request.Name}:: Processed Successfully" };
}
private static async Task ProcessIntermediateSteps(CommandRequest request, IServerStreamWriter<CommandResponse> responseStream, int stepCount)
{
// 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 {stepCount} Completed" });
}
private static async Task ProcessCommand(IServerStreamWriter<CommandResponse> responseStream, CommandRequest request)
{
// 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}:: Processed Successfully" });
}
}
}
Step 6: Add CommandProcessorService Service to the Routing Pipeline With the MapGrpcService Method in the Startup.cs
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<CommandProcessorService>();
});
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