C#和C#之间的关系:REST,gRPC及其之间的所有内容

在C#客户端和C#服务器之间进行通信的方法有很多。其中一些是可靠的,另一些则不是。有些非常快,有些则不是。重要的是要知道各种选项,以便您可以确定最适合您的选项。本文将讨论迄今为止最流行的技术以及为何如此广泛地使用它们。我们将讨论REST,gRPC及其之间的所有内容。

最佳方案


让我们看一下我们希望客户-服务器通信在现实世界中如何看待。我提出这样的事情:

// 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;
    }
}

我想要全面的Intellisense支持。当我单击服务器我希望Visual Studio显示所有控制器。当我单击CalculatorController我想查看所有动作方法。我还想要最高的性能,非常低的网络负载和双向数据传输。而且,我需要一个可靠的系统来应对版本控制,以便可以轻松部署客户端的新版本和服务器的新版本。

太多了?

请注意,我在这里谈论的是无状态 API 这等效于C#项目,其中只有两种类型的类:

  • 仅带有静态方法的静态类。
  • POCO类,其中只有类型为基本类型或其他POCO类的字段和属性。

在API中使用状态会带来复杂性,这是万恶之源。因此,就本文而言,让我们使事物变得美丽而无状态。

传统REST方法


REST API出现在2000年代初期,并征服了Internet。现在,这是迄今为止创建Web服务的最流行方法。

REST为从客户端到服务器的请求定义了一组固定的GETPOSTPUTDELETE操作对于每个请求,我们都会得到一个包含有效负载(通常为JSON)的响应。当请求是POST或PUT请求时,请求会将参数包含在请求本身中或作为有效负载(通常为JSON)。RESTful API

标准定义了以下规则(您实际上并不需要):

  • GET用于获取资源
  • PUT用于更改资源的状态。
  • POST用于创建资源。
  • DELETE用于删除资源。

如果到目前为止您还不熟悉REST,那么上面的解释可能无济于事,因此这里是一个示例。.NET具有内置的REST支持。实际上,默认情况下,ASP.NET Web API是REST Web服务。这是典型的ASP.NET客户端和服务器的外观:

在服务器上:

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

在客户端上:

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

REST非常方便,但不适合最佳方案。因此,让我们看看是否可以做得更好。

改装


ReFit不能替代 REST。相反,它建立在REST之上,并允许我们像调用简单方法一样调用服务器端点。这是通过在客户端和服务器之间共享接口来实现的。在服务器端,您的控制器将实现接口:

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

然后在客户端,您将需要启用相同的接口并使用以下代码:


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

很简单。除了几个NuGet程序包外,无需运行复杂的自动化程序或使用第三方工具。

这越来越接近最佳方案。现在,我们有了IntelliSense以及客户端和服务器之间的可靠合同。但是还有另一种选择在某些方面甚至更好。

昂首阔步


像ReFit一样,Swagger也建立在REST之上。OpenAPISwagger是REST API规范。它以简单的JSON文件描述了REST Web服务。这些文件是Web服务API架构。它们包括:

  • API中的所有路径(URL)。
  • 每个路径的预期操作(GET,POST等)。每个路径可以处理不同的操作。例如,相同的路径mystore.com/Product可以接受添加产品的POST操作和返回产品的GET操作。
  • 每个路径和操作的预期参数。
  • 每个路径的预期答案。
  • 每个参数和响应对象的类型。

此JSON文件本质上是客户端与服务器之间的合同。这是一个示例文件,描述了一个名为Swagger Petstore的Web服务(为清楚起见,我删除了一些部分):

Json模式
{ 
   "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"
                     }
                  }
               },
...
 


让我们看一下这带来的后果。使用上面的JSON文件,您可以潜在地创建具有完整IntelliSense的C#客户端。最后,您知道所有的路径,操作,它们期望的参数,参数的类型,答案是什么。

有几种工具可以做到这一点。在服务器端,可以使用Swashbuckle.AspNetCore将Swagger添加到ASP.NET并创建指定的JSON文件。对于客户端,您可以使用swagger-codegenAutoRest以JSON格式处理这些文件并生成客户端。让我们看一个如何做到这一点的例子:

将Swagger添加到ASP.NET Server


首先添加NuGet Swashbuckle.AspNetCore软件包ConfigureServices中,注册Swagger生成器:

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

Configure方法Startup.cs文件中,添加:

app.UseSwagger();

最后,控制器内部的操作方法必须标记为[HttpXXX][FromXXX]属性:

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

