The relationship between C # and C #: REST, gRPC and everything in between

There are many ways to communicate between a C # client and a C # server. Some of them are reliable, others are not. Some are very fast, others not. It is important to know the various options so that you can decide which is best for you. This article will discuss the most popular technologies to date and why they are so widely used. We will talk about REST, gRPC and everything in between.

Optimal scenario


Let's look at how we would like our client-server communication to look in the real world. I present something like this:

// on client side
public void Foo()
{
    var server = new MyServer(new Uri("https://www.myserver.com/");)
    int sum = server.Calculator.SumNumbers(12,13); 
}

// on server side
class CalculatorController : Controller{
    public int SumNumbers(int a, int b)
    {
        return a + b;
    }
}

I would like to have full Intellisense support. When I click server and . I want Visual Studio to show all the controllers. And when I click CalculatorController and . I want to see all the action methods. I also want the highest performance, very low network load and bidirectional data transfer. And I need a reliable system that copes with version control so that I can easily deploy new versions of clients and new versions of servers.

It's too much?

Note that I'm talking about stateless APIs here . This is equivalent to a C # project where there are only two types of classes:

  • Static classes with static methods only.
  • POCO classes in which there is nothing but fields and properties whose type is a primitive or other POCO class.

Having state in the API introduces complexity, and this is the root of all evil. So, for the sake of this article, let's make things beautiful and stateless.

Traditional REST approach


REST API appeared in the early 2000s and conquered the Internet. Now this is by far the most popular way to create web services.

REST defines a fixed set of GET , POST , PUT, and DELETE operations for requests from the client to the server. For each request, we get a response containing a payload (usually JSON). Requests include parameters in the request itself or as a payload (usually JSON) when it is a POST or PUT request.

