Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do paging with Exchange Web Services CalendarView

If I do this:

_calendar = (CalendarFolder)Folder.Bind(_service, WellKnownFolderName.Calendar);

var findResults = _calendar.FindAppointments(
    new CalendarView(startDate.Date, endDate.Date)
);

I sometimes get an exception that too many items were found.

"You have exceeded the maximum number of objects that can be returned for the find operation. Use paging to reduce the result size and try your request again."

CalendarView supports a constructor that will let me specify MaxItemsReturned, but I can't figure out how I would call it again, specifying the offset for paging. ItemView has this constructor:

 public ItemView(int pageSize, int offset)

And the usage of that is obvious.

What about CalendarView? How does one do paging with a CalendarView?

I could reduce the date range to be a shorter span, but there's still no way of determining if it will work for sure.

like image 742
tig Avatar asked Aug 17 '12 04:08

tig


3 Answers

CalendarView is not actually derived from PagedView, so all of the paging logic that you expect isn't possible. MaxItemsReturned is more of an upper limit than a page size. The error that's returned is more relevant to the PagedView derived view types.

I played around with some PowerShell to emulate paging by rolling the CalendarView window based on the last item returned, but unfortunately the logic behind the CalendarView and Appointment expansion make it impossible to get exactly what you need. Basically as it does the expansion, it's going to stop at "N" items, but you might have more than one appointment that starts at the exact same time and it may give you one, but not the rest. Also, any appointments that overlap the window will get included, so the below code would go into an infinite loop if you had 50 appointments on the calendar that all had the same start time.

Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"

$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService
$cred = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials ($user , $passwd)
$service.UseDefaultCredentials = $false
$service.Credentials = $cred
$service.AutodiscoverUrl($user)

$num=50
$total=0
$propsetfc = [Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties
$calfolder = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar

$service.UserAgent = "EWSCalViewTest"
$calview = New-Object Microsoft.Exchange.WebServices.Data.CalendarView("1/1/2012","12/31/2012", $num)
$calview.PropertySet = $propsetfc

do {
    $findresults = $service.FindAppointments($calfolder,$calview)
    write-host  "Found:" $findresults.Items.Count "of" $findresults.TotalCount
    $calview.StartDate = $findresults.Items[$findresults.Items.Count-1].Start
    $total+=$findresults.Items.Count
} while($findresults.MoreAvailable)
write-host $total "total found (including dups)"

Unfortunately the expansion and overlap logic mean you'll get duplicates this way, at least one duplicate for each call beyond the first.

If I had to write code using CalendarView, I'd probably use a MaxItemsReturned of 1000 (this is also the limit that throws you into the error condition if you don't specify MaxItemsReturned). If you get them all in one call, you're good. If you have to make a second call, then you'll have to do some extra work to dedup the result set. I'd also try to limit the burden on the server by using as narrow of a date window as possible in the CalendarView since you're asking Exchange to calculate the expansion of recurring appointments across the entire time span. It can be a fairly expensive operation for the server.

like image 179
RickH Avatar answered Nov 16 '22 23:11

RickH


You can still paginate the FindAppointments function manipulating the CalendarView start dates.

var cal = CalendarFolder.Bind(_service, WellKnownFolderName.Calendar);
var cv = new CalendarView(start, end, 1000);

var appointments = new List<Appointment>();

var result = cal.FindAppointments(cv);

appointments.AddRange(result);

while (result.MoreAvailable)
{
     cv.StartDate = appointments.Last().Start;

     result = cal.FindAppointments(cv);

     appointments.AddRange(result);
}

Though I don't know if they come in order. If they don't you might have to use the last envent start date and remove the duplicates.

like image 22
Lucas Martins Juviniano Avatar answered Nov 17 '22 00:11

Lucas Martins Juviniano


You can use ItemView and SearchFilter to query appointments:

var itemView = new ItemView(100, 0);
itemView.PropertySet = new PropertySet(BasePropertySet.IdOnly,
    ItemSchema.Subject, AppointmentSchema.Start, AppointmentSchema.End);

var filter = new SearchFilter.SearchFilterCollection(LogicalOperator.And,
    new SearchFilter.IsEqualTo(ItemSchema.ItemClass, "IPM.Appointment"),
    new SearchFilter.IsGreaterThanOrEqualTo(AppointmentSchema.Start, startDate),
    new SearchFilter.IsLessThan(AppointmentSchema.Start, endDate));

bool moreAvailable = true;
while (moreAvailable)
{
    var result = _service.FindItems(WellKnownFolderName.Calendar, filter, itemView);

    foreach (var appointment in result.OfType<Appointment>())
    {
        DateTime start = appointment.Start;
        DateTime end = appointment.End;
        string subject = appointment.Subject;

        // ...
    }

    itemView.Offset += itemView.PageSize;
    moreAvailable = result.MoreAvailable;
}
like image 34
Markus Jarderot Avatar answered Nov 17 '22 01:11

Markus Jarderot