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

问题描述

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

    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.

标签: c#imapmailkit

解决方案


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

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

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

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

(注意:FolderNamespace.ctor 是公开的唯一原因是因为有人可能想要实现 MailKitIMailStoreIImapClient接口,并且需要能够创建FolderNamespace实例)。

也就是说,我不愿意推荐这个 API,因为 MailKit 的设计目的是通过和/或APIImapFolder一次获取 1 级文件夹,因此API 是一个巨大的 hack,必须递归地获取文件夹路径并将它们串在一起。ImapFolder.GetSubfolders()ImapFolder.GetSubfolder()ImapClient.GetFolder()

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

这使得很难保证开发人员是否可以ImapClient使用完整路径在树中的任何位置请求随机文件夹。

无论如何......是的,Imapclient.GetFolder (string path, CancellationToken)API 存在并且它可以工作,如果你愿意,你可以使用它,但要知道我讨厌那个 API。

好的,现在client.GetFolder(new FolderNamespace(...))谈谈为什么如果您删除对GetSubfolders()

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

你的问题是:为什么?

(或者你的问题是:为什么没有那条线它不起作用?

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

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

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

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


推荐阅读