使用 RESTful Web 服务

将 Web 服务集成到应用程序中是一种常见方案。 本文演示了如何从 Xamarin.Forms 应用程序使用 RESTful Web 服务。

表述性状态转移 (REST) 是一种用于生成 Web 服务的体系结构样式。 Web 浏览器使用 HTTP 谓词来检索网页以及向服务器发送数据,REST 请求使用与此相同的 HTTP 谓词通过 HTTP 发出。 谓词如下:

  • GET - 此操作用于从 Web 服务检索数据。
  • POST - 此操作用于在 Web 服务上创建新的数据项。
  • PUT - 此操作用于更新 Web 服务上的数据项。
  • PATCH - 此操作用于更新 Web 服务上的数据项,方法是描述有关如何修改此项的一组说明。 示例应用程序中未使用此谓词。
  • DELETE - 此操作用于删除 Web 服务上的数据项。

遵循 REST 的 Web 服务 API 称为 RESTful API,并使用以下资源来定义:

  • 一个基 URI。
  • HTTP 方法,例如 GET、POST、PUT、PATCH 或 DELETE。
  • 数据的媒体类型,例如 JavaScript 对象表示法 (JSON)。

RESTful Web 服务通常使用 JSON 消息将数据返回到客户端。 JSON 是一种基于文本的数据交换格式,可生成精简的有效负载,从而导致降低发送数据时的带宽要求。 示例应用程序使用开放源代码 NewtonSoft JSON.NET 库来序列化和反序列化消息。

REST 的简单性使其成为在移动应用程序中访问 Web 服务的主要方法。

当示例应用程序运行时,它连接到本地托管的 REST 服务,如以下屏幕截图所示:

示例应用程序

注意

在 iOS 9 及更高版本中,应用传输安全性 (ATS) 会在 Internet 资源(如应用的后端服务器)与应用之间的强制实施安全连接,从而防止意外泄露敏感信息。 由于为 iOS 9 生成的应用中默认启用了 ATS,因此所有连接都受 ATS 安全要求的约束。 如果连接不符合这些要求,它们将失败并出现异常。

如果无法对 Internet 资源使用 HTTPS 协议和安全通信,则可以选择退出 ATS。 这可以通过更新应用的 Info.plist 文件来实现。 有关详细信息,请参阅应用传输安全性

使用 Web 服务

REST 服务是使用 ASP.NET Core 编写的,提供以下操作:

操作 HTTP 方法 相对 URI 参数
获取待办事项的列表 GET /api/todoitems/
新建待办事项 POST /api/todoitems/ JSON 格式的 TodoItem
更新待办事项 PUT /api/todoitems/ JSON 格式的 TodoItem
删除待办事项 DELETE /api/todoitems/{id}

大多数 URI 的路径中都包含 TodoItem ID。 例如,要删除 ID 为 6bb8a868-dba1-4f1a-93b7-24ebce87e243TodoItem,客户端会向 http://hostname/api/todoitems/6bb8a868-dba1-4f1a-93b7-24ebce87e243 发送 DELETE 请求。 有关示例应用程序中使用的数据模型的详细信息,请参阅数据建模

当 Web API 框架收到请求时,它会将请求路由到操作。 这些操作只是 TodoItemsController 类中的公共方法。 该框架使用路由中间件来匹配传入请求的 URL 并将它们映射到操作。 REST API 应使用属性路由,将应用功能建模为一组资源,其操作由 HTTP 谓词表示。 属性路由使用一组属性将操作直接映射到路由模板。 有关特性路由的详细信息,请参阅 REST API 的特性路由。 有关使用 ASP.NET Core 生成 REST 服务的详细信息,请参阅为本机移动应用程序创建后端服务

HttpClient 类用于通过 HTTP 发送和接收请求。 它提供用于发送 HTTP 请求和从 URI 标识的资源接收 HTTP 响应的功能。 每个请求都作为异步操作发送。 有关异步操作的详细信息,请参阅异步支持概述

HttpResponseMessage 类表示在发出 HTTP 请求后从 Web 服务接收到的 HTTP 响应消息。 它包含有关响应的信息,包括状态代码、标头和任何正文。 HttpContent 类表示 HTTP 正文和内容标头,例如 Content-TypeContent-Encoding。 可使用任何 ReadAs 方法(如 ReadAsStringAsyncReadAsByteArrayAsync)读取内容,具体取决于数据的格式。

创建 HTTPClient 对象

HttpClient 实例在类级别声明,以便只要应用程序需要发出 HTTP 请求,该对象就一直存在,如以下代码示例所示:

public class RestService : IRestService
{
  HttpClient client;
  ...

  public RestService ()
  {
    client = new HttpClient ();
    ...
  }
  ...
}

检索数据

HttpClient.GetAsync 方法用于将 GET 请求发送到 URI 指定的 Web 服务,然后接收来自 Web 服务的响应,如以下代码示例所示:

