一、开始

现代 web 开发要求与服务器进行交互,而不需要任何麻烦。 这意味着,随着不同 UI 和后端框架的发展,开发人员需要找到一种方法,在没有任何依赖关系的情况下与任何可用框架共享数据。 这意味着应该有一种方式来共享来自服务器的数据与客户端,而不管他们的语言和框架。 为了使共享数据具有一致性,首先想到的是.xml.json。 每个框架都支持这些格式。

在本章中,我们将研究一种架构风格,通过这种风格,我们可以从使用任何语言和框架编写的任何程序中获取或发送数据。 通过我们将要讨论的架构 REST,我们可以引入一些方法,客户机可以很容易地使用这些方法进行数据操作。

本章将涵盖以下主题:

  • 基于 rest 的服务
  • 为什么我们要使用 RESTful 服务? RESTful 和 RESTful 服务的区别
  • 客户机-服务器体系结构
  • ASP.NET Core 和 RESTful 服务

讨论基于 rest 的服务

REST代表表征状态转移。 它是一种体系结构风格,定义了一组构建 web 服务的指导方针。

什么是建筑风格? 它只是一个带有预定义原则的概念。 我们稍后将讨论这些原则。 当您遵循 REST 时,您实际上是在应用中实现了作为 REST 构建块的原则。

然而,REST 的实现肯定会因开发人员的不同而不同。 没有固定的实现风格。 不要与架构模式混淆,它们不是概念,而是实际的实现。 MVC 是一种体系结构模式,因为它有一个固定的结构,它定义了组件之间如何相互作用,而它们不能以不同的方式实现。

下面是一个基于 rest 的服务的非常简单的图:

为了简化问题,考虑前面的图表,它向您展示了具有某些方法的服务,例如GETPOSTPUTDELETE。 这就是这种风格的意义所在。 当您设计服务时,它将包含所有这些方法(其中包含预期的操作),我们可以将其视为基于 rest 的服务,或者称为 RESTful 服务。 更重要的是,服务可以从任何平台和语言构建的应用中调用,因为服务具有标准的体系结构。

如前所述,RESTful 服务是一种支持 REST 的服务。 让我们来讨论一下 REST 的特征,这样我们就可以理解对 RESTful 服务的期望。

其他特点

web 服务的主要构建块是客户机和服务器架构。 从服务器发送的响应实际上是对客户机请求的应答。 这就像您在问一个问题,如果服务器找到了答案,它就会做出响应。 来自服务器的响应实际上是某种格式或表示形式的资源。 常见的格式有.json.xml.pdf.doc等。

休息是无状态的。 无状态表示系统的状态总是不同的。 因此,当一个请求到达服务器时,它被送达并被遗忘。 因此,下一个请求不依赖于前一个请求的状态。 每个请求都由服务器独立处理。

请求在 HTTP 连接中执行。 它们各自采用uniform 资源标识符(URI)的形式。 这个标识符帮助我们在 web 服务器上定位所需的资源。

Roy Fielding 的博士论文《Architectural Styles and the Design of Network-Based Software Architectures》中定义了 REST。 以下是从他的研究中摘录的一些要点:

  • 像许多分布式体系结构一样,REST 增加了层、无状态和缓存。
  • REST 提高了效率、互操作性和整体性能。
  • REST 通过遵循一组关于如何识别和操作资源的规则,以及通过元数据简化关于其操作的描述的过程,从而引入一致性,以便传递的消息将是自解释的。 我们将更多地讨论这种均匀性,它被称为uniform 接口
  • 由于 REST 是一种体系结构风格,因此可以使用任何语言或平台开发服务,只要它支持 HTTP。

You can read the whole dissertation at https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm

面向资源的架构

web 上的每个资源都有一个唯一的标识符,也就是 URI。 统一资源定位器(URL)是当今 web 上使用的最常见的 URI 类型。 URLhttps://www.packtpub.com/用于标识和定位数据包发布站点。

让我们快速地看一下该架构的图片。 在下面的图中,客户端试图通过标识符(URL)访问资源。 资源存在于服务器上,并且有一个可以在请求时返回给客户端的表示:

顾名思义,URL 只绑定到一个资源; 因此,如果我想将某人指向某个资源,我可以很容易地在电子邮件、聊天等中共享该标识符。

如果这些标识符以公司或资源名称命名,则很容易记住。 最好的例子是www.google.com,它很容易记住,因为它的名字是谷歌。 因此,我们可以通过口碑传播资源链接,您可以在几秒钟内将其输入到浏览器中,如 Chrome 或 Firefox。

