10. 使用 @requires 和 @external 指令
5m

概述

为了为特定食谱推荐更好的(真实的)播放列表,soundtracks 需要从食谱中获取更具体的数据。

在本课中,我们将

  • 了解 @external@requires 联合特定的 s
  • 解析 soundtracks 中使用来自 recipes 的数据

搜索完美的播放列表

The soundtracks 使用 Spotify REST API 作为 。我们有一个方便的端点,我们可以使用它来搜索与食谱相配的完美播放列表: the /search 端点

https://spotify-demo-api-fe224840a08c.herokuapp.com/v1/docs/#/Playlists/search

Spotify REST API search endpoint documentation

The /search 端点接收几个参数,最重要的是:一个 字符串以搜索,以及要返回的结果类型(专辑、艺术家、歌曲、播放列表)。我们已经知道我们想要播放列表,并且我们将把其余的参数保留为默认值。但是查询字符串呢?我们可以提供关于食谱的哪些搜索词或关键字?

The soundtracks 已经可以访问食谱的唯一 id,但我们不能仅仅根据一个 id 来进行有用的推荐!

食谱的 name,然而,可能更有用。我们如何使用 that 来自 recipes 的信息来确定我们从 soundtracks 返回什么?目前,该信息仅存在于 recipes 中。

一个 中的逻辑依赖于来自 另一个 的数据时,我们可以使用 @requires@external 。让我们深入了解它们,看看我们如何将它们一起使用。

使用 @requires@external 指令

The @requires 用于模式中的 上,以表明此特定字段依赖于 其他 的值,这些值由 其他 解决。这 告诉 它需要首先获取那些外部定义的 的值,即使原始 没有请求它们。这些外部定义的字段需要包含在子图的模式中,并用 @external 标记。

在我们的例子中,Recipe recommendedPlaylists 需要 name

在 Hot Chocolate 中,我们将使用注释来表示这些 [Requires][External]

让我们看看这些 的实际操作。

  1. 打开 Recipe.cs 文件。

  2. 我们将使用 RecommendedPlaylists 添加 [Requires] 指令。该指令需要一个 :它所依赖的 。在本例中,该字段为 name

    请注意,此 需要以小写 n 开头,以匹配我们在 schema 中的显示方式。

    Types/Recipe.cs
    [Requires("name")]
    public List<Playlist> RecommendedPlaylists()
  3. 接下来,我们需要添加 Name 。它将是一个简单的 getter 属性,返回一个可为空的 string 类型,以匹配 recipes 的返回值。

    请注意这里的 大写 N。我们遵循 C# 约定。在后台,Hot Chocolate 将其转换为小写 n 以匹配 GraphQL schema 约定。

    Types/Recipe.cs
    public string? Name { get; }
  4. 我们将使用 [External] 属性标记 Name 。这将使 知道 另一个 负责提供此 的数据。

    Types/Recipe.cs
    [External]
    public string? Name { get; }

查询计划的更改

重启服务器,让 rover dev 完成其 操作,然后返回 Sandbox,地址为 https://127.0.0.1:4000。我们可以看到在查询计划中使用这两个 的效果。

让我们再次运行该

query GetRecipeWithPlaylists {
randomRecipe {
name
recommendedPlaylists {
id
name
}
}
}

然后,让我们以文本形式查看

查询计划
QueryPlan {
Sequence {
Fetch(service: "recipes") {
{
randomRecipe {
__typename
id
name
}
}
},
Flatten(path: "recipe") {
Fetch(service: "soundtracks") {
{
... on Recipe {
__typename
id
name
}
} =>
{
... on Recipe {
recommendedPlaylists {
id
name
}
}
}
},
},
},
}

与我们之前拥有的 (在我们引入 @requires@external) 之前)相比,这里有一个细微的差别: 表示(突出显示的行)现在包括 name 。有趣!

让我们对 进行一个小小的调整,并注释掉食谱名称。

query GetRecipeWithPlaylists {
randomRecipe {
# name
recommendedPlaylists {
id
name
}
}
}

运行 ,再次查看 ……查询计划保持不变!

这是因为解析 recommendedPlaylists 的过程 需要name 的值传递给它,即使原始的 没有请求它。因此, 总是先获取食谱的 name 数据,并确保将其传递给 soundtracks

访问必需字段

现在让我们对 name 做点什么!

回到我们的 Recipe.cs 文件,让我们找到参考

Types/Recipe.cs
[ReferenceResolver]
public static Recipe GetRecipeById(string id)
{
return new Recipe(id);
}

我们将更新参数列表以包含 name,并在构造函数调用中使用它。

Types/Recipe.cs
public static Recipe GetRecipeById(
string id,
string? name
) {
return new Recipe(id, name);
}

我们的构造函数也需要考虑 name 值!

