【微软技术栈】C#.NET 控制台应用发送HTTP请求

本文内容

  1. 先决条件
  2. 创建客户端应用
  3. 发出 HTTP 请求
  4. 反序列化 JSON 结果
  5. 配置反序列化
  6. 重构代码
  7. 反序列化更多属性
  8. 添加日期属性

本章将生成应用,用于向 GitHub 上的 REST 服务发出 HTTP 请求。 该应用读取 JSON 格式的信息并将 JSON 转换为 C# 对象。 JSON 转换为 C# 对象称为“反序列化”。演示如何:

  • 发送 HTTP 请求。
  • 反序列化 JSON 响应。
  • 配置具有特性的反序列化。

1、先决条件

  • .NET SDK 6.0 或更高版本
  • 代码编辑器如 [Visual Studio Code(开源、跨平台编辑器)。 可以在 Windows、Linux、macOS 或 Docker 容器中运行此示例应用。

2、创建客户端应用

  1. 打开命令提示符并为应用新建目录。 将新建的目录设为当前目录。

  2. 在控制台窗口中输入以下命令:

    dotnet new console --name WebAPIClient
    

    该命令将为“Hello World”基本应用创建入门文件。 项目名称为“WebAPIClient”。

  3. 导航到“WebAPIClient”目录并运行应用。

    cd WebAPIClient
    
    dotnet run
    


    dotnet run 自动运行 dotnet restore 还原应用需要的依赖项。 还会按需运行 dotnet build。 你应该会看到应用输出 "Hello, World!"。 在终端中,按 Ctrl+C 可停止应用。

3、发出 HTTP 请求

此应用调用 Github API 以获取 .NET Foundation 伞下的项目相关信息。 终结点为 https://api.github.com/orgs/dotnet/repos。 若要检索信息,它会发出 HTTP GET 请求。 此外,浏览器也发出 HTTP GET 请求,以便你可以将相应的 URL 粘贴到浏览器地址栏,查看将要接收和处理的信息。

使用 HttpClient 类发出 HTTP 请求。 HttpClient 仅支持其长时间运行 API 的异步方法。 因此,采取下列步骤创建异步方法,并从 Main 方法中调用该方法。

  1. 在项目目录中打开 Program.cs 文件,并将其内容替换为以下内容:

    await ProcessRepositoriesAsync();
    
    static async Task ProcessRepositoriesAsync(HttpClient client)
    {
    }
    

    此代码:

    • 将 Console.WriteLine 语句替换为调用使用 await 关键字的 ProcessRepositoriesAsync
    • 定义空 ProcessRepositoriesAsync 方法。
  2. 在 Program 类中,使用 HttpClient 将内容替换为以下 C# 来处理请求和响应

    using System.Net.Http.Headers;
    
    using HttpClient client = new();
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
    client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation Repository Reporter");
    
    await ProcessRepositoriesAsync(client);
    
    static async Task ProcessRepositoriesAsync(HttpClient client)
    {
    }
    

    此代码:

    • 为所有请求设置 HTTP 标头:
      • 接受 JSON 响应的 Accept 标头
      • 一个 User-Agent 标头。 标头均由 GitHub 服务器代码进行检查,需使用标头检索 GitHub 中的信息。
  3. 在 ProcessRepositoriesAsync 方法中调用 GitHub 终结点,该终结点返回 .NET foundation 组织下的所有存储库列表:

     static async Task ProcessRepositoriesAsync(HttpClient client)
     {
         var json = await client.GetStringAsync(
             "https://api.github.com/orgs/dotnet/repos");
    
         Console.Write(json);
     }
    

    此代码:

    • 等待从调用 HttpClient.GetStringAsync(String) 方法返回的任务。 此方法将 HTTP GET 请求发送到指定的 URI。 响应正文以 String 形式返回,任务结束时可用。
    • 响应字符串 json 将输出到控制台。
  4. 生成并运行应用。

    dotnet run
    

    因为 ProcessRepositoriesAsync 目前含有一个 await 运算符,所以没有生成警告。 输出 JSON 文本的长显示。

4、反序列化 JSON 结果

以下步骤将 JSON 响应转换为 C# 对象。 使用 System.Text.Json.JsonSerializer 类将 JSON 反序列化为对象。

  1. 创建名为“Repository.cs”的文件并添加以下代码:

    public record class Repository(string name);
    

    先前的代码定义了一个类,用于表示从 GitHub API 返回的 JSON 对象。 你将使用此类来显示存储库名称列表。

    存储库对象的 JSON 包含数十个属性,但仅对 name 属性进行反序列化。 序列化程序自动忽略目标类中没有匹配项的 JSON 属性。 借助此功能,可更轻松地创建仅适用于 JSON 数据包中一个子集字段的类型。

    C# 约定是属性名称首字母大写,但此处的 name 属性首字母小写,因为这与 JSON 中的内容完全匹配。 稍后你将了解如何使用与 JSON 属性名称不匹配的 C# 属性名称。

  2. 使用序列化程序将 JSON 转换成 C# 对象。 使用以下行替换 ProcessRepositoriesAsync 方法中 GetStringAsync(String) 的调用:

    await using Stream stream =
        await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
    var repositories =
        await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
    

    更新的代码将 GetStringAsync(String) 替换为 GetStreamAsync(String)。 序列化程序方法使用流代替字符串作为其源。


    JsonSerializer.DeserializeAsync<TValue>(Stream, JsonSerializerOptions, CancellationToken) 的第一个自变量是 await 表达式。 尽管到目前为止,你只在赋值语句中见过,但 await 表达式可以出现在代码中的几乎任何位置。 其他两个参数 JsonSerializerOptions 与 CancellationToken 均可选,并在代码片段中省略。

    DeserializeAsync 方法为泛型,这意味着必须为应为从 JSON 文本创建的对象类型提供类型参数。 在此示例中,你要反序列化到 List<Repository>,即另一个泛型对象 System.Collections.Generic.List<T>。 List<T> 类存储对象的集合。 类型参数声明存储在 List<T> 中的对象的类型。 类型参数是 Repository 记录,因为 JSON 文本表示存储库对象的集合。

  3. 添加代码以显示每个存储库的名称。 将以下代码行:

    Console.Write(json);
    

    使用以下代码:

    foreach (var repo in repositories ?? Enumerable.Empty<Repository>())
        Console.Write(repo.name);
    
  4. 文件顶部应存在以下 using 指令:

    using System.Net.Http.Headers;
    using System.Text.Json;
    
  5. 运行应用。

    dotnet run
    

    输出是 .NET Foundation 中的存储库名称列表。

5、配置反序列化

  1. 在 Repository.cs 中,将文件内容替换为以下 C#。

    using System.Text.Json.Serialization;
    
    public record class Repository(
        [property: JsonPropertyName("name")] string Name);
    

    此代码:

    • 将 name 属性的名称更改为 Name
    • 添加 JsonPropertyNameAttribute 以指定此属性在 JSON 中的显示方式。
  2. 在“Program.cs”中,更新代码以使用首字母大写的 Name 属性:

    foreach (var repo in repositories)
       Console.Write(repo.Name);
    
  3. 运行应用。

    输出相同。

6、重构代码

ProcessRepositoriesAsync 方法可以执行异步工作,并返回一组存储库。 更改该方法以返回 Task<List<Repository>>,并将写入到控制台的代码移动到其调用方附近的控制台。

  1. 更改 ProcessRepositoriesAsync 的签名,以返回可生成 Repository 对象列表的任务:

    static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
    
  2. 处理 JSON 响应后返回存储库:

    await using Stream stream =
        await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
    var repositories =
        await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
    return repositories ?? new();
    

    编译器生成返回值的 Task<T> 对象,因为你已将此方法标记为 async

  3. 修改 Program.cs 文件,将对 ProcessRepositoriesAsync 的调用替换为以下内容以捕获结果并将每个存储库名称写入控制台。

    var repositories = await ProcessRepositoriesAsync(client);
    
    foreach (var repo in repositories)
        Console.Write(repo.Name);
    
  4. 运行应用。

    输出相同。

7、反序列化更多属性

以下步骤添加代码来处理收到的 JSON 数据包中的多个属性。 你可能不希望处理每个属性,但却希望另外添加一些属性演示 C# 的其他功能。

  1. 将 Repository 类的内容替换为以下 record 订阅:

    using System.Text.Json.Serialization;
    
    public record class Repository(
        [property: JsonPropertyName("name")] string Name,
        [property: JsonPropertyName("description")] string Description,
        [property: JsonPropertyName("html_url")] Uri GitHubHomeUrl,
        [property: JsonPropertyName("homepage")] Uri Homepage,
        [property: JsonPropertyName("watchers")] int Watchers);
    

    Uri 和 int 类型具有转换字符串表示形式的内置功能。 无需额外代码即可从 JSON 字符串格式反序列化为这些目标类型。 如果 JSON 数据包包含不会转换为目标类型的数据,则序列化操作将引发异常。

  2. 在 Program.cs 文件中更新 foreach 循环以显示属性值:

    foreach (var repo in repositories)
    {
        Console.WriteLine($"Name: {repo.Name}");
        Console.WriteLine($"Homepage: {repo.Homepage}");
        Console.WriteLine($"GitHub: {repo.GitHubHomeUrl}");
        Console.WriteLine($"Description: {repo.Description}");
        Console.WriteLine($"Watchers: {repo.Watchers:#,0}");
        Console.WriteLine();
    }
    
  3. 运行应用。

    现在列表包含其他属性。

8、添加日期属性

在 JSON 响应中以此方式设置上次推送操作的日期格式:

2016-02-08T21:27:00Z

此格式适用于协调世界时 (UTC),因此反序列化的结果是 DateTime 值,其 Kind 属性为 Utc。

若要获取你所在时区表示的日期和时间,必须写入自定义转换方法。

  1. 在“Repository.cs”中添加日期和时间的 UTC 表示形式的属性和只读 LastPush 属性(该属性返回转换为当地时间的日期),文件应如下所示:

    using System.Text.Json.Serialization;
    
    public record class Repository(
        [property: JsonPropertyName("name")] string Name,
        [property: JsonPropertyName("description")] string Description,
        [property: JsonPropertyName("html_url")] Uri GitHubHomeUrl,
        [property: JsonPropertyName("homepage")] Uri Homepage,
        [property: JsonPropertyName("watchers")] int Watchers,
        [property: JsonPropertyName("pushed_at")] DateTime LastPushUtc)
    {
        public DateTime LastPush => LastPushUtc.ToLocalTime();
    }
    

    LastPush 属性使用 get 访问器的 expression-bodied member 进行定义。 不存在 set 访问器。 通过省略 set 访问器,可采用 C# 语言定义“只读”属性。 (是的,可以在 C# 中创建只写属性,但属性值受限。)

  2. 在“Program.cs”中再次添加另一个输出语句:

    Console.WriteLine($"Last push: {repo.LastPush}");
    
  3. 完整的应用应类似于以下 Program.cs 文件:

    using System.Net.Http.Headers;
    using System.Text.Json;
    
    using HttpClient client = new();
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/vnd.github.v3+json"));
    client.DefaultRequestHeaders.Add("User-Agent", ".NET Foundation Repository Reporter");
    
    var repositories = await ProcessRepositoriesAsync(client);
    
    foreach (var repo in repositories)
    {
        Console.WriteLine($"Name: {repo.Name}");
        Console.WriteLine($"Homepage: {repo.Homepage}");
        Console.WriteLine($"GitHub: {repo.GitHubHomeUrl}");
        Console.WriteLine($"Description: {repo.Description}");
        Console.WriteLine($"Watchers: {repo.Watchers:#,0}");
        Console.WriteLine($"{repo.LastPush}");
        Console.WriteLine();
    }
    
    static async Task<List<Repository>> ProcessRepositoriesAsync(HttpClient client)
    {
        await using Stream stream =
            await client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
        var repositories =
            await JsonSerializer.DeserializeAsync<List<Repository>>(stream);
        return repositories ?? new();
    }
    
  4. 运行应用。

    输出包括上次推送到每个存储库的日期和时间。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吉特思米(gitusme)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值