您可能会在特定的网页上发现超链接,这些超链接链接到另一个网站以获取另一个资源。 这意味着,由于超链接,资源现在是相互连接的。

这些相互连接的资源形成了面向资源的架构。 通过使用目标资源 URI,超链接可以方便地从一个资源导航到另一个资源。

For example, in HTML, you link to another resource through the anchor element. The following is one anchor element that links to Packt's IoT book catalog page:

<a href="/go?url=https://www.packtpub.com/" target="_blank" tech/Internet%20of%20 Things">Packt IoT Books</a>

默认情况下,锚元素呈现为带下划线的文本。 当你将鼠标悬停在它上面时,你可以在底部看到附加的 URI,如下图所示:

您可以单击锚定文本(Packt IoT Books),然后它会触发一个针对目标资源 URI 的GET请求。

请注意,当您单击超链接时,您将进入一个实际上是资源表示的 web 页面。 最常见的表示形式是 HTML 格式。 其他一些常见的格式是(X)HTML、JPEG、GIF、WMV、SWF、RSS、ATOM、CSS、JavaScript/JSON 等等。 当浏览器接收到其中一种表示时,它会尝试解析它,如果解析成功,则会呈现它以供查看。

尤里

关于资源,我们已经谈了很多。 它们实际上是我们在一个特定的网站上看到的页面。 然而,HTTP 中的资源不仅仅是 HTML 网页形式的简单文件。 通常,资源被定义为任何可以由 URI 唯一标识的信息片段,例如http://packtpub.com/

让我们先讨论一下 uri。 URI 由几个组件组成:URI 方案名称(如httpftp)是第一部分,后面是冒号字符。 冒号之后是层次结构部分:

