Exchange Calendar Syncing with Exchange Web Services (EWS) API

I’ve been working with the Exchange Web Services managed API on and off for the past year and surprisingly there isn’t all that much documentation.  Sure there is the standard Microsoft documentation (here) but it only gives basic examples.  Which as it turns out worked out for the majority of the functions I need to handle for my project, but there is one problem that the API couldn’t handle.

The project that I’ve been working on is an appointment scheduling application.  The basics are that the application maintains a master calendar of a various number of people and they each specify when they are available for appointments and for what type of appointment.  These appointments are displayed in the program but are also written out to their exchange calendars.  Keeping these appointments in order would be all fine and dandy except the appointment aren’t read-only.

EWS does have a function that will supposedly aid in synchronizing the calendar to the database.  The function called “SyncFolderItems” takes a sync state (which it returns to you when you run the function) and using this sync state information EWS returns any changes to the calendar since that sync.  However it doesn’t seem to work all that well and appears to be quite fragile (it is probably worth noting that I am working against an Exchange 2007sp1 server and not 2010).  The sync might work at first but after some amount of time or some condition it just stops returning any changes at all.  And it’s hard to debug since EWS just tells me no changes happened.

This is the MyAppointment class that is used in the program.

public class MyAppointment
{
    public int Id { get; set; }
    public DateTime Start { get; set; }
    public DateTime End { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }

    public string ExchangeId { get; set; }
}

The following is the code for using the EWS sync function.

public string SyncChanges(string mailboxId, IEnumerable<MyAppointment> appointments, string syncState)
{
    var service = InitializeService(mailboxId);

    var changeLog = service.SyncFolderItems(
        new FolderId(WellKnownFolderName.Calendar, mailboxId),
        PropertySet.FirstClassProperties, null, 512,
        SyncFolderItemsScope.NormalItems, syncState);

    foreach (var changedItem in changeLog)
    {
        var appt = appointments.Where(a => a.ExchangeId == changedItem.ItemId.UniqueId).FirstOrDefault();

        if (appt != null)
        {
            switch(changedItem.ChangeType)
            {
                case ChangeType.Update:
                    var appointment = (Appointment) changedItem.Item;
                    appointment.Start = appt.Start;
                    appointment.End = appt.End;
                    appointment.Subject = appt.Subject;
                    appointment.Body = appt.Body;

                    // write the change back to exchange

                    break;
                case ChangeType.Delete:

                    var newAppointment = new Appointment(service);

                    newAppointment.Start = appt.Start;
                    newAppointment.End = appt.End;
                    newAppointment.Subject = appt.Subject;
                    newAppointment.Body = appt.Body;

                    // write the change back to exchange

                    break;
                default:
                    break;
            }
        }
    }

    return changeLog.SyncState;
}

However the above code doesn’t work for whatever reason.  Instead I wrote the following function that essentially does the same thing, but only syncs data 2 months ahead.  It takes a few more lines of code but using Linq it makes it easy to do the comparisons.

public void SyncChanges(string mailboxId, IEnumerable<MyAppointment> myAppointments)
{
    // get appt one day before and 1 month out
    var searchFilter = new SearchFilter.SearchFilterCollection(LogicalOperator.And);
    searchFilter.Add(
        new SearchFilter.IsGreaterThanOrEqualTo(AppointmentSchema.Start, DateTime.Now.AddDays(-1)));
    searchFilter.Add(
        new SearchFilter.IsLessThanOrEqualTo(AppointmentSchema.End, DateTime.Now.AddMonths(2)));
    searchFilter.Add(
        new SearchFilter.IsEqualTo(ItemSchema.ItemClass, "IPM.Appointment"));
    // get back 512 results max
    var view = new ItemView(512);
    // make the remote call
    var service = InitializeService(mailboxId);
    var results = service.FindItems(
        new FolderId(WellKnownFolderName.Calendar, mailboxId), searchFilter, view);

    // get the distinct ids from exchange
    var exchangeIds = results.Select(a => a.Id.UniqueId);

    // get the distinct ones we have
    var dbIds = myAppointments.Select(a => a.ExchangeId);

    // find the ids that are in the db but not in exchange
    var missing = dbIds.Where(a => !exchangeIds.Contains(a));

    var newAppts = myAppointments.Where(a => missing.Contains(a.ExchangeId)).Select(
                        a => new Appointment(service) {Start = a.Start
                                                     , End = a.End
                                                     , Subject = a.Subject
                                                     , Body = a.Body});

    // get the exchange objects we do have in the db
    var appts = results.Where(a => !missing.Contains(a.Id.UniqueId) && dbIds.Contains(a.Id.UniqueId))
                       .Select(a => (Appointment)a);

    var changedAppts = new List();

    // find the changed appointments
    foreach (var appointment in appts)
    {
        // get the db appointment object
        var appt = myAppointments.Where(a => a.ExchangeId == appointment.Id.UniqueId).FirstOrDefault();

        // compare the time stamps and determine if we need to make a change
        if (appt != null)
        {
            if (appt.Start != appointment.Start || appt.End != appointment.End)
            {
                // make the changes
                appointment.Start = appt.Start;
                appointment.End = appt.End;
                appointment.Subject = appt.Subject;
                appointment.Body = appt.Body;

                // add it to the list of ones needed to change
                changedAppts.Add((Item)appointment);
            }
        }
    }

    // write the new and updated objects to exchange
}

