Synchronizing folder hierarchy by using the EWS Managed API 2.0
Last modified: October 13, 2012
Applies to: EWS Managed API | Exchange Server 2007 Service Pack 1 (SP1) | Exchange Server 2010
Note: This content applies to the EWS Managed API 2.0 and earlier versions. For the latest information about the EWS Managed API, see Web services in Exchange.
You can use the Microsoft Exchange Web Services (EWS) Managed API to retrieve a list of changes that have occurred on a folder hierarchy since a given point in time.
A client uses the SyncFolderHierarchy method to keep its folder hierarchy in sync with the server's folder hierarchy by doing the following:
Executing an initial synchronization operation (to retrieve a list of all folders in a folder hierarchy).
Periodically executing a subsequent synchronization operation to retrieve a list of folder changes that have occurred since the prior synchronization.
To synchronize its folder hierarchy with the server's folder hierarchy, a client processes the collection of changes that are returned by the SyncFolderHierarchy method, applying the same changes to its local folder hierarchy.
Note
The SyncFolderHierarchy method enables one-way synchronization, server to client. To propagate client folder hierarchy changes back to the server, the client must use other EWS Managed API methods to create, update, and delete folders as necessary. For more information about the methods that create, update, and delete folders, see Working with folders by using the EWS Managed API 2.0.
To execute the initial synchronization operation
Call the SyncFolderHierarchy(FolderId, PropertySet, String) method and specify the folder that contains the data to synchronize, the set of properties to return for each folder in the response, and a null sync state. Specifying a null sync state causes the SyncFolderHierarchy method to return a collection that represents all folders in the specified hierarchy. The following code shows how to request a list of all folders that are contained in the Inbox folder. The first-class property set will be returned for each folder in the response. Connection configuration information is provided by using an ExchangeService object named service.
ChangeCollection<FolderChange> fcc = service.SyncFolderHierarchy(new FolderId(WellKnownFolderName.Inbox), PropertySet.FirstClassProperties, null);
Note
To request a list of all folders in the mailbox, use the SyncFolderHierarchy overload method that does not require a folder ID.
Save the sync state for use in the subsequent SyncFolderHierarchy call. The following code example shows how to access the SyncState property in the response. The client should store this value such that it persists and can be retrieved later to be used in the subsequent SyncFolderHierarchy call.
string sSyncState = fcc.SyncState;
If the SyncFolderHierarchy method returns a list of folder changes, iterate through the changes and process each change on the client. For the initial synchronization, all items in the list of changes will be of type ChangeType.Create.
if (fcc.Count == 0) { Console.WriteLine("There are no folders to synchronize."); } else { foreach (FolderChange fc in fcc) { Console.WriteLine("ChangeType: " + fc.ChangeType.ToString()); Console.WriteLine("FolderId: " + fc.FolderId.UniqueId); Console.WriteLine("DisplayName: " + fc.Folder.DisplayName); Console.WriteLine("ChildFolderCount: " + fc.Folder.ChildFolderCount); Console.WriteLine("ParentFolderId: " + fc.Folder.ParentFolderId); Console.WriteLine("==========="); //TODO: Create folder on the client. } }
To execute subsequent synchronization operations
Call the SyncFolderHierarchy(FolderId, PropertySet, String) method and specify the folder that contains the data to synchronize, the set of properties to return for each folder in the response, and the sync state value from the prior synchronization. The following code shows how to request a list of all folder changes that have occurred in the Inbox folder since the time that corresponds to the specified sync state. The first-class property set will be returned for each folder in the response. Connection configuration information is provided by using an ExchangeService object named service.
ChangeCollection<FolderChange> fcc = service.SyncFolderHierarchy(new FolderId(WellKnownFolderName.Inbox), PropertySet.FirstClassProperties, sSyncState);
Note
To request a list of all folders in the mailbox, use the SyncFolderHierarchy overload method that does not require a folder ID.
Save the sync state for use in the subsequent SyncFolderHierarchy call. The following code example shows how to access the sSyncState property in the response. The client should store this value such that it persists and can be retrieved later to be used in the subsequent SyncFolderHierarchy call.
string sSyncState = fcc.SyncState;
If the SyncFolderHierarchy method returns a list of folder changes, iterate through the changes and process each change on the client.
if (fcc.Count == 0) { Console.WriteLine("There are no folder changes to synchronize."); } else { Console.WriteLine("ChangeType: " + fc.ChangeType.ToString()); Console.WriteLine("FolderId: " + fc.FolderId.UniqueId); if (fc.Folder != null) { Console.WriteLine("DisplayName: " + fc.Folder.DisplayName); Console.WriteLine("ChildFolderCount: " + fc.Folder.ChildFolderCount); Console.WriteLine("ParentFolderId: " + fc.Folder.ParentFolderId); } Console.WriteLine("==========="); foreach (FolderChange fc in fcc) { if (fc.ChangeType == ChangeType.Create) { //TODO: Create folder on the client. } else if (fc.ChangeType == ChangeType.Update) { //TODO: Update folder on the client. } else if (fc.ChangeType == ChangeType.Delete) { //TODO: Delete folder on the client. } } }
Note
When a folder has been updated, the information that is contained in the corresponding FolderChange within the SyncFolderHierarchy response will not specify how the folder has changed. Therefore, the client is responsible for identifying what has changed by comparing property values of folders in the change list with its local folder property values. The client must specify PropertySet in the SyncFolderHierarchy call such that each FolderChange within the SyncFolderHierarchy response will contain the properties that are of interest to the client.
Example
The following code example shows how to get a list of changes to all folders in the Inbox folder that have occurred since the time that is represented by sSyncState. This example assumes that service is a valid ExchangeService binding and that sSyncState represents the sync state that was returned by a prior call to SyncFolderHierarchy.
// Get a list of all changes that have occurred to folders in the Inbox folder since the prior synchronization.
ChangeCollection<FolderChange> fcc = service.SyncFolderHierarchy(new FolderId(WellKnownFolderName.Inbox), PropertySet.FirstClassProperties, sSyncState);
if (fcc.Count == 0)
{
Console.WriteLine("There are no folder changes to synchronize.");
}
else
{
foreach (FolderChange fc in fcc)
{
if (fc.ChangeType == ChangeType.Create)
{
//TODO: create folder on the client.
}
else if (fc.ChangeType == ChangeType.Update)
{
//TODO: update folder on the client
}
else if (fc.ChangeType == ChangeType.Delete)
{
//TODO: delete folder on the client
}
}
}
// Save the sync state for use in future SyncFolderHierarchy calls.
string sSyncState = fcc.SyncState;
The following example shows the XML request that is sent by the client to the server when the client calls the SyncFolderHierarchy method. The value of SyncState has been shortened to preserve readability.
<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types"
xmlns:soap="https://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<t:RequestServerVersion Version="Exchange2010" />
</soap:Header>
<soap:Body>
<m:SyncFolderHierarchy>
<m:FolderShape>
<t:BaseShape>AllProperties</t:BaseShape>
</m:FolderShape>
<m:SyncFolderId>
<t:DistinguishedFolderId Id="inbox" />
</m:SyncFolderId>
<m:SyncState>H4sIAA==</m:SyncState>
</m:SyncFolderHierarchy>
</soap:Body>
</soap:Envelope>
The following example shows the XML response that is returned by the server after it processes the request from the client. This response indicates that one folder was updated, one folder was created, and one folder was deleted since the prior synchronization. The value of the SyncState element, Id attributes, and ChangeKey attributes have been shortened to preserve readability.
<s:Envelope xmlns:s="https://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<h:ServerVersionInfo MajorVersion="14" MinorVersion="0" MajorBuildNumber="639" MinorBuildNumber="21" Version="Exchange2010"
xmlns:h="https://schemas.microsoft.com/exchange/services/2006/types"
xmlns="https://schemas.microsoft.com/exchange/services/2006/types"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<m:SyncFolderHierarchyResponse xmlns:m="https://schemas.microsoft.com/exchange/services/2006/messages"
xmlns:t="https://schemas.microsoft.com/exchange/services/2006/types">
<m:ResponseMessages>
<m:SyncFolderHierarchyResponseMessage ResponseClass="Success">
<m:ResponseCode>NoError</m:ResponseCode>
<m:SyncState>H4sIAAA</m:SyncState>
<m:IncludesLastFolderInRange>true</m:IncludesLastFolderInRange>
<m:Changes>
<t:Update>
<t:Folder>
<t:FolderId Id="AAMkADM=" ChangeKey="AQAAABY" />
<t:ParentFolderId Id="AQMkADMzADI1==" ChangeKey="AQAAAA==" />
<t:FolderClass>IPF.Note</t:FolderClass>
<t:DisplayName>test_2_renamed</t:DisplayName>
<t:TotalCount>0</t:TotalCount>
<t:ChildFolderCount>0</t:ChildFolderCount>
<t:EffectiveRights>
<t:CreateAssociated>true</t:CreateAssociated>
<t:CreateContents>true</t:CreateContents>
<t:CreateHierarchy>true</t:CreateHierarchy>
<t:Delete>true</t:Delete>
<t:Modify>true</t:Modify>
<t:Read>true</t:Read>
</t:EffectiveRights>
<t:PermissionSet>
<t:Permissions>
<t:Permission>
<t:UserId>
<t:DistinguishedUser>Default</t:DistinguishedUser>
</t:UserId>
<t:CanCreateItems>false</t:CanCreateItems>
<t:CanCreateSubFolders>false</t:CanCreateSubFolders>
<t:IsFolderOwner>false</t:IsFolderOwner>
<t:IsFolderVisible>false</t:IsFolderVisible>
<t:IsFolderContact>false</t:IsFolderContact>
<t:EditItems>None</t:EditItems>
<t:DeleteItems>None</t:DeleteItems>
<t:ReadItems>None</t:ReadItems>
<t:PermissionLevel>None</t:PermissionLevel>
</t:Permission>
<t:Permission>
<t:UserId>
<t:DistinguishedUser>Anonymous</t:DistinguishedUser>
</t:UserId>
<t:CanCreateItems>false</t:CanCreateItems>
<t:CanCreateSubFolders>false</t:CanCreateSubFolders>
<t:IsFolderOwner>false</t:IsFolderOwner>
<t:IsFolderVisible>false</t:IsFolderVisible>
<t:IsFolderContact>false</t:IsFolderContact>
<t:EditItems>None</t:EditItems>
<t:DeleteItems>None</t:DeleteItems>
<t:ReadItems>None</t:ReadItems>
<t:PermissionLevel>None</t:PermissionLevel>
</t:Permission>
</t:Permissions>
</t:PermissionSet>
<t:UnreadCount>0</t:UnreadCount>
</t:Folder>
</t:Update>
<t:Create>
<t:Folder>
<t:FolderId Id="AAMkADMzM=" ChangeKey="AQAAABYAA" />
<t:ParentFolderId Id="AQMkO67A==" ChangeKey="AQAAAA==" />
<t:FolderClass>IPF.Note</t:FolderClass>
<t:DisplayName>test_4</t:DisplayName>
<t:TotalCount>0</t:TotalCount>
<t:ChildFolderCount>0</t:ChildFolderCount>
<t:EffectiveRights>
<t:CreateAssociated>true</t:CreateAssociated>
<t:CreateContents>true</t:CreateContents>
<t:CreateHierarchy>true</t:CreateHierarchy>
<t:Delete>true</t:Delete>
<t:Modify>true</t:Modify>
<t:Read>true</t:Read>
</t:EffectiveRights>
<t:PermissionSet>
<t:Permissions>
<t:Permission>
<t:UserId>
<t:DistinguishedUser>Default</t:DistinguishedUser>
</t:UserId>
<t:CanCreateItems>false</t:CanCreateItems>
<t:CanCreateSubFolders>false</t:CanCreateSubFolders>
<t:IsFolderOwner>false</t:IsFolderOwner>
<t:IsFolderVisible>false</t:IsFolderVisible>
<t:IsFolderContact>false</t:IsFolderContact>
<t:EditItems>None</t:EditItems>
<t:DeleteItems>None</t:DeleteItems>
<t:ReadItems>None</t:ReadItems>
<t:PermissionLevel>None</t:PermissionLevel>
</t:Permission>
<t:Permission>
<t:UserId>
<t:DistinguishedUser>Anonymous</t:DistinguishedUser>
</t:UserId>
<t:CanCreateItems>false</t:CanCreateItems>
<t:CanCreateSubFolders>false</t:CanCreateSubFolders>
<t:IsFolderOwner>false</t:IsFolderOwner>
<t:IsFolderVisible>false</t:IsFolderVisible>
<t:IsFolderContact>false</t:IsFolderContact>
<t:EditItems>None</t:EditItems>
<t:DeleteItems>None</t:DeleteItems>
<t:ReadItems>None</t:ReadItems>
<t:PermissionLevel>None</t:PermissionLevel>
</t:Permission>
</t:Permissions>
</t:PermissionSet>
<t:UnreadCount>0</t:UnreadCount>
</t:Folder>
</t:Create>
<t:Delete>
<t:FolderId Id="AAMkAD/AAA=" ChangeKey="AQAAAA==" />
</t:Delete>
</m:Changes>
</m:SyncFolderHierarchyResponseMessage>
</m:ResponseMessages>
</m:SyncFolderHierarchyResponse>
</s:Body>
</s:Envelope>
Compiling the code
For information about compiling this code, see Getting started with the EWS Managed API 2.0.
Robust programming
Write appropriate error handling code for common search errors.
Review the client request XML that is sent to the Exchange server.
Review the server response XML that is sent from the Exchange server.
Set the service binding as shown in Setting the Exchange service URL by using the EWS Managed API 2.0. Do not hard code URLs because if mailboxes move, they might be serviced by a different Client Access server. If the client cannot connect to the service, retry setting the binding by using the AutodiscoverUrl(String) method.
Set the target Exchange Web Services schema version by setting the requestedServerVersion parameter of the ExchangeService constructor. For more information, see Versioning EWS requests by using the EWS Managed API 2.0.
Security
Use HTTP with SSL for all communication between client and server.
Always validate the server certificate that is used for establishing the SSL connections. For more information, see Validating X509 certificates by using the EWS Managed API 2.0.
Do not include user names and passwords in trace files.
Verify that Autodiscover lookups that use HTTP GET to find an endpoint always prompt for user confirmation; otherwise, they should be blocked.