public async Task<List<TodoItem>> RefreshDataAsync ()
{
  ...
  Uri uri = new Uri (string.Format (Constants.TodoItemsUrl, string.Empty));
  ...
  HttpResponseMessage response = await client.GetAsync (uri);
  if (response.IsSuccessStatusCode)
  {
      string content = await response.Content.ReadAsStringAsync ();
      Items = JsonSerializer.Deserialize<List<TodoItem>>(content, serializerOptions);
  }
  ...
}

REST 服务在 HttpResponseMessage.IsSuccessStatusCode 属性中发送 HTTP 状态代码,以指示 HTTP 请求是成功还是失败。 对于此操作,REST 服务在响应中发送 HTTP 状态代码 200(正常),这表示请求成功,并且请求的信息在响应中。

如果 HTTP 操作成功,则读取响应的内容进行显示。 HttpResponseMessage.Content 属性表示 HTTP 响应的内容,HttpContent.ReadAsStringAsync 方法将 HTTP 内容异步写入字符串。 然后,此内容从 JSON 反序列化为 TodoItemList 实例。

警告

使用 ReadAsStringAsync 方法检索大型响应可能会对性能产生负面影响。 在这种情况下,应直接反序列化响应,以避免对其进行完全缓冲。

创建数据

HttpClient.PostAsync 方法用于将 POST 请求发送到 URI 指定的 Web 服务,然后接收来自 Web 服务的响应,如以下代码示例所示:

public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)
{
  Uri uri = new Uri (string.Format (Constants.TodoItemsUrl, string.Empty));

  ...
  string json = JsonSerializer.Serialize<TodoItem>(item, serializerOptions);
  StringContent content = new StringContent (json, Encoding.UTF8, "application/json");

  HttpResponseMessage response = null;
  if (isNewItem)
  {
    response = await client.PostAsync (uri, content);
  }
  ...

  if (response.IsSuccessStatusCode)
  {
    Debug.WriteLine (@"\tTodoItem successfully saved.");
  }
  ...
}

TodoItem 实例序列化为 JSON 有效负载,以发送到 Web 服务。 然后,此有效负载嵌入在 HTTP 内容的正文中,系统将在使用 PostAsync 方法发出请求之前将其发送至 Web 服务。

REST 服务在 HttpResponseMessage.IsSuccessStatusCode 属性中发送 HTTP 状态代码,以指示 HTTP 请求是成功还是失败。 此操作的常见响应包括:

  • 201 (CREATED) – 请求导致在发送响应之前创建了新资源。
  • 400 (BAD REQUEST) – 服务器无法理解请求。
  • 409 (CONFLICT) – 由于服务器上的冲突而未能执行请求。

更新数据

HttpClient.PutAsync 方法用于将 PUT 请求发送到 URI 指定的 Web 服务,然后接收来自 Web 服务的响应,如以下代码示例所示:

public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)
{
  ...
  response = await client.PutAsync (uri, content);
  ...
}

PutAsync 方法的操作与用于在 Web 服务中创建数据的 PostAsync 方法相同。 但是,从 Web 服务发送的可能响应有所不同。

REST 服务在 HttpResponseMessage.IsSuccessStatusCode 属性中发送 HTTP 状态代码,以指示 HTTP 请求是成功还是失败。 此操作的常见响应包括:

  • 204 (NO CONTENT) – 已成功处理请求,并且响应内容故意为空。
  • 400 (BAD REQUEST) – 服务器无法理解请求。
  • 404 (NOT FOUND) – 请求的资源不存在于服务器上。

删除数据

HttpClient.DeleteAsync 方法用于将 DELETE 请求发送到 URI 指定的 Web 服务,然后接收来自 Web 服务的响应,如以下代码示例所示:

public async Task DeleteTodoItemAsync (string id)
{
  Uri uri = new Uri (string.Format (Constants.TodoItemsUrl, id));
  ...
  HttpResponseMessage response = await client.DeleteAsync (uri);
  if (response.IsSuccessStatusCode)
  {
    Debug.WriteLine (@"\tTodoItem successfully deleted.");
  }
  ...
}

REST 服务在 HttpResponseMessage.IsSuccessStatusCode 属性中发送 HTTP 状态代码,以指示 HTTP 请求是成功还是失败。 此操作的常见响应包括:

  • 204 (NO CONTENT) – 已成功处理请求,并且响应内容故意为空。
  • 400 (BAD REQUEST) – 服务器无法理解请求。
  • 404 (NOT FOUND) – 请求的资源不存在于服务器上。

本地开发

如果要使用 ASP.NET Core Web API 等框架在本地开发 REST Web 服务,则可以同时调试 Web 服务和移动应用。 在此方案中,必须为 iOS 模拟器和 Android Emulator 启用明文 HTTP 流量。 有关配置项目以允许通信的信息,请参阅连接到本地 Web 服务