在 C# .NET 中读取 MS Outlook PST 文件

Conversation Thread 是对具有公共线程主题的消息的一系列回复。对话中的消息可以以各种方式显示,例如按层次或时间顺序。要显示消息线程,电子邮件应用程序会识别消息回复。最流行的电子邮件文件格式提供此功能。 对话线程使读者可以快速了解对话的整体结构,突出对话的某些要点,并分析重要信息。 在本文中,我们将重点介绍使用 Aspose.Email 的 PST/MAPI 功能按对话查找和分组邮件。为此,我们将实现一个示例代码,该代码将遍历给定文件夹中的消息,按对话对它们进行分组,然后将每个对话保存到单独的磁盘目录中。

用于支持对话线程的 MAPI 属性

由于 pst 中的消息存储为一组 MAPI 属性,因此我们需要定义与收集消息回复相关联的 MAPI 属性。 这在 Microsoft Docs 部分 中有描述。 可以看出,PidTagConversationIndex 属性允许准确地确定消息是否与某个对话相关联。该属性还指示对话线程中的相对消息位置。 访问页面 了解有关 PidTagConversationIndex 属性的更多信息。标头是 PidTagConversationIndex 属性值的前 22 个字节。它是确定消息是否属于某个会话线程的数据部分。

C# .NET API 读取 Outlook PST 文件

读取 PST 文件,我们将使用 Aspose.Email for .NET。这是一个使用 .NET 实现电子邮件处理应用程序的了不起的库。使用该库,您可以轻松处理许多不同的电子邮件文件格式。您可以通过 NuGet下载 其 DLL 安装 Aspose.Email for .NET。

PM> Install-Package Aspose.Email

按对话线程对 PST 中的消息进行分组

要按对话对 PST 中的消息进行分组,我们需要以下内容:

  • 首先,创建一个“ConversationThread”类。它是一个容器,用于对对话中的消息进行分组。
  • 然后,创建一种按对话搜索和分组消息的方法。
  • 最后,创建一个将对话线程保存到单独目录的方法。

创建一个 ConversationThread 类

它将具有以下属性。

  • Id:会话索引标头的字符串表示形式(22 个字节)。
  • Messages:对话线程中的消息 ID 列表。
///<summary>
/// 表示对话线程中的消息。
///</summary>
public class ConversationThread
{
    private string id;
    private List<string> messages;
    
    ///<summary>
    ///初始化一个新的实例<see cref="ConversationThread"/>班级。
    ///</summary>
    ///<param name="id">会话索引标头的字符串表示形式。</param>
    public MessageThread(string id)
    {
        this.id = id;
        this.messages = new List<string>();
    }
    
    ///<summary>
    /// 获取或设置会话索引头的 HEX 字符串表示(22 字节)。
    ///</summary>
    public string Id { get => id; set => id = value; }
    
    ///<summary>
    /// 获取对话线程中的消息 ID 列表。
    ///</summary>
    public List<string> Messages { get => messages; }
}

创建一种按对话搜索和分组消息的方法

创建 ConversationThread 类后,我们可以专注于编写一个执行以下操作的方法:

  • 浏览文件夹中的所有邮件。出于性能原因,我们将仅使用 EnumerateMessagesEntryId 方法读取消息标识符。
  • 对于每条消息,我们将使用 ExtractProperty 方法提取 PidTagConversationIndex 属性。
  • PidTagConversationIndex 属性值的前 22 个字节相同的消息属于同一个会话。我们将消息 Id 添加到相应 ConversationThread 类实例的 Messages 属性表示的列表中。
  • 返回 ConversationThread 实例的列表。
using Aspose.Email.Mapi;
using Aspose.Email.Storage.Pst;

public List<ConversationThread> GroupMessagesByThread(PersonalStorage pst, FolderInfo folder)
{
    // 结果
    var list = new List<ConversationThread>();
        
    foreach (var entryId in folder.EnumerateMessagesEntryId())
    {
        // 提取 PidTagConversationIndex 属性及其标头值(22 个字节)。
        var convIndexTag = KnownPropertyList.ConversationIndex.Tag;
        var convIndexProperty = pst.ExtractProperty(Convert.FromBase64String(entryId), convIndexTag);
        var convIndexHeader = (convIndexProperty != null && convIndexProperty.Data != null) ? convIndexProperty.Data.Take(22).ToArray() : null;
        var convIndexHeaderString = convIndexHeader != null ? 
			      BitConverter.ToString(convIndexHeader).Replace('-', '') : null;
        
        if (convIndexHeaderString != null)
        {
            var convThread = list.Find(x => x.Id == convIndexHeaderString);
            
            if (convThread == null)
            {
                convThread = new ConversationThread(convIndexHeaderString);
                convThread.Messages.Add(entryId);
                list.Add(convThread);
            }
            else
            {
                convThread.Messages.Add(entryId);
            }
        }
    }
        
    return list.Where(x => x.Messages.Count > 1).ToList();
}

创建将对话线程保存到单独目录的方法

最后,让我们将对话保存在目录中。

对于每个 ConversationThread 实例,请执行以下操作:

  • 使用线程主题的名称创建一个单独的目录。
  • 枚举 ConversationThread.Messages 属性中的标识符,对于每个标识符,使用 ExtractMessage 方法提取一条消息,并使用 Save 方法将消息保存在创建的目录中。
public void SaveThreads(List<ConversationThread> list, string outDirectory)
{
    var invalidChars = Path.GetInvalidFileNameChars();
    
    foreach (var item in list)
    {
        string? threadDirectory = null;
        var i = item.Messages.Count - 1;
        
        foreach (var entryId in item.Messages)
        {
            var msg = pst.ExtractMessage(entryId);
            
            if (threadDirectory == null)
            {
                var threadName = new string(msg.ConversationTopic.Where(x => !invalidChars.Contains(x)).ToArray()).Trim();
                threadDirectory = Path.Combine(outDirectory, threadName);
                Directory.CreateDirectory(threadDirectory);
            }
            
            msg.Save(Path.Combine(threadDirectory, $"{i--}.msg"));
        }
        
        threadDirectory = null;
    }
}

获取免费 API 许可证

您可以使用 免费临时许可证 使用 Aspose.Email for .NET,而不受评估限制。

结论

本文展示了如何使用 Aspose.Email 在 PST 中搜索与对话相关的消息。 通过进一步探索 PidTagConversationIndex 文档,您还可以通过添加例如对话消息的分层排序来使实现复杂化。您可以使用 文档 了解有关 Aspose.Email 的更多信息。如果您有任何问题,可以发帖到我们的 论坛

也可以看看