<scheme name> : <hierarchical part> [ ? <query> ] [ # <fragment> ]

让我们分析一个 URI:

https://www.flipkart.com/men/tshirts/pr?sid=2oq%2Cs9b%2Cj9y

让我们分解前面的 URI:

  • 方案名称为https
  • 方案名称后面是层次结构部分//www.flipkart.com/men/tshirts/pr。 分层部分从//开始。
  • 层次结构部分还包含一个可选查询,在本例中为sid=2oq%2Cs9b%2Cj9y

下面是一个包含可选片段部分的 URI 示例:

https://en.wikipedia.org/wiki/Packt#PacktLib

其他约束

REST 由六个约束定义,如下图所示。 其中一个是可选的:

每个约束都强制执行要遵循的服务的设计决策。 如果没有遵循它,则不能将该服务标记为 RESTful 服务。 让我们逐个讨论这些约束条件。

客户机-服务器体系结构

客户端或服务的消费者不应该担心服务器如何处理数据并将其存储在数据库中。 类似地,服务器不需要依赖客户机的实现,特别是 UI。

想想一个没有太多 UI 的物联网设备或传感器。 然而,它与服务器交互以使用 api 存储数据,这些 api 被编程为在特定事件上触发。 假设您正在使用一个物联网设备,当您的汽车没有汽油时,它会提醒您。 当物联网设备中的传感器检测到汽油短缺时,它调用已配置的 API,然后最终向所有者发送警报。

这意味着客户端和服务器不是一个实体,彼此可以独立存在。 它们可以独立设计和发展。 现在您可能会问,在不了解服务器架构的情况下,客户机如何工作,反之亦然? 嗯,这就是这些约束的意义所在。 当与客户端进行交互时,服务提供了关于其性质的足够信息:如何使用它,以及可以使用它执行哪些操作。

随着本节的继续,您将意识到客户机和服务器之间绝对没有关系,如果它们完美地遵守所有这些约束,它们可以完全解耦。

无状态的

术语无状态意味着应用在特定时间内保持的状态可能不会持续到下一个时刻。 RESTful 服务不维护应用的状态,因此它是无状态的。

RESTful 服务中的请求不依赖于过去的请求。 服务独立地处理每个请求。 另一方面,当执行请求时,有状态服务需要记录应用的当前状态,以便它可以按照需要执行下一个请求。

此外,由于没有这些复杂性,无状态服务变得非常容易托管。 由于我们不需要担心应用的状态,它变得容易实现,维护也变得顺畅。

缓存

为了避免在每个请求中生成相同的数据,有一种称为缓存的技术用于在客户端或服务器端存储数据。 这些缓存的数据可以在需要的时候用作进一步的参考。

在使用缓存时,正确地管理它是很重要的。 原因很简单。 我们存储的数据不会被来自服务器的新数据所取代。 虽然这是提高服务性能的一个优势,但同时,如果我们不小心在其生命周期中缓存和配置什么,我们可能最终会看到过时的数据。 例如,假设我们在网站上显示黄金的实时价格,并缓存了这个数据。 下次价格变化时,除非我们使之前存储的缓存过期,否则不会反映出来。

让我们来看看不同种类的 HTTP 头以及如何配置缓存:

| 头部 | 应用 | | 日期 | 生成表示的日期和时间。 | | 最后修改 | 服务器最后修改此表示的日期和时间。 | | cache - control | 用于控制缓存的 HTTP 1.1 报头。 在这张表之后,我们将更详细地研究这一点。 | | 到期 | 此头帮助标记此表示的过期日期和时间。 | | 年龄 | 表示从服务器获取表示后的时间(以秒为单位)。 |

前面 5 个头的配置取决于服务的性质。 以提供黄金价格的服务为例,理想情况下,它的缓存年龄限制应该尽可能低,或者甚至关闭缓存,因为用户在每次引用该站点时都应该看到最新的结果。

然而,一个包含许多图片的网站几乎不会改变或更新它们。 在这种情况下,可以配置缓存来更长时间地存储它们。

这些头值与缓存控制头一致,以检查缓存结果是否仍然有效。

以下是缓存控制头中最常见的值:

| 指令 | 应用 | | 公共 | 这是默认指令。 这允许每个组件缓存表示。 | | 私人 | 只有客户端或服务器可以缓存表示。 然而,中间组件受到限制。 | | no - cache /没有商店 | 有了这个值,我们可以关闭缓存。 | | 信息 | 这个值是日期和时间在date报头中提到之后的秒数,它表示表示的有效性。 | | s-maxage | 它的作用与 max-age 相同,但只针对中间缓存。 | | must-revalidate | 这就是说,如果超过了最大年龄,则必须重新验证表示。 | | proxy-validate | 这与 max-revalidate 的作用相同,但只针对中间缓存。 |

随需应变代码(可选)

正如短语随需应变所建议的,服务可能会尝试在客户端上执行代码来扩展功能。 然而,这是可选的,并不是每个服务都这样做。

考虑一个 web 应用的例子,它调用票务服务来获取所有可用的票务。 该服务希望始终在警报中显示此信息。 为了做到这一点,服务可以连同数据一起返回一个 JavaScript 代码,该代码有一条带有可用票证数量的警告消息。 因此,一旦客户端接收到服务的响应,就会执行警报并显示数据。

统一的接口

当我们遇到接口这个词时,我们首先想到的是解耦。 我们创建了具有松散耦合体系结构的接口,在 RESTful 中可以看到相同类型的体系结构。

在实现 REST 时,我们使用相同的概念将客户机与 REST 服务的实现解耦。 然而,为了实现客户机和服务之间的这种解耦,需要定义每个 RESTful 服务支持的标准。

注意前面一行中的单词standard。 我们在世界上有这么多的服务,很明显,消费者的数量超过了服务的数量。 因此,我们在设计服务时必须遵循一些规则,因为每个客户都应该轻松地理解服务。

REST 由四个接口约束定义:

  • 资源标识:URI 用于标识资源。 该资源是一个 web 文档。
  • 通过表示操作资源:当客户端拥有给定的资源以及任何元数据时,他们应该有足够的信息来修改或删除资源。 因此,例如,GET意味着您想要检索关于 uri 标识的资源的数据。 您可以使用HTTP方法和 URI 来描述操作。
  • 自描述消息:传递的消息应该包含足够的数据信息,以便理解和处理进一步的操作。 MIME 类型用于此目的。
  • 超媒体作为应用状态(HATEOAS)的引擎:从服务返回的表示应该包含所有未来的操作作为链接。 这就像访问一个网站,在其中你会发现不同的超链接为你提供不同类型的可用操作。

HTTP 1.1 提供了一组方法,称为动词。 在我们的服务中实现这些动词将标志着它们是标准化的。 重要的动词如下:

| 方法 | 服务器上执行的操作 | 方法类型 | | GET | 读/检索资源。 | 安全 | | PUT | 插入一个新资源,或者更新已经存在的资源。 | 幂等 | | POST | 插入一个新的资源。 也可以用来更新现有资源。 | Nonidempotent | | DELETE | 删除资源。 | 幂等 | | OPTIONS | 获取资源允许的所有操作的列表。 | 安全 | | HEAD | 只返回没有响应体的响应头。 | 安全 |

除了方法类型列之外,前面的表是不言自明的。 我来澄清一下。

在服务上执行 afe 操作不会对资源的原始值产生任何影响。 因为GETOPTIONSHEAD动词只检索或读取与资源相关的内容,而不进行更新,所以它们是安全的。

幂等(可重复)操作无论执行多少次,结果都是相同的。 例如,当您执行DELETEPUT操作时,您实际上是在操作特定的资源,并且可以毫无问题地重复该操作。

POST versus PUT: This is a very common topic of discussion on the internet, and one that is very easy to understand. Both POST and PUT can be used to insert or update a resource. However, POST is nonidempotent, meaning that it isn't repeatable. The reason is that each time you call using POST, it will create a new resource if you don't provide the exact URI of the resource. The next time you use POST, it will again create a new resource. However, in PUT, it will first validate the existence of the resource. If it exists, it will update it; otherwise, it will create it.

更多的解释

在所有可用的方法中,GET是最常用的方法,因为它用于获取资源。

HEAD方法将只返回带有空主体的响应头。 这通常只在我们不需要资源的整个表示时才需要。

方法用于获取资源上允许或可用操作的列表。

考虑以下请求:

OPTIONS http://packtservice.com/Authors/1 HTTP/1.1 HOST: packtservice

如果请求被授权和验证,它可能会返回如下内容:

200 OK Allow: HEAD, GET, PUT

响应实际上是说,只能使用所有这些方法调用服务。

确保您根据它们的规范使用 HTTP 方法。 如果您将服务设计为允许GET,但在其中执行删除操作,那么客户机将会感到困惑。 当它们尝试GET某件事时,它实际上会删除资源,这很奇怪。

下面是一个使用GET发出的请求,但它实际上删除了服务器内部的资源(想象一下):

GET http://packtservice.com/DeleteAuthor/1 HTTP/1.1 HOST: packtservice

前面的请求可能会起作用并删除资源,但这并不被认为是 RESTful 设计。 推荐的操作是使用DELETE方法删除资源,如下所示:

DELETE http://packtservice.com/Authors/1 HTTP/1.1 HOST: packtservice

说明 POST 与 PUT

POSTPUT的使用可以总结为以下两点:

  • PUT是幂等的——它可以重复,每次都产生相同的结果。 如果资源不存在,它将创建它; 否则,它将更新它。
  • POST是非幂等的——如果它被多次调用,将创建多个资源。

上述这些动词之间的差别只是一般的差别。 然而,有一个非常重要和显著的区别。 当使用PUT时,需要指定资源的完整 URI。 否则,它不会工作。 例如,下面的代码将不起作用,因为它没有指定作者的确切 URI,这可以通过指定一个 ID 来完成:

PUT http://packtservice.com/Authors/

要解决这个问题,你可以使用如下方法发送一个带有这个 URI 的 ID:

PUT http://packtservice.com/Authors/19
created/updated.

这意味着将处理 ID 为19的作者,但是如果该 ID 不存在,将首先创建它。 带有此 URI 的后续请求将被视为修改 ID 为19的作者资源的请求。

另一方面,如果我们像下面这样对一个POST请求执行同样的操作,它将使用已发布的数据创建一个新的 author 资源:

POST http://packtservice.com/Authors/

有趣的是,如果您重复这一操作,您将对具有相同数据的重复记录负责。 这就是为什么它在自然界中是非幂等的原因。

注意以下带有 ID 的带有POST的请求。 与PUT不同,POST不会将此作为新资源,如果该资源不存在的话。 它将始终被视为一个更新请求:

POST http://packtservice.com/Authors/19
updated.

以下是本节的重点:

  • PUT创建或更新一个资源,只要调用相同的 URI
  • 如果资源已经存在,则PUTPOST的行为相同
  • 没有 ID 的POST将在每次触发资源时创建一个资源

分层系统

大多数现代应用都是使用多层设计的,基于 rest 的服务也应该如此。 在分层系统中,每一层只能看到或了解层次中的下一层。

分层架构有助于提高代码的可读性,隐藏复杂性,并提高代码的可维护性。 假设您有一个层,从身份验证到数据库操作,所有事情都在其中进行。 绝对不建议这样做,因为主要组件(如身份验证、业务逻辑和数据库操作)没有被分离出来。

因此,这个约束是对 RESTful 服务的期望,而且没有客户机实际上可以说它已连接到最后一层。

RESTful 服务的优缺点

下面是 RESTful 服务的一些优缺点:

优势

使用 RESTful 服务的优点如下:

  • 不依赖于平台或任何编程语言
  • 通过 HTTP 的标准化方法
  • 它不会在服务器上存储客户机的状态
  • 支持缓存
  • 可访问的任何类型的客户端,如移动,web,或桌面

缺点

虽然 RESTful 服务有优点,但也有缺点。让我们来看看 RESTful 服务的缺点:

  • 如果不正确地遵循标准,客户就很难理解这些标准
  • 由于没有提供这样的元数据,文档就会出现问题
  • 如果没有遵循这样的过程来限制对资源的访问,那么安全性就是一个问题

ASP.NET Core 和 RESTful 服务

. net Core 被定义为一个跨平台、开源、云准备和模块化的。net 平台,用于创建可以在任何地方(Windows、Linux 和 macOS)运行的现代 web 应用、微服务、库和控制台应用。

ASP.NET Core 是一个免费的开源 web 框架,是 ASP.NET 的下一代。 它是一个模块化的框架,由一些小的框架组件包组成,这些组件包可以在完整的。net framework、Windows 和跨平台的。net Core 上运行。

这个框架是彻底重写的。 它统一了以前分离的 ASP。 asp.net MVC 和 asp.net.NET Web API 转换为单个编程模型。

ASP.NET Web API 的建立是为了将 Web /HTTP 编程模型映射到。NET 框架编程模型。 它使用熟悉的结构,如控制器、动作、过滤器等,这些结构在 ASP 中使用。 净 MVC。

ASP.NET Web API 是在 asp.net 的基础上设计的.NET MVC 运行时,以及一些简化 HTTP 编程的组件。 我们可以利用 Web API 技术在。net 框架的服务器上执行操作; 然而,要实现 RESTful,我们应该遵循本章前面讨论过的标准。 幸运的是,Web API 自动管理 HTTP 的所有底层传输细节,同时维护所有必需的约束。

由于 Web API 提供的一致性,强制 RESTful 原则,诸如移动设备、Web 应用、云等客户端可以轻松地访问它,没有任何问题:

在 ASP.NET Core、MVC 和 Web API 是不同的,它们分别继承了ControllerApiController类。 另一方面,在 ASP.NET Core,它们遵循相同的结构。

下面是 MVC 和 Web API 的解决方案资源管理器视图。 你可以看到它们有一个相似的结构:

下面是一个控制器,当我点击文件|新|项目| ASP 自动创建.NET Core Web Application | Web API。 你可以看到控制器的基类是Controller而不是ApiController:

namespace WebAPIExample.Controllers
{
  [Route("api/[controller]")]
  public class ValuesController : Controller
  {
    // GET api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
      return new string[] { "value1", "value2" };
    }
    // GET api/values/5
    [HttpGet("{id}")]
    public string Get(int id)
    {
      return "value";
    }
    // POST api/values
    [HttpPost]
    public void Post([FromBody]string value)
    { }
    // PUT api/values/5
    [HttpPut("{id}")]
    public void Put(int id, [FromBody]string value)
    { }
    // DELETE api/values/5
    [HttpDelete("{id}")]
    public void Delete(int id)
    {  }
  }
}

