尝试在 MailKit 中获取文件夹时出现异常,但在首先枚举文件夹时却没有

Exception trying to get folder in MailKit, but not when enumerating folders first

我正在使用 MaikKit,我正在尝试弄清楚如何在我在 Gmail 帐户中设置的 Jim 文件夹中获取邮件。我尝试按如下方式枚举文件夹...

    private void GetFolders() {
      using ImapClient client = new();
      EmailAccount emailAccount = EmailAccountOptions.Value;
      client.Connect(emailAccount.Server, emailAccount.Port, SecureSocketOptions.SslOnConnect);
      client.Authenticate(emailAccount.UserName, emailAccount.Password);

      foreach (FolderNamespace ns in client.PersonalNamespaces) {
        IMailFolder folder = client.GetFolder(ns);
        _msg += $"<br/>Ns: {ns.Path} / {folder.FullName}";
        foreach (IMailFolder subfolder in folder.GetSubfolders()) {
          _msg += $"<br/>&nbsp;&nbsp; {subfolder.FullName}";
        }
      }

      try {
        IMailFolder jim = client.GetFolder(new FolderNamespace('/', "Jim"));
        jim.Open(FolderAccess.ReadOnly);
        _msg += $"<br/>Got Jim, has {jim.Count} email(s)";
      }
      catch (Exception ex) {
        _msg += $"<br/>Ex ({ex.GetType()}): {ex.Message}";
      }
    }

这显示了以下...

Ns: /
   INBOX
   Jim
   [Gmail]
   ✔
   ✔✔
Got Jim, has 1 email(s)

所以,看来我可以毫无问题地访问 Jim。由于这样做的目的是仅访问 Jim,因此不需要枚举。但是,当我删除它时,出现异常...

Ns: /
Ex (MailKit.FolderNotFoundException): The requested folder could not be found

经过反复试验,我发现以下方法有效...

private void GetFolders() {
  using ImapClient client = new();
  EmailAccount emailAccount = EmailAccountOptions.Value;
  client.Connect(emailAccount.Server, emailAccount.Port, SecureSocketOptions.SslOnConnect);
  client.Authenticate(emailAccount.UserName, emailAccount.Password);

  foreach (FolderNamespace ns in client.PersonalNamespaces) {
    IMailFolder folder = client.GetFolder(ns);
    var subs = folder.GetSubfolders();
  }

  try {
    IMailFolder jim = client.GetFolder(new FolderNamespace('/', "Jim"));
    jim.Open(FolderAccess.ReadOnly);
    _msg += $"<br/>Got Jim, has {jim.Count} email(s)";
  }
  catch (Exception ex) {
    _msg += $"<br/>Ex ({ex.GetType()}): {ex.Message}";
  }
}

...但是如果我删除对 folder.GetSubfolders() 的调用,它会引发异常。

如果我更改代码以查找 INBOX 而不是...

IMailFolder jim = client.GetFolder(new FolderNamespace('/', "INBOX"));

...那么即使不调用 folder.GetSubfolders().

也能正常工作

有人知道为什么会这样吗?据我所知,foreach 循环没有做任何应该影响 Jim.

的事情

很明显,使用 ImapClient.GetFolder (FolderNamespace) 的方式并不是正确的使用方式 API。

使用 MailKit 时要考虑的一些通用 API 规则:

  1. 所有 API需要网络I/O的都采用CancellationToken参数。因此,如果 *Client 或 *Folder API 不接受 CancellationToken,这意味着它是缓存查找或其他不需要访问网络的东西。
  2. MailKit APIs 不需要您调用构造函数只是为了将字符串传递给方法;-)

对于 ImapClient.GetFolder(FolderNamespace) API,有一个等效的 ImapFolder.GetFolder(string path, CancellationToken) API 用于此类任务。

(注意:FolderNamespace .ctor 是 public 的唯一原因是因为有人可能想要实现 MailKit IMailStoreIImapClient 接口和将需要能够创建 FolderNamespace 个实例)。

也就是说,我不愿意推荐这个 API,因为 MailKit 的 ImapFolder 旨在通过 ImapFolder.GetSubfolders() and/or ImapFolder.GetSubfolder() APIs 所以 ImapClient.GetFolder() API 是一个巨大的 hack,它必须递归地获取文件夹路径并将它们串在一起。

请记住,ImapFolder 个实例具有 ParentFolder 属性,MailKit API 保证将始终设置。

如果开发人员可以使用完整路径在 ImapClient 级别的树中的任何位置请求 rando 文件夹,这就很难保证。

无论如何...是的,Imapclient.GetFolder (string path, CancellationToken) API 存在并且有效,您可以根据需要使用它,但要让大家知道我讨厌 API .

好的,现在来谈谈为什么如果您删除对 GetSubfolders()

的调用,您的 client.GetFolder(new FolderNamespace(...)) hack 将不起作用

正如您所发现的那样,IMailFolder jim = client.GetFolder(new FolderNamespace('/', "Jim")); 只有 才有效,如果 var subs = folder.GetSubfolders(); 之类的调用在 return 之前存在/Jim 文件夹作为子文件夹之一。

您的问题是:为什么?

(或者您的问题可能是:为什么没有该行它不能工作?

无论哪种方式,答案都是一样的。

MailKit 的 ImapClient 缓存存在于 IMAP 文件夹树中的已知文件夹,以便它可以快速轻松地找到每个文件夹的父文件夹,而不必在每个父节点上显式调用 LIST在构建 ImapFolder 链的路径中(记住:每个 ImapFolder 都有一个 ParentFolder 属性 指向代表其直接父级的 ImapFolder 实例一直回到根)。

所以... GetSubfolders() 调用正在将 /Jim 文件夹添加到缓存中。

由于 GetFolder (new FolderNamesdpace ('/', "Jim")) 调用无法传出网络,它依赖于内部缓存。连接后缓存中唯一存在的文件夹是在 NAMESPACE 命令和 INBOX 中 returned 的文件夹。由于 LIST 响应(作为 GetSubfolders()GetSubfolder() 调用的结果),其他所有内容都被添加到缓存中。