The end result is that if a user moves or deletes an appointment managed by the program it can on interval go back and sync the calendar.

4 thoughts on “Exchange Calendar Syncing with Exchange Web Services (EWS) API

  1. Hi there. Could You please show how to use MyAppointment in Your code?
    I would like to have that kind of class with 8 fields, so that I would be able to create appointment by that class, then save it to exchange and read exchange appointment with extendedProperties (cast appointment to MyAppointment).

    This would help a lot with my master thesis application that I’m building.

    1. Tomasz,

      The MyAppointment class is simply another class that I am using to make it easier for myself in the rest of my program. I’m doing very simple copying of the MyAppointment class to the Appointment class (see lines 51-54 in the code snippet).

      So it sounds like you are going to be doing a much simplier case, than the one in my post. You simply want to use some class like MyAppointment and use that in your program, but have some function that converts that to and from exchange classes.

      If the above is correct, take a look at this example:


      public class ExchangeFunctions
      {
      private const string _username = "uname";
      private const string _password = "password";
      private const string _domain = "domain";
      public static string CreateAppointment(string mailboxId, MyAppointment appt)
      {
      var service = new ExchangeService(ExchangeVersion.Exchange2007_SP1);
      service.Credentials = new WebCredentials(_username, _password, _domain);
      // auto loads the url for the targetted mailbox
      service.AutodiscoverUrl(mailboxId);
      var appointment = ConvertAppointment(service, appt);
      // save the appointment, bound to the targetted user's mailbox, using delegate permissions
      appointment.Save(new FolderId(WellKnownFolderName.Calendar, mailboxId));
      }
      // Convert My Appointment to Exchange Appointment class
      private Appointment ConvertAppointment(ExchangeService service, MyAppointment appt)
      {
      // create the appointment
      var appointment = new Appointment(service);
      appointment.Subject = appt.Subject;
      appointment.Body = appt.Body;
      appointment.Start = appt.Begin;
      appointment.End = appt.End;
      appointment.LegacyFreeBusyStatus = appt.Busy ? LegacyFreeBusyStatus.Busy : LegacyFreeBusyStatus.Free;
      return appointment;
      }
      }


      function void Main()
      {
      var appt = new MyAppointment()
      {
      Start = DateTime.Now(),
      End = DateTime.Now().AddMinutes(30),
      Subject = "Sample 1",
      Body = "Sample 1 Body",
      Busy = true
      };
      CreateAppt("mailboxid", appt);
      }

      view raw

      Main.cs

      hosted with ❤ by GitHub


      public class MyAppointment
      {
      public int Id { get; set; }
      public DateTime Start { get; set; }
      public DateTime End { get; set; }
      public string Subject { get; set; }
      public string Body { get; set; }
      public string Busy { get; set; }
      public string ExchangeId { get; set; }
      }

      Hope it helps, and good luck with your master thesis!

  2. Hello Alan, I would to submit this problem.

    We use in our appointment scheduling application, the function “SyncFolderItems” calendars exchange 2010 . In the test environment is performed using the synchronizationaccount with the impersonation configured. Migrating in a production environment with the same configuration, the same function generates the following error: “microsoft.exchange.webservices.data.ServiceRequestException: The request failed. Could not read value from START_ELEMENT.Could not find CHARACTERS.”

    Any suggestions regarding this error? Thanks

    1. Sorry I can’t really help you with that, I have never been able to get the SyncFolderItems function to work reliably. Hence, I wrote my own sync function. I had to work off an older version of Exchange, I’ve heard 2010’s works but i haven’t had a chance to test it out.

Leave a comment