对于服务器端来说是如此简单。项目启动时将生成一个swagger.json文件,可用于生成客户端。

使用AutoRest从Swagger生成客户端


要开始使用AutoRest,与安装NPMNPM安装-g autorest安装后,您将需要使用AutoRest命令行界面从swagger.json文件创建C#客户端这是一个例子:

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

这将使用生成的C#文件创建一个GeneratedClient文件夹请注意,名称空间和客户端名称已重新定义。将此文件夹添加到Visual Studio中的客户端项目中,如下所示。



您将需要安装Microsoft.Rest.ClientRuntime NuGet软件包,因为生成的代码取决于该软件包。安装后,您可以像使用常规C#类一样使用API​​:

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

您可以在AutoRest 文档中了解一些细微之处而且您将需要使该过程自动化,因此我建议阅读Patrick Svensson的手册以获取一些有用的提示,以及Peter Yausovets的这篇文章

Swagger的问题在于JSON文件是在运行时创建的,因此使CI / CD处理的自动化有些困难。

传统REST vs Swagger vs ReFit


选择时,请注意以下几点。

  • 如果您有一个非常简单的私有REST API,则可能不必担心客户端的生成和通用接口。一项小任务并不能证明付出额外的努力。
  • Swagger支持多种语言,而ReFit仅支持.NET。Swagger还是许多工具,测试,自动化工具和用户界面工具的基础。如果要创建大型公共API,这可能是最佳选择。
  • Swagger比ReFit复杂得多。使用ReFit,只需在服务器和客户端项目中添加一个接口即可。另一方面,使用ReFit,您将必须为每个控制器创建新接口,而Swagger将自动处理此接口。

但是在决定某项之前,请检查第四个选项,它与REST无关。

gRPC


gRPC(gRPC-远程过程调用)是Google开发的开源远程过程调用系统。从某种意义上说,这有点像REST,它提供了一种从客户端向服务器发送请求的方式。但这是非常不同的,这是相同点和不同点:

  • 像REST一样,gRPC是独立于语言的。有适用于所有流行语言的工具,包括C#。
  • gRPC基于合同,并使用.proto文件定义合同。这有点类似于Swagger swagger.json和常见的ReFit接口。可以从这些文件中生成任何编程语言的客户端。
  • 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 .

有两种使用gRPC的方法。对于.NET Core 3.0,有一个用于.NET库的完全托管的gRPC您也可以使用gRPC C#这并不意味着gRPC for .NET会替换gRPC C#让我们来看一个用于.NET的更新gRPC的示例

.NET服务器端的GRPC


这不是指南,而是关于期望的一般概念。这是一个示例控制器在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
        });
    }
}

您需要Startup.cs文件中添加以下内容以进行配置


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

该API在项目的.proto文件中进行了描述

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

.proto文件已添加到.csproj文件中:

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

.NET客户端的GRPC


客户端是从.proto文件生成的代码本身非常简单:


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与REST


GRPC听起来不错。它在引擎盖下更快,更容易。那么我们都应该从REST切换到gRPC吗?答案是,这取决于。以下是一些注意事项:

根据我的经验,使用gRPC和ASP.NET的工作仍然很少。成熟的REST支持将使您更好。就合同通信而言,这很好,除了在REST中有我们已经讨论过的类似替代方案:Swagger和ReFit。

最大的优势是性能。在大多数情况下,根据这些条件,gRPC会快得多。特别是对于Protobuf序列化真正重要的大型有效负载。这意味着对于高负载的服务器而言,这是一个巨大的优势。

在大型ASP.NET应用程序中从REST迁移到gRPC将很困难。但是,如果您具有基于微服务的体系结构,那么逐步完成此过渡将变得容易得多。

其他沟通方式


我根本没有提到其他几种交流方式,但是值得一提的是它们存在:

  • GraphQL是Facebook开发的一种API查询语言。这允许客户端从服务器确切地请求它需要的数据。因此,您只能在服务器上创建一个端点,这将非常灵活,并且仅返回客户端需要的数据。近年来,GraphQL变得非常流行。
  • TcpClient TcpListener ( System.Net.Sockets) TCP. , . , ASP.NET API.
  • UdpClient UDP. TCP , UDP . TCP , , UDP — . UDP , , . : , IP (VoIP).
  • WCF是一种旧技术,主要使用基于SOAP的进程间通信。这是一个我不会讨论的庞大框架,我只能说它在REST和JSON负载中已经失去了流行性。

就这样。我希望这篇文章有趣!所有人的好代码!

All Articles