Daniel
Daniel

Reputation: 1469

Get to an Exchange folder by path using EWS

I need to retrieve items from the 'Inbox\test\final' Exchange folder using EWS. The folder is provided by a literal path as written above. I know I can split this string into folder names and recursively search for the necessary folder, but is there a more optimal way that can translate a string path into a folder instance or folder ID?

I'm using the latest EWS 2.0 assemblies. Do these assemblies provide any help, or am I stuck with manual recursion?

Upvotes: 3

Views: 4615

Answers (4)

Stephen Turner
Stephen Turner

Reputation: 7314

No, you don't need recursion and you efficiently go straight to the folder. This uses the same extended property as Tom, and uses it to apply a search filter:

using Microsoft.Exchange.WebServices.Data; // from nuget package "Microsoft.Exchange.WebServices"
...
    private static Folder GetOneFolder(ExchangeService service, string folderPath)
    {
        var propertySet = new PropertySet(BasePropertySet.IdOnly);
        propertySet.AddRange(new List<PropertyDefinitionBase> {
            FolderSchema.DisplayName,
            FolderSchema.TotalCount
                });

        var pageSize = 100;
        var folderView = new FolderView(pageSize)
        {
            Offset = 0,
            OffsetBasePoint = OffsetBasePoint.Beginning,
            PropertySet = propertySet
        };
        folderView.Traversal = FolderTraversal.Deep;

        var searchFilter = new SearchFilter.IsEqualTo(ExchangeExtendedProperty.FolderPathname, folderPath);

        FindFoldersResults findFoldersResults;
        var baseFolder = new FolderId(WellKnownFolderName.MsgFolderRoot);
        var localFolderList = new List<Folder>();

        do
        {
            findFoldersResults = service.FindFolders(baseFolder, searchFilter, folderView);
            localFolderList.AddRange(findFoldersResults.Folders);
            folderView.Offset += pageSize;

        } while (findFoldersResults.MoreAvailable);

        return localFolderList.SingleOrDefault();
    }

...
public static class ExchangeExtendedProperty
{
    /// <summary>PR_FOLDER_PATHNAME String</summary>
    public static ExtendedPropertyDefinition FolderPathname { get => new ExtendedPropertyDefinition(0x66B5, MapiPropertyType.String); }
}

The path will need to be prefixed with a backslash, ie. \Inbox\test\final.

Upvotes: 0

Aaron S.
Aaron S.

Reputation: 31

Since Exchange Server likes to map everything together with Folder.Id, the only way to find the path you're looking for is by looking at folder names.

You'll need to create a recursive function to go through all folders in a folder collection, and track the path as it moves through the tree of email folders.

Another parameter is needed to track the path that you're looking for.

public static Folder GetPathFolder(ExchangeService service, FindFoldersResults results,
                                   string lookupPath, string currentPath)
{
    foreach (Folder folder in results)
    {
        string path = currentPath + @"\" + folder.DisplayName;
        if (folder.DisplayName == "Calendar")
        {
            continue;
        }

        Console.WriteLine(path);

        FolderView view = new FolderView(50);
        SearchFilter filter = new SearchFilter.IsEqualTo(FolderSchema.Id, folder.Id);
        FindFoldersResults folderResults = service.FindFolders(folder.Id, view);
        Folder result = GetPathFolder(service, folderResults, lookupPath, path);
        if (result != null)
        {
            return result;
        }

        string[] pathSplitForward = path.Split(new[] {  "/" }, StringSplitOptions.RemoveEmptyEntries);
        string[] pathSplitBack    = path.Split(new[] { @"\" }, StringSplitOptions.RemoveEmptyEntries);
        string[] lookupPathSplitForward = lookupPath.Split(new[] {  "/" }, StringSplitOptions.RemoveEmptyEntries);
        string[] lookupPathSplitBack    = lookupPath.Split(new[] { @"\" }, StringSplitOptions.RemoveEmptyEntries);

        if (ArraysEqual(pathSplitForward, lookupPathSplitForward) || 
            ArraysEqual(pathSplitBack,    lookupPathSplitBack) || 
            ArraysEqual(pathSplitForward, lookupPathSplitBack) || 
            ArraysEqual(pathSplitBack,    lookupPathSplitForward))
        {
            return folder;
        }
    }
    return null;
}

"ArraysEqual":

public static bool ArraysEqual<T>(T[] a1, T[] a2)
{
    if (ReferenceEquals(a1, a2))
        return true;

    if (a1 == null || a2 == null)
        return false;

    if (a1.Length != a2.Length)
        return false;

    EqualityComparer<T> comparer = EqualityComparer<T>.Default;
    for (int i = 0; i < a1.Length; i++)
    {
        if (!comparer.Equals(a1[i], a2[i])) return false;
    }
    return true;
}

I do all the extra array checking since sometimes my clients enter paths with forward slashes, back slashes, starting with a slash, etc. They're not tech savvy so let's make sure the program works every time!

As you go through each directory, compare the desired path to the iterated path. Once it's found, bubble up the Folder object that it's currently on. You'll need to create a search filter for that folder's id:

FindItemsResults<item> results = service.FindItems(foundFolder.Id, searchFilter, view);

Loop through the emails in results!

foreach (Item item in results) 
{
    // do something with item (email)
}

Upvotes: 2

LOAS
LOAS

Reputation: 7282

Here's my recursive descent implementation, which attempts to fetch as little information as possible on the way to the target folder:

private readonly FolderView _folderTraversalView = new FolderView(1) { PropertySet = PropertySet.IdOnly }; 

private Folder TraceFolderPathRec(string[] pathTokens, FolderId rootId)
{
  var token = pathTokens.FirstOrDefault();
  var matchingSubFolder = _exchangeService.FindFolders(
    rootId, 
    new SearchFilter.IsEqualTo(FolderSchema.DisplayName, token), 
    _folderTraversalView)
    .FirstOrDefault();
  if (matchingSubFolder != null && pathTokens.Length == 1) return matchingSubFolder;
  return matchingSubFolder == null ? null : TraceFolderPathRec(pathTokens.Skip(1).ToArray(), matchingSubFolder.Id);
}

For a '/'-delimited path, it can be called as follows:

public Folder TraceFolderPath(string folderPath)
{ // Handle folder names with '/' in them
  var tokens = folderPath
    .Replace("\\/", "<slash>")
    .Split('/')
    .Select(t => t.Replace("<slash>", "/"))
    .ToArray();
  return TraceFolderPathRec(tokens, WellKnownFolderName.MsgFolderRoot);
}

Upvotes: 0

tstojecki
tstojecki

Reputation: 1520

You could use an extended property as in this example

private string GetFolderPath(ExchangeService service, FolderId folderId) 
{
    var folderPathExtendedProp = new ExtendedPropertyDefinition(26293, MapiPropertyType.String);
    var folderPropSet = new PropertySet(BasePropertySet.FirstClassProperties) { folderPathExtendedProp };
    var folder = Folder.Bind(service, folderId, folderPropSet);

    string path = null;
    folder.TryGetProperty(folderPathExtendedProp, out path);

    return path?.Replace("\ufffe", "\\");
}

Source: https://social.msdn.microsoft.com/Forums/en-US/e5d07492-f8a3-4db5-b137-46e920ab3dde/exchange-ews-managed-getting-full-path-for-a-folder?forum=exchangesvrdevelopment

Upvotes: 3

Related Questions