Types/Recipe.cs
public Recipe(string id, string? name)
{
Id = id;
if (name != null)
{
Name = name;
}
}

调用 /search 端点

  1. 回到我们的 RecommendedPlaylists 函数参数,让我们添加 SpotifyService

    Types/Recipe.cs
    public List<Playlist> RecommendedPlaylists(
    SpotifyService spotifyService
    )
  2. 不要忘记在文件顶部导入 SpotifyService 包。

    Types/Recipe.cs
    using SpotifyWeb;
  3. 我们还将更新函数签名,使其成为异步函数,并返回 Task 类型。

    Types/Recipe.cs
    public async Task<List<Playlist>> RecommendedPlaylists(
    SpotifyService spotifyService
    )
  4. 在函数主体内部,我们将使用 spotifyService.SearchAsync() 方法。

    提示:将鼠标悬停在该方法上,查看其签名,并点击查看 SpotifyService.cs 文件中的函数定义。

    Types/Recipe.cs
    var response = await spotifyService.SearchAsync(
    this.Name,
    new List<SearchType> { SearchType.Playlist },
    3,
    0,
    null
    );

    第一个参数是搜索词,即食谱名称。我们现在可以通过类访问它(如果你愿意,可以省略 this)!

    第二个参数是 SearchType 枚举的列表:我们只在寻找 Playlist 类型。然后,应返回的最大播放列表数量(limit),以及如果我们正在使用分页,则应返回的 offset(0 是第一页)。最后,对于 include_external 类型,使用 null,我们不需要担心它。

  5. 请记住,RecommendedPlaylists 函数应该返回 List<Playlist> 类型。但 SearchAsync 方法的 response 返回的是 SearchResults 类型,不匹配!我们需要深入了解一下才能获得我们想要的返回类型。

    看起来播放列表结果包含在 response.Playlists 属性中,该属性又包含一个 Items 属性,播放列表 实际 位于该属性中。

    Types/Recipe.cs
    var items = response.Playlists.Items;
  6. items现在是一个 PlaylistSimplified 类型的集合,我们需要将其转换为 Playlist 类型。幸运的是,我们有一个 Playlist(PlaylistSimplified obj) 构造函数可以做到这一点!因此,我们将使用 Select 遍历 items 集合,并在每个元素上调用 new Playlist(item)

    Types/Recipe.cs
    var playlists = items.Select(item => new Playlist(item));
  7. 最后, 函数期望的是 List 类型,所以我们将在返回结果之前调用 ToList() 方法。

    Types/Recipe.cs
    return playlists.ToList();
  8. 完美!您可以将这三行代码合并成行简洁的代码。

    Types/Recipe.cs
    return response.Playlists.Items.Select(item => new Playlist(item)).ToList();
  9. 我们还将删除 硬编码的返回数组。

    Types/Recipe.cs
    - return new List<Playlist>
    - {
    - new Playlist("1", "Grooving"),
    - new Playlist("2", "Graph Explorer Jams"),
    - new Playlist("3", "Interpretive GraphQL Dance")
    - };

查询梦寐以求的查询

一切都准备好了!重启服务器,并给 rover dev 一些时间来整理新的 ,其中包含我们所有的更改。

在 Sandbox 中,让我们以完整、精彩的细节来体验一下梦寐以求的

query GetRecipeWithPlaylists {
randomRecipe {
name
description
ingredients {
text
}
instructions
recommendedPlaylists {
id
name
description
tracks {
explicit
id
name
uri
durationMs
}
}
}
}
任务!

👏👏👏 食谱细节、说明、食材……以及完美的烹饪伴侣音乐播放列表。太棒了!

关键要点

  • @requires 用于指示 schema 中的 依赖于由 其他 解析的 其他 的值。这个 确保即使在原始 中没有明确请求,外部定义的 也会优先被获取。在 Hot Chocolate 中,这个 directive 由 [Requires] 属性表示。
  • @external 用于标记 为外部定义,表示该字段的数据来自另一个 。在 Hot Chocolate 中,这个 directive 由 [External] 属性表示。

下一步

我们对服务器进行了很多更改, rover dev 帮助我们在本地组成的 中测试了所有内容。重点是 本地;要让我们的更改真正“上线”(至少在 教程 的意义上),我们需要告诉 关于这些更改!

在下一课中,我们将看看如何 使用 安全可靠地将这些更改落地。

上一页

分享您对此课程的疑问和评论

此课程目前处于

beta
.您的反馈有助于我们改进!如果您遇到问题或困惑,请告诉我们,我们会帮助您。所有评论都是公开的,必须遵守 Apollo 行为准则。请注意,已解决或已处理的评论可能会被删除。

您需要一个 GitHub 帐户才能在下方发布评论。还没有帐户吗? 请在我们的 Odyssey 论坛上发布评论。