FAESEL.COM

.NET & GRPC What they forgot to tell you

Date Published September 7th, 2020  Reading Time 11 Minutes

  1. grpc
  2. .net
  3. c#
  4. asp.net
  5. grpc-web
  6. rest
  7. nswag
  8. proto-files
  9. nuget
  10. grpc-reflection
  11. bloomrpc
GRPC Logo

As an engineer, I have always had a heavy reliance on REST'ful API's for passing information between applications. With the introduction of open API specification now in version 3.0.3, integration has never been easier. The push to break monoliths into microservices has further boosted its usage, however I always found one size never fits all.

Where REST falls down 👎

RESTful services have many shortfalls built in, if you're in my boat and most of the time your creating services and working on client applications. Having to tailor a client library to call those services has always been a tiresome task. 3rd party tooling like Nswag made some attempt to fix this problem however I still find breaking changes between versions that make huge changesets across all your endpoints. If your working across multiple languages like C# and Javascript your work doubles up.

There are also encumbrances experienced when mixing batch/bulk operations, overnight jobs with REST'ful APIs. Leading to complex solutions that auto scale or spread load over time. Having to go through each request-response cycle on bulk is just in-efficient.

In most cases, responses are also in the form of JSON which is designed to cater for human readability at the expense of being inefficient. If you talking machine to machine readability is not a concern?

Lets also not mention those endless subjective PR threads trying to decide whats RESTful and whats not 😇.

Can GRPC fill the gaps 🤷‍♂️

If you experienced the REST'ful pains above, GRPC's got your back. To get a quick demonstration of its capabilities, I recommend Shawn Wildermuth's gRPC Talk at netPonto User Group (2020) he explains it in a easy to understand way.

To sum up its capabilities it has two key differences to REST (if your already familiar with this, skip to section Things to look into).

1. Proto files

Proto files contain the definition of your API in a structured spec compliant way. The code below shows a simple GreetingsService with a basic request and response.

syntax = "proto3"; option csharp_namespace = "HelloService"; service GreetingsService { rpc GetHello (HelloRequest) returns (HelloResponse); } message HelloRequest { int32 HelloCount = 1; } message HelloResponse { string HelloDescription = 1; }

