The Startup
Published in

The Startup

Working With gRPC in .Net

gRPC

  • It’s modern lightweight Contract first API development using Protocol Buffers which uses binary serialization to transfer data. It mainly helps to reduce network usage.
  • You can use multiple languages to develop contracts and clients to call the services (protobuf supports wide range of programming languages) and has strongly-typed servers and clients.
  • gRPC supports Clinet, server calls like REST and also supports bi-directional streaming calls where real-time services that need to handle streaming requests or responses.

REST VS gRPC

REST:

  • REST is a popular resource based technique using HTTP Verbs HTTP GET,HTTP POST,HTTP PUT,HTTP PACTH,HTTP DELETE to perform CURD operations. REST’s communication often includes sending a JSON and can run over HTTP/1.1 or HTTP/2.
  • It is easy to develop a RESTful web service and it has browser support to perform action over Web Clients from Browser.
  • Major Communication happens using a JSON or XML, which is human readable. This makes it easier for developers to determine if the client input was sent correctly to the server and back, but it is too large to parse data.
  • REST is also widely used. A lot of people have experience with it and a lot of other web services (and clients) use REST. Having a REST web service makes it easier for other people to interact with your web service.
  • We don’t need any client setup to make calls to the server.

gRPC

  • gRPC is an open-source RPC framework that is created and used by Google. It is built upon HTTP/2 which makes bi-directional communication possible. gRPC communicates using binary data using protocol buffers by default for serializing structured data. gRPC servers allow cross-language unary calls or stream calls.
  • gRPC can use protocol buffer for data serialization. This makes payloads faster, smaller and simpler.
  • Just like REST, gRPC can be used cross-language which means that if you have written a web service in Golang, a Java written application can still use that web service, which makes gRPC web services very scalable.
  • gRPC uses HTTP/2 to support highly performant and scalable API’s and makes use of binary data rather than just text which makes the communication more compact and more efficient. gRPC makes better use of HTTP/2 then REST. gRPC for example makes it possible to turn-off message compression. This might be useful if you want to send an image that is already compressed. Compressing it again just takes up more time.
  • It is also type-safe. This basically means that you can’t give an apple while a banana is expected. When the server expects an integer, gRPC won’t allow you to send a string because these are two different types.
  • gRPC has Support to Java Script from web. It is easy to make calls form Browser using gRPC web library.

Protos

Proto Example:

namespace Practice
{
public class Product
{
public int ProductId { get; set;}
public string ProdcutName { get; set;}
public string Description { get; set;}
public int SKU { get; set; }
}
}
syntax = "proto3";option csharp_namespace = "Practice";import "google/protobuf/empty.proto";service Products{
rpc GetProducts(google.protobuf.Empty) returns (Response);
}
message Response{
repeated Product products = 1;
Status status = 2;
}
enum Status {
Success = 0;
Failure = 1;
Unknown = 2;
}
message Product{ int32 product_id = 1;
string prodcut_name = 2;
string sescription = 3;
int32 SKU = 4;
}

Error handing

Raise errors in ASP.NET Core gRPC

public async Task<Empty> CreateProduct(Product request, ServerCallContext context)
{
if (CheckForProductExists(request.ProductId))
{
var metadata = new Metadata
{
{ "Product", request.ProductId }
};
throw new RpcException(new Status(StatusCode.AlreadyExists, "Product Already Exists"), metadata);
}
}

Catch errors in gRPC clients

try
{
await client.CreateProduct(new Product{ Id = id });
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.AlreadyExists)
{
var productId = ex.Trailers.FirstOrDefault(e => e.Key == "Product");
Console.WriteLine($"Product '{productId }' already exists.");
}
catch (RpcException)
{
// Handle any other error type ...
}

Testing

Manual Testing

  • Add a Grpc.AspNetCore.Server.Reflection package reference.
  • Register reflection in Startup.cs:
  • AddGrpcReflection to register services that enable reflection.
  • MapGrpcReflectionService to add a reflection service endpoint.
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddGrpcReflection();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();

app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>();
if (env.IsDevelopment())
{
endpoints.MapGrpcReflectionService();
}
});
}

Functional and Unit Testing

Interceptor

namespace ProductGrpc
{
public class LoggerInterceptor : Interceptor
{
private readonly ILogger<LoggerInterceptor> _logger;
public LoggerInterceptor(ILogger<LoggerInterceptor> logger)
{
_logger = logger;
}
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
_logger.LogInformation($"gRPC call {context.Method}.");
try
{
return await continuation(request, context);
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error thrown by {context.Method}.");
throw;
}
}
}
}
namespace ProductGrpc
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc(options =>
{
options.Interceptors.Add<LoggerInterceptor>();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting(); app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>();
});
}
}
}

gRPC Client

Register gRPC clients

services.AddGrpcClient<Greeter.GreeterClient>(o =>
{
o.Address = new Uri("https://localhost:5001");
});

Health Check

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddGrpcHealthChecks()
.AddAsyncCheck("HealthCheck", () =>
{
var result = VerifyDbConnection()
? HealthCheckResult.Unhealthy()
: HealthCheckResult.Healthy();
return Task.FromResult(result);
}, Array.Empty<string>());
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting(); app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcHealthChecksService();
});
}
}
namespace HealthCheck
{
public class Program
{
static async Task Main(string[] args)
{
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Health.HealthClient(channel);
var cts = new CancellationTokenSource();
using var call = client.Watch(new HealthCheckRequest { Service = "HealthCheck" }, cancellationToken: cts.Token);
var watchTask = Task.Run(async () =>
{
try
{
await foreach (var message in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine($"{DateTime.Now}: Service is {message.Status}");
}
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.Cancelled)
{
// Handle cancellation exception.
}
});
Console.ReadKey();
Console.WriteLine("Finished");
cts.Cancel();
await watchTask;
}
}
}

Benchmarks

--

--

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +760K followers.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store