Announcing gRPC JSON transcoding for .NET
gRPC is a modern way to communicate between apps. gRPC uses HTTP/2, streaming, binary serialization, and message contracts to create high-performance, real-time services. .NET has excellent support for building gRPC apps.
However, like most technology choices, there are trade-offs when choosing gRPC over an alternative like REST+JSON. gRPC’s strengths include performance and developer productivity. Meanwhile, REST+JSON can be used everywhere, and its human-readable JSON messages are easy to debug.
Wouldn’t it be great if we could build services once in ASP.NET Core and get both gRPC and REST? Now you can! Introducing gRPC JSON transcoding for .NET.
gRPC or REST? Why not both
gRPC JSON transcoding is an extension for ASP.NET Core that creates RESTful HTTP APIs for gRPC services. Once configured, JSON transcoding allows you to call gRPC methods with familiar HTTP concepts:
- HTTP verbs
- URL parameter binding
- JSON requests/responses
This is a highly requested feature that was previously available as an experiment. We’re excited to ship the first preview today, and we aim to ship a stable release with .NET 7.
Getting started
- The first step is to create a gRPC service (if you don’t already have one). Create a gRPC client and service is a great tutorial for getting started.
- Next, add a package reference to
Microsoft.AspNetCore.Grpc.JsonTranscoding
to the server. Register it in server startup code by addingAddJsonTranscoding()
. For example,services.AddGrpc().AddJsonTranscoding()
. - The last step is annotating your gRPC .proto file with HTTP bindings and routes. The annotations define how gRPC services map to the JSON request and response. You will need to add
import "google/api/annotations.proto";
to the gRPC proto file and have a copy of annotations.proto and http.proto in thegoogle/api
folder in your project.
syntax = "proto3";
import "google/api/annotations.proto";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
get: "/v1/greeter/{name}"
};
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
In the sample above, the SayHello gRPC method has been annotated with HTTP information. It can now be invoked as gRPC and as a RESTful API:
- Request:
HTTP/1.1 GET /v1/greeter/world
- Response:
{ "message": "Hello world" }
And browser apps call it like any other RESTful API:
fetch('https://localhost:5001/v1/greeter/world')
.then((response) => response.json())
.then((result) => {
console.log(result.message);
// Hello world
});
This is a simple example. See HttpRule for more customization options.
JSON transcoding vs gRPC-Web
Both JSON transcoding and gRPC-Web allow gRPC services to be called from a browser. However, the way each does this is different:
- gRPC-Web lets browser apps call gRPC services from the browser with the gRPC-Web client and Protobuf. gRPC-Web requires the browser app to generate a gRPC client and has the advantage of sending small, fast Protobuf messages.
- JSON transcoding allows browser apps to call gRPC services as RESTful APIs with JSON. The browser app doesn’t need to generate a gRPC client or know anything about gRPC.
About JSON transcoding implementation
JSON transcoding with gRPC isn’t a new concept. grpc-gateway is another technology for creating RESTful JSON APIs from gRPC services. It uses the same .proto annotations to map HTTP concepts to gRPC services. The important difference is in how each is implemented.
grpc-gateway uses code generation to create a reverse-proxy server. The reverse-proxy translates RESTful calls into gRPC+Protobuf and sends them over HTTP/2 to the gRPC service. The benefit of this approach is the gRPC service doesn’t know about the RESTful JSON APIs. Any gRPC server can use grpc-gateway.
Meanwhile, gRPC JSON transcoding runs inside an ASP.NET Core app. It deserializes JSON into Protobuf messages, then invokes the gRPC service directly. We believe JSON transcoding offers many advantages to .NET app developers:
- Fewer moving parts. Both gRPC services and mapped RESTful JSON API run out of one ASP.NET Core application.
- Performance. JSON transcoding deserializes JSON to Protobuf messages and invokes the gRPC service directly. There are significant performance benefits in doing this in-process vs. making a new gRPC call to a different server.
- Cost. Fewer servers = smaller monthly hosting bill.
JSON transcoding doesn’t replace MVC or Minimal APIs. It only supports JSON, and it’s very opinionated about how Protobuf maps to JSON.
What’s next
This is the first release of gRPC JSON transcoding for .NET. Future previews of .NET 7 will focus on improving performance and OpenAPI support.
Try it with .NET today
gRPC JSON transcoding is available now with .NET 7 Preview 4.
For more information about JSON transcoding, check out the documentation, or try out a sample app that uses JSON transcoding.
See it in action
On an recent episode of ASP.NET Community Standup, James Newton-King joined Jon Galloway to demonstrate gRPC Transcoding and answer live questions from viewers:
We look forward to seeing what you create with .NET, gRPC, and now gRPC JSON transcoding!
14 comments
Rest api call with post is not working on my grpc server api. The body in post api is empty on grpc server (json transcoded through asp.net).
Grpc-server works fine with grpc-client and the rest-client works fine on a regular asp-net core server (post api is received with body).
Somehow – when sending the same post message to grpc server – autogenerated code goofs up something and the received message has no data.
My proto file:
Server Code:
Rest client code:
Kindly help me on this.
Hi James
Thank you for sharing the JSON transcoding feature. I have a particular use case to make the gRPC services compatible with systems that supports HTTP calls in a Kubernetes Cluster. How to access the JSON Transcoded API in the scenario where the dotnet gRPC services are accessed over HTTP?
I get 400 Bad Request for calls to http endpoint, https endpoint works fine.
I’ve been experimenting with this. The majority of my gRPC methods require Authorization. I’ve not been able to figure out how to do that for the REST/Json side.
I’m using JWT and setting the context in each gRPC call that needs authorization. For example:
Model = await Client.GetHomePageModelAsync(new NoRequest(), myState.AuthHeader);
Where the myState service provides the JWT in a header from my cached login data:
Yeah!!
I llike it very much. Thank you for sharing this.
Great,
Do you recommend it for interservice communication for microservices?
Is .NET7 target really required. Why are .NET5 and .NET6 not supported?
JSON transcoding requires a lot of custom JSON serialization. System.Text.Json is JSON transcoding’s serializer and it requires some System.Text.Json features new in .NET 7.
This is really cool!! Can you say anything regarding openapi support for gRPC Transcoding and documentation comments in .proto files?
Because if JSON Transcoding can have openapi and documenation it is not a question anymore if REST or gRPC but an option to have both 🙂
OpenAPI currently isn’t supported.
However, we know it’s really important to have OpenAPI when publishing REST APIs. There will definitely be some support for it in JSON transcoding. We’re looking at the best way to do it now. If we don’t find a solution we’re happy with in .NET 7 we’ll provide an experimental package NuGet package that enables OpenAPI.
Hi, one question arises here.
I understood gRpc more efficient when needed to improve communication between applications, and says that the usage of binary was part of that advantage.
Now, how this is going to change this approach because I feel we are moving to the web api territory. I’m a bit confused 🤔
Great.
Does it support streaming (server and client)?
JSON transcoding supports server streaming methods (i.e. the server streaming messages to the client). The response is written as line delimited JSON objects.
Client and bidirectional streaming methods aren’t supported.
I just tried this with
WriteAsync(..,CancellationToken)
and gotTo avoid this I had to use
WriteAsync(T)
. The only way I could find to detect whether the call was gRPC or HTTP was to useGetHttpContext()
and check the ContentType. There should be an easier way to detect whether the CancellationToken parameter can be used or not.Yay! This is exciting.