Proto files can then be used to transpile code into many languages. When transpiling we have the option to either create code for a server or client. Code generation creates a base class GreetingsServiceBase for us (it's generated in the bin folder on build time). Eventually, you end up with a service that looks like this:

using Grpc.Core; using HelloServer; using System.Threading.Tasks; namespace TaxServer.Services { public class HelloGrpcService : GreetingsService.GreetingsServiceBase { public override async Task<HelloResponse> GetHello(HelloRequest request, ServerCallContext context) { return new HelloResponse { HelloDescription = $"{request.HelloCount} Hellos to you!" }; } } }

The act of sharing and distributing proto files means that consuming clients can easily create their own client code and be completely agnostic of language.

2. Defining request/response lifecycle

GRPC allows you to change its request/response lifecycle, it has 4 options described below,

  • Unary RPC's: Unary RPCs where the client sends a single request to the server and gets a single response back.
  • Server Streaming RPC's: Server streaming RPCs where the client sends a request to the server and gets a stream to read a sequence of messages back.
  • Client Streaming RPC's: Client streaming RPCs where the client writes a sequence of messages and sends them to the server, again using a provided stream.
  • Bi-Directional Streaming RPC's: Bidirectional streaming RPCs where both sides send a sequence of messages using a read-write stream.

Taken from GRPC.io

These additional modes are more suited for batch processing over your traditional request/response lifecycle.

Things to look into ✅

So far so great, getting to this point is relatively easy and straightforward. Problem is all the tutorials seem to end at this point 😟. To have a live API several additional concerns need to be addressed. My list was as follows:

  1. Check how we can consume/distribute .proto files
  2. How to create a health checking probe for a GRPC service
  3. How to version endpoints
  4. Can a .NET Framework client app consume a .NET Core GRPC server?
  5. How to debug with tools, call an endpoint
  6. Authentication and authorization
  7. Can you call the service from a browser?

1. Check how we can consume/distribute .proto files

There are two different approaches to achieve this, mainly dependent on whether your service is internal or external public facing.

Option 1 - With nuget packages

Option one is to distribute your proto files using Nuget packages. This solution is recommended in the situation where you are using GRPC for internal services. Your solution structure would look something like this:

  • HelloService.Protos

    • Hello.protos
  • HelloService.Server

    • Server code ...

In this case we would use a Nuspec file to package the .protos and output it into the following structure in the client app. Considering you could be consuming more than one GRPC service it might make sense to create subfolders to know where the proto file comes from.

  • HelloClient /Protos/service name goes gere/Hello.protos

From here the client application can generate its client service code using the protofile. If you want to go one step further there is a dotnet command you can use to integrate the proto file into the .csproj file using a dotnet command which can be triggered after the installation of the package.

dotnet grpc add-file Hello.proto

Option 2 - With a discovery endpoint

This approach is recommended if your GRPC service is a service meant for external consumers. The idea behind this approach is to expose which services/endpoints are available. The method is dependent on the Grpc.Reflection Nuget package.

The general approach is outlined here.

Once implemented it allows you to use an endpoint from the server code to generate your client code. Dotnet has a GRPC CLI tool, that can read from a server reflection endpoint and produce a proto file out of it. The command looks like this,

dotnet grpc-cli dump https://localhost:5001 Reflection.HelloService

You can also write the proto file to disk using this command

dotnet grpc-cli dump http://localhost:10042 Reflection.HelloService -o ./prot

2. How to create a health checking probe for a GRPC service

Health checking probe endpoints are useful for monitoring uptime as well as managing containers when services are unresponsive. GRPC specification has a defined structure for creating your health checking endpoint called the GRPC Health Checking Protocol.

However, since we are using asp.net core we can get away from this and rely on middleware to do this for us with little code.

public void ConfigureServices(IServiceCollection services) { services.AddGrpc(); services.AddHealthChecks(); ... } public void Configure(IApplicationBuilder app) { app.UseEndpoints(endpoints => { endpoints.MapHealthChecks("/healthz"); ... }); }

Now when running locally https://localhost:5001/healthz we can get a 200 response. Here is what the output logs look like:

Request starting HTTP/2 GET https://localhost:5001/healthz info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] Executing endpoint 'Health checks' info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'Health checks' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished in 19.056ms 200 text/plain

3. How to version endpoints

The problem of versioning is easily solved using namespaces, it's just a case of incorporating your version number into the namespace like so,

option csharp_namespace = "HelloService.v1";

For each version, you would have different proto files and different service implementations. When inheriting from the base we can be specific on the version we need.

Server Code

public class HelloGrpcService : HelloService.v1.GreetingsService.GreetingsServiceBase { }

Client Code

The namespaces segregate the types so it just works out.

//Version 1 using var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new HelloService.v1.GreetingsService.GreetingsServiceClient(channel); var response = await client.GetHello(new HelloService.v1.HelloRequest() { HelloCount = 1 }); //Version 2 using var channel2 = GrpcChannel.ForAddress("https://localhost:5001"); var client2 = new HelloService.v2.GreetingsService.GreetingsServiceClient(channel); var response2 = await client2.GetHello(new HelloService.v2.HelloRequest() { HelloCount = 2 });

4. Can a .NET Framework client app consume a .NET Core GRPC server?

Turns out it can yes, however ... as GRPC is built upon HTTP/2 which is not supported in .net framework, making secure connections to your API is not possible. The client code for .net framework is very similar, we just pass a ChannelCredentials.Insecure option in when building the client.

var channel = new Channel("127.0.0.1", 5000, ChannelCredentials.Insecure); var client = new GreetingsService.GreetingsServiceClient(channel);

5. How to debug with tools, call an endpoint

If you're like me and you've come from a REST background your most likely used to polished tools like Postman or Insomnia to test out your endpoints. Sadly these tools don't support GRPC 😢... yet anyway...

The GRPC Tooling Community is still in its infancy. There are however some new players that are emerging that get the job done, most notably for me BloomRPC.

BloomRPC

After importing in your proto files you get a great swagger-esk UI that automatically build up your request body from your proto file.

6. Authentication and authorization

Because we are working under the guise of asp.net core we can take advantage of its authentication middleware. The following authentication methods are supported.

  • Azure Active Directory
  • Client Certificate
  • IdentityServer
  • JWT Token
  • OAuth 2.0
  • OpenID Connect
  • WS-Federation

Below is a simple code example of authenticating a JWT token with an identity service. As you can see its no different from a REST service.

public void ConfigureServices(IServiceCollection services) { var authority = "https://myidentityserver.com"; services .AddAuthentication("Bearer") .AddJwtBearer("Bearer", options => { options.Authority = authority; options.RequireHttpsMetadata = false; options.TokenValidationParameters = new TokenValidationParameters { ValidateAudience = false, }; options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration> ( metadataAddress: authority + "/.well-known/openid-configuration", configRetriever: new OpenIdConnectConfigurationRetriever(), docRetriever: new HttpDocumentRetriever { RequireHttps = false } ); options.Events = new JwtBearerEvents { OnTokenValidated = context => { var ci = (ClaimsIdentity)context.Principal.Identity; var authHeader = context.Request.Headers["Authorization"]; var token = authHeader.FirstOrDefault()?.Substring(7); if (token != null) { ci.AddClaim(new Claim("token", token)); } return Task.CompletedTask; } }; }); services.AddAuthorization(); ... } public void Configure(IApplicationBuilder app) { app.UseAuthentication(); app.UseAuthorization(); ... }

Below is output from an authenticated request:

Request starting HTTP/2 POST https://localhost:5001/HelloGrpcService.GreetingsService/GetHello application/grpc info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[2] Successfully validated the token. info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[1] Authorization was successful. info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] Executing endpoint 'gRPC - /HelloGrpcService.GreetingsService/GetHello' Request parameter 1 Request came from test-client-id info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'gRPC - /HelloGrpcService.GreetingsService/GetHello' info: Microsoft.AspNetCore.Hosting.Diagnostics[2] Request finished in 5865.2411ms 200 application/grpc

7. Can you call the service from a browser?

Currently, as it stands the answer is no, browsers don't offer fine-grained control over API requests to support GRPC. However, there is some light at the end of the tunnel.

Back in 2016 Google started working on a specification for "GRPC for the browser". You can read more about it here but in essence,

The basic idea is to have the browser send normal HTTP requests (with Fetch or XHR) and have a small proxy in front of the gRPC server to translate the requests and responses to something the browser can use - grpc.io

In the C# world, Microsoft has an implementation of this specification in their docs, Use gRPC in browser apps.

There are some disclaimers to this, as gRPC supports streaming and bidirectional requests this addition is only recommended for unary requests. Due to this limiting factor helpers are present to turn it on and off for services when setting up GRPC services in the startup,

endpoints.MapGrpcService<HelloGrpcService>().EnableGrpcWeb().RequireCors("AllowAll");

What I find particularly interesting is that the problem grpc-web solves is similar to the problems we have with .net framework (https/2 is not supported). Could this perhaps be an answer to getting secure requests working? ... sadly not yet! at the moment its not possible as grpc-web was was built on .net standard 2.1 so .net framework is not supported. Perhaps there might be movement on this in time to come.

Things I missed out

  1. Integration Testing, im a big fan of using in memory testing with Test Server it would be interesting to see if this works with a GRPC service.
  1. C# Examples
  2. More c# examples