There is a RESTful API standard that defines the following rules (which you don't really need):

  • GET is used to get the resource
  • PUT is used to change the state of a resource.
  • POST is used to create a resource.
  • DELETE is used to delete a resource.

If you are not familiar with REST so far, the above explanation will probably not help, so here is an example. .NET has built-in REST support. In fact, the ASP.NET Web API is by default a REST web service. Here's what a typical ASP.NET client and server looks like:

On the server:

[Route("People")]
public class PeopleController : Controller
{
    [HttpGet]
    public Person GetPersonById(int id)
    {
        Person person = _db.GetPerson(id);
        return person;//Automatically serialized to JSON
    }
} 

On the client:

var client = new HttpClient();
string resultJson = await client.GetStringAsync("https://www.myserver.com/People/GetPersonById?id=123");
Person person = JsonConvert.DeserializeObject<Person>(resultJson);

REST is damn convenient, but not suitable for the optimal scenario. So let's see if we can do it better.

Refit


ReFit is not an alternative to REST. Instead, it is built on top of REST and allows us to invoke server endpoints as if they were a simple method. This is achieved by sharing the interface between the client and server. On the server side, your controller will implement the interface:

public interface IMyEmployeeApi
{
    [Get("/employee/{id}")]
    Task<Employee> GetEmployee(string id);
}

Then on the client side you will need to enable the same interface and use the following code:


var api = RestService.For<IMyEmployeeApi>("https://www.myserver.com");
var employee = await api.GetEmployee("abc");

It is so simple. There is no need to run complex automation or use third-party tools, except for a couple of NuGet packages.

This is getting much closer to the optimal scenario. Now we have IntelliSense and a reliable contract between client and server. But there is another option that is even better in some respects.

Swagger


Like ReFit, Swagger is also built on top of REST. OpenAPI , or Swagger , is a REST API specification. It describes a REST web service in simple JSON files. These files are a web service API schema. They include:

  • All paths (URLs) in the API.
  • Expected operations (GET, POST, ...) for each path. Each path can handle different operations. For example, the same path mystore.com/Product can accept a POST operation that adds a product and a GET operation that returns a product.
  • The expected parameters for each path and operation.
  • Expected answers for each path.
  • Types of each parameter and response object.

This JSON file is essentially a contract between clients and the server. Here is an example swagger file describing a web service called Swagger Petstore (for clarity, I removed some parts):

Json schema
{ 
   "swagger":"2.0",
   "info":{ 
      "version":"1.0.0",
      "title":"Swagger Petstore",
      "description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification",
   },
   "host":"petstore.swagger.io",
   "basePath":"/api",
   "schemes":[ 
      "http"
   ],
   "consumes":[ 
      "application/json"
   ],
   "produces":[ 
      "application/json"
   ],
   "paths":{ 
      "/pets":{ 
         "get":{ 
            "description":"Returns all pets from the system that the user has access to",
            "operationId":"findPets",
            "produces":[ 
               "application/json",
               "application/xml",
            ],
            "parameters":[ 
               { 
                  "name":"tags",
                  "in":"query",
                  "description":"tags to filter by",
                  "required":false,
                  "type":"array",
                  "items":{ 
                     "type":"string"
                  },
                  "collectionFormat":"csv"
               },
               { 
                  "name":"limit",
                  "in":"query",
                  "description":"maximum number of results to return",
                  "required":false,
                  "type":"integer",
                  "format":"int32"
               }
            ],
            "responses":{ 
               "200":{ 
                  "description":"pet response",
                  "schema":{ 
                     "type":"array",
                     "items":{ 
                        "$ref":"#/definitions/Pet"
                     }
                  }
               },
...
 


Let's look at the consequences of this. Using a JSON file like the one above you can potentially create a C # client with full IntelliSense. In the end, you know all the paths, operations, what parameters they expect, what types of parameters, what are the answers.

There are several tools that do just that. On the server side, you can use Swashbuckle.AspNetCore to add Swagger to your ASP.NET and create the specified JSON files. For the client side, you can use swagger-codegen and AutoRest to process these files in JSON format and generate the client. Let's see an example of how to do this:

Adding Swagger to your ASP.NET Server


Start by adding the NuGet Swashbuckle.AspNetCore package . In ConfigureServices , register the Swagger generator:

services.AddSwaggerGen(options => 
	options.SwaggerDoc("v1", new OpenApiInfo {Title = "My Web API", Version = "v1"}));

In the Startup.cs file in the Configure method, add:

app.UseSwagger();

Finally, the action methods inside the controller must be marked with [HttpXXX] and [FromXXX] attributes:

[HttpPost]
public async Task AddEmployee([FromBody]Employee employee)
{
    //...
}
 
[HttpGet]
public async Task<Employee> Employee([FromQuery]string id)
{
    //...
}

It is so simple for the server side. When the project starts , a swagger.json file will be generated , which you can use to generate the client.

Generating a client from Swagger using AutoRest


To start using AutoRest , install it with npm : npm install -g autorest . After installation, you will need to use the AutoRest command-line interface to create the C # client from the swagger.json file . Here is an example:

autorest --input-file="./swagger.json" --output-folder="GeneratedClient" --namespace="MyClient" --override-client-name="MyClient" --csharp

This will create a GeneratedClient folder with the generated C # files. Note that the namespace and client name are redefined. Add this folder to your client project in Visual Studio as shown below.



You will need to install the Microsoft.Rest.ClientRuntime NuGet package, because the generated code depends on it. After installation, you can use the API like a regular C # class:

var client = new MyClient();
Employee employee = client.Employee(id: "abc");

There are some subtleties that you can read about in the AutoRest documentation . And you will need to automate this process, so I suggest reading Patrick Svensson’s manual for some useful tips, as well as this article by Peter Yausovets.

My problem with Swagger is that the JSON file is created at runtime, so this makes it a little difficult to automate the CI / CD process.

Traditional REST vs Swagger vs ReFit


Here are a few points to consider when choosing.

  • If you have a very simple private REST API, you might not have to worry about client generation and common interfaces. A small task does not justify additional effort.
  • Swagger supports many languages, and ReFit only supports .NET. Swagger is also the basis for many tools, tests, automation tools, and user interface tools. This will probably be the best choice if you are creating a large public API.
  • Swagger is much more complicated than ReFit. With ReFit, it is just a matter of adding a single interface to both your server and client project. On the other hand, with ReFit you will have to create new interfaces for each controller, while Swagger will take care of this automatically.

But before you decide something, check out the 4th option, which has nothing to do with REST.

gRPC


gRPC (gRPC - Remote Procedure Call) is an open source remote procedure call system developed by Google. This is a bit like REST in the sense that it provides a way to send requests from the client to the server. But this is very different, here are the similarities and differences:

  • Like REST, gRPC is language independent. There are tools for all popular languages, including C #.
  • gRPC is contract-based and uses .proto files to define the contract. This is somewhat similar to Swagger swagger.json and the common ReFit interface. A client of any programming language can be generated from these files.
  • gRPC Protocol Buffer (Protobuf). REST, JSON XML. , , .
  • gRPC HTTP/2. . , REST HTTP 1.x ( HTTP 1.1).
  • HTTP 1.1 TCP- () , HTTP/2 .
  • HTTP/2 . , TCP- . , , HTTP 1.1.
  • gRPC .

There are two ways to use gRPC. For .NET Core 3.0, there is a fully managed gRPC for .NET library . Also you can use gRPC C # . This does not mean that gRPC for .NET replaces gRPC C # . Let's see an example with the newer gRPC for .NET .

GRPC for .NET server side


This is not a guide, but rather a general idea of ​​what to expect. Here's what an example controller would look like in gRPC:


public class GreeterService : Greeter.GreeterBase
{
    public override Task<HelloReply> SayHello(HelloRequest request,
        ServerCallContext context)
    {
        _logger.LogInformation("Saying hello to {Name}", request.Name);
        return Task.FromResult(new HelloReply 
        {
            Message = "Hello " + request.Name
        });
    }
}

You need to add the following to Configure in the Startup.cs file :


app.UseEndpoints(endpoints =>
{
    endpoints.MapGrpcService<GreeterService>();
});

The API is described in the .proto file, which is part of the project:

syntax = "proto3";
 
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}
 
message HelloRequest {
  string name = 1;
}
 
message HelloReply {
  string message = 1;
}

This .proto file is added in the .csproj file:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto"/>
</ItemGroup>

GRPC for .NET client side


The client is generated from .proto files. The code itself is very simple:


var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
 
var response = await client.SayHello(
    new HelloRequest { Name = "World" });
 
Console.WriteLine(response.Message);

gRPC vs REST


GRPC sounds like a nice deal. It is faster and easier under the hood. So should we all switch from REST to gRPC? The answer is that it depends . Here are some considerations:

In my experience, working with gRPC and ASP.NET is still small. You will be better off with mature REST support. As far as contract communication is concerned, this is good, except that you have similar alternatives in REST that we have already talked about: Swagger and ReFit.

The biggest advantage is performance. In most cases, according to these criteria , gRPC is much faster. Especially for large payloads for which Protobuf serialization really matters. This means that this is a huge advantage for a server with a high load.

Moving from REST to gRPC in a large ASP.NET application will be difficult. However, if you have a microservice-based architecture, then this transition will become much easier to complete gradually.

Other ways of communication


There are several other ways of communication that I did not mention at all, but it is worth knowing that they exist:

  • GraphQL is an API query language developed by Facebook. This allows the client to request exactly the data that it needs from the server. Thus, you can create only one endpoint on the server, which will be extremely flexible and will return only the data that the client needs. GraphQL has become very popular in recent years.
  • TcpClient TcpListener ( System.Net.Sockets) TCP. , . , ASP.NET API.
  • UdpClient UDP. TCP , UDP . TCP , , UDP — . UDP , , . : , IP (VoIP).
  • WCF is an old technology that mainly uses SOAP-based inter-process communication. This is a huge framework that I won’t go into, I can only say that it has lost its popularity for REST and JSON payloads.

That's all. I hope the article was interesting! Good code to all!

All Articles