只需几次调用,您就可以分页您的请求并从服务器获取整个文件夹层次结构。正如 Jacob 所指出的,关键是 FolderView.Traversal 属性。
例如,对于包含约 1,300 个文件夹的 Exchange 邮箱,以下代码仅发出 2 个请求。您可以将页面大小设置为您喜欢的任何大小,只要您保持在或低于服务器限制即可。
仅供参考:Exchange Online (Office365) 在响应中最多包含 1,000 个项目。我没有测试过,所以在查询本地 Exchange Server 时,我无法说出任何类似的限制。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security;
using Exchange = Microsoft.Exchange.WebServices.Data; // from nuget package "Microsoft.Exchange.WebServices"
namespace FolderViewTraversal
{
class Program
{
public static void Main()
{
Exchange.ExchangeService oService;
Dictionary<string, User> oUsers;
oUsers = new Dictionary<string, User>
{
{ "User1", new User("write.to.me1@my.address.com", "Some-Fancy-Password1") },
{ "User2", new User("write.to.me2@my.address.com", "Some-Fancy-Password2") }
};
foreach (KeyValuePair<string, User> Credential in oUsers)
{
File.Delete(String.Format(LOG_FILE_PATH, Credential.Key));
}
foreach (KeyValuePair<string, User> Credential in oUsers)
{
LogFileName = Credential.Key;
Console.WriteLine("Getting message counts for mailbox [{0}]...", LogFileName);
Console.WriteLine();
oService = Service.ConnectToService(Credential.Value);
GetAllFolders(oService, String.Format(LOG_FILE_PATH, Credential.Key));
Console.Clear();
};
Console.WriteLine();
Console.Write("Press any key to exit...");
Console.ReadKey();
}
private static void GetAllFolders(Exchange.ExchangeService Service, string LogFilePath)
{
Exchange.ExtendedPropertyDefinition oIsHidden = default;
List<Exchange.Folder> oFolders = default;
Exchange.FindFoldersResults oResults = default;
bool lHasMore = false;
Exchange.Folder oChild = default;
Exchange.FolderView oView = default;
short nPageSize = 0;
short nOffSet = 0;
List<string> oPaths = default;
List<string> oPath = default;
oIsHidden = new Exchange.ExtendedPropertyDefinition(0x10f4, Exchange.MapiPropertyType.Boolean);
nPageSize = 1000;
oFolders = new List<Exchange.Folder>();
lHasMore = true;
nOffSet = 0;
while (lHasMore)
{
oView = new Exchange.FolderView(nPageSize, nOffSet, Exchange.OffsetBasePoint.Beginning)
{
PropertySet = new Exchange.PropertySet(Exchange.BasePropertySet.IdOnly)
};
oView.PropertySet.Add(oIsHidden);
oView.PropertySet.Add(Exchange.FolderSchema.ParentFolderId);
oView.PropertySet.Add(Exchange.FolderSchema.DisplayName);
oView.PropertySet.Add(Exchange.FolderSchema.FolderClass);
oView.PropertySet.Add(Exchange.FolderSchema.TotalCount);
oView.Traversal = Exchange.FolderTraversal.Deep;
oResults = Service.FindFolders(Exchange.WellKnownFolderName.MsgFolderRoot, oView);
oFolders.AddRange(oResults.Folders);
lHasMore = oResults.MoreAvailable;
if (lHasMore)
{
nOffSet += nPageSize;
}
}
oFolders.RemoveAll(Folder => (bool)Folder.ExtendedProperties[0].Value == true);
oFolders.RemoveAll(Folder => Folder.FolderClass != "IPF.Note");
oPaths = new List<string>();
oFolders.ForEach(Folder =>
{
oChild = Folder;
oPath = new List<string>();
do
{
oPath.Add(oChild.DisplayName);
oChild = oFolders.SingleOrDefault(Parent => Parent.Id.UniqueId == oChild.ParentFolderId.UniqueId);
} while (oChild != null);
oPath.Reverse();
oPaths.Add(String.Format("{0}{1}{2}", String.Join(DELIMITER, oPath), '\t', Folder.TotalCount));
});
oPaths.RemoveAll(Path => Path.StartsWith("Sync Issues"));
File.WriteAllText(LogFilePath, String.Join(Environment.NewLine, oPaths));
}
private static string LogFileName;
private const string LOG_FILE_PATH = "D:\\Emails\\Remote{0}.txt";
private const string DELIMITER = "\\";
}
internal class Service
{
public static Exchange.ExchangeService ConnectToService(User User)
{
return Service.ConnectToService(User, null);
}
public static Exchange.ExchangeService ConnectToService(User User, Exchange.ITraceListener Listener)
{
Exchange.ExchangeService oService = default;
oService = new Exchange.ExchangeService(Exchange.ExchangeVersion.Exchange2013_SP1)
{
Credentials = new NetworkCredential(User.EmailAddress, User.Password)
};
oService.AutodiscoverUrl(User.EmailAddress, RedirectionUrlValidationCallback);
if (Listener != null)
{
oService.TraceListener = Listener;
oService.TraceEnabled = true;
oService.TraceFlags = Exchange.TraceFlags.All;
}
return oService;
}
private static bool RedirectionUrlValidationCallback(string RedirectionUrl)
{
var _with1 = new Uri(RedirectionUrl);
return _with1.Scheme.ToLower() == "https";
}
}
internal class User
{
public string EmailAddress { get; }
public SecureString Password { get; }
public User(string EmailAddress)
{
this.EmailAddress = EmailAddress;
this.Password = new SecureString();
}
public User(string EmailAddress, string Password)
{
this.EmailAddress = EmailAddress;
this.Password = new SecureString();
foreach(char Chr in Password) { this.Password.AppendChar(Chr); };
this.Password.MakeReadOnly();
}
public static User GetUser()
{
Console.Write("Enter email address: ");
string sEmailAddress = Console.ReadLine();
Console.Write("Enter password: ");
User functionReturnValue = new User(sEmailAddress);
while (true)
{
ConsoleKeyInfo oUserInput = Console.ReadKey(true);
if (oUserInput.Key == ConsoleKey.Enter)
{
break; // TODO: might not be correct. Was : Exit While
}
else if (oUserInput.Key == ConsoleKey.Escape)
{
functionReturnValue.Password.Clear();
}
else if (oUserInput.Key == ConsoleKey.Backspace)
{
if (functionReturnValue.Password.Length != 0)
{
functionReturnValue.Password.RemoveAt(functionReturnValue.Password.Length - 1);
}
}
else
{
functionReturnValue.Password.AppendChar(oUserInput.KeyChar);
Console.Write("*");
}
}
if (functionReturnValue.Password.Length == 0)
{
functionReturnValue = null;
}
else
{
functionReturnValue.Password.MakeReadOnly();
Console.WriteLine();
}
return functionReturnValue;
}
}
internal class TraceListener : Exchange.ITraceListener
{
public void Trace(string TraceType, string TraceMessage)
{
File.AppendAllText(String.Format("{0}.txt", Path.Combine("D:\\Emails\\TraceOutput", Guid.NewGuid().ToString("D"))), TraceMessage);
}
}
}