现在不要担心代码; 我们将在本书的后面讨论一切。

总结

REST 定义了如何通过附加的约束使用统一接口、如何识别资源、如何通过表示操作资源以及如何包含使消息自描述的元数据。

web 是建立在 HTTP 的统一接口之上的,其重点是与资源及其表示进行交互。 REST 不绑定任何特定的平台或技术; web 是唯一一个完全体现 REST 的主要平台。 基于 rest 的 web 服务体系结构的基本风格是客户机-服务器。

在这里,客户端请求资源,服务器处理并响应所请求的资源。 服务器的响应是基于用户和平台独立的。 关注点分离是客户机-服务器约束背后的原则。 因为在客户机-服务器体系结构中,存储和用户界面分别由服务器和客户机扮演角色,所以它提高了跨多个平台的用户界面的可移植性。

我们应该为客户端开发人员记录每个资源和 URI。 我们可以使用任何格式来结构化我们的文档,但它应该包含关于资源、uri、可用方法和访问服务所需的任何其他信息的足够信息。

Swagger 是一种用于文档目的的工具,它在一个屏幕上提供了关于 API 端点的所有信息,在这个屏幕上您可以可视化 API 并通过发送参数来测试它。 开发人员可以使用另一个名为Postman的工具来测试 api。 这两种工具将在本书接下来的章节中通过示例进行解释。

ASP。 净 Web API 是一个开发环境构建开发 RESTful Web 服务允许应用发送和接收 HTTP 请求(Web 请求)很容易和类型的基础上执行操作请求,它(如提供用户信息时,考虑到他们的 ID,等等)。

基于 ASP 的 Web API 设计.NET Core 遵循与 MVC 相同的编程模型进行了简化。

在下一章中,我们将通过设置环境开始编码,并研究 Web API 中 HTTP 动词的各种基础知识。