使用基于 REST 的 Web 服务

Browse sample.浏览示例

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

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

遵循 REST 的 Web 服务 API 是使用以下方法定义的:

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

基于 REST 的 Web 服务通常使用 JSON 消息将数据返回到客户端。 JSON 是一种基于文本的数据交换格式,可生成精简的有效负载,从而导致降低发送数据时的带宽要求。 REST 的简易性使其成为在移动应用中访问 Web 服务的主要方法。

注意

访问 Web 服务通常需要异步编程。 有关异步编程的详细信息,请参阅使用 Async 和 Await 的异步编程

Web 服务操作

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

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

对于显示并发送到 Web 服务以进行存储的数据,.NET MAUI 应用和 Web 服务使用 TodoItem 类对它们进行建模:

public class TodoItem
{
    public string ID { get; set; }
    public string Name { get; set; }
    public string Notes { get; set; }
    public bool Done { get; set; }
}

ID 属性用于唯一标识每个 TodoItem 对象,并由 Web 服务用来标识要更新或删除的数据。 例如,要删除 ID 为 6bb8a868-dba1-4f1a-93b7-24ebce87e243TodoItem,.NET MAUI 应用会向 https://hostname/api/todoitems/6bb8a868-dba1-4f1a-93b7-24ebce87e243 发送 DELETE 请求。

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

创建 HTTPClient 对象

.NET Multi-platform App UI (.NET MAUI) 应用可以通过 HttpClient 类向 Web 服务发送请求的方式使用基于 REST 的 Web 服务。 此类提供发送 HTTP 请求和从 URI 标识的资源接收 HTTP 响应的功能。 每个请求都作为异步操作发送。

应在类级别声明 HttpClient 对象,以确保只要应用需要发出 HTTP 请求,该对象就一直存在:

public class RestService
{
    HttpClient _client;
    JsonSerializerOptions _serializerOptions;

    public List<TodoItem> Items { get; private set; }

    public RestService()
    {
        _client = new HttpClient();
        _serializerOptions = new JsonSerializerOptions
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            WriteIndented = true
        };
    }
    ...
}

JsonSerializerOptions 对象用于配置从 Web 服务接收并发送到 Web 服务的 JSON 有效负载的格式。 有关详细信息,请参阅如何使用 System.Text.Json 实例化 JsonSerializerOptions 实例

检索数据

HttpClient.GetAsync 方法用于向 URI 指定的 Web 服务发送 GET 请求,然后从 Web 服务接收响应:

public async Task<List<TodoItem>> RefreshDataAsync()
{
    Items = new List<TodoItem>();

    Uri uri = new Uri(string.Format(Constants.RestUrl, string.Empty));
    try
    {
        HttpResponseMessage response = await _client.GetAsync(uri);
        if (response.IsSuccessStatusCode)
        {
            string content = await response.Content.ReadAsStringAsync();
            Items = JsonSerializer.Deserialize<List<TodoItem>>(content, _serializerOptions);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine(@"\tERROR {0}", ex.Message);
    }

    return Items;
}

数据作为 HttpResponseMessage 对象从 Web 服务接收。 它包含有关响应的信息,包括状态代码、标头和任何正文。 REST 服务在其响应中发送 HTTP 状态代码,该代码可从 HttpResponseMessage.IsSuccessStatusCode 属性获取,以指示 HTTP 请求成功与否。 对于此操作,REST 服务在响应中发送 HTTP 状态代码 200(正常),这表示请求成功,并且请求的信息在响应中。

如果 HTTP 操作成功,则读取响应的内容。 HttpResponseMessage.Content 属性表示响应的内容,并且属于 HttpContent 类型。 HttpContent 类表示 HTTP 正文和内容标头,例如 Content-TypeContent-Encoding。 然后,使用 HttpContent.ReadAsStringAsync 方法将内容读入 string。 然后,将 string 从 JSON 反序列化为 TodoItem 对象的 List

警告

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

创建数据

HttpClient.PostAsync 方法用于向 URI 指定的 Web 服务发送 POST 请求,然后从 Web 服务接收响应:

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

    try
    {
        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);
        else
            response = await _client.PutAsync(uri, content);

        if (response.IsSuccessStatusCode)
            Debug.WriteLine(@"\tTodoItem successfully saved.");
    }
    catch (Exception ex)
    {
        Debug.WriteLine(@"\tERROR {0}", ex.Message);
    }
}

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

REST 服务在其响应中发送 HTTP 状态代码,该代码可从 HttpResponseMessage.IsSuccessStatusCode 属性获取,以指示 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 服务在其响应中发送 HTTP 状态代码,该代码可从 HttpResponseMessage.IsSuccessStatusCode 属性获取,以指示 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.RestUrl, id));

    try
    {
        HttpResponseMessage response = await _client.DeleteAsync(uri);
        if (response.IsSuccessStatusCode)
            Debug.WriteLine(@"\tTodoItem successfully deleted.");
    }
    catch (Exception ex)
    {
        Debug.WriteLine(@"\tERROR {0}", ex.Message);
    }
}

REST 服务在其响应中发送 HTTP 状态代码,该代码可从 HttpResponseMessage.IsSuccessStatusCode 属性获取,以指示 HTTP 请求成功与否。 此操作的典型响应包括:

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

本地开发

如果你正在使用 ASP.NET Core Web API 等框架在本地开发 REST Web 服务,则可以同时调试 Web 服务和 .NET MAUI 应用。 在这种应用场景中,要从 Android 仿真器和 iOS 模拟器通过 HTTP 使用 Web 服务,必须在 .NET MAUI 应用中启用明文 HTTP 流量。 有关详细信息,请参阅从 Android 仿真器和 iOS 模拟器连接到本地 Web 服务