I wrote this method with much help from StackO
, but I've been unable to find an a was to improve on this.
Right now, I iterate through each message in an Office 365 inbox, traverse its attachment collection, and save each file to disk if it's an Excel workbook. This is currently working, but results in a ton of calls to Exchange. The inbox size can get pretty large, and this takes a long time to run as a result, with each call clocking in at around 0.5 seconds.
If I've counted right, the number of calls scales as (n / 100) + 2n.
Note: Typically (99.9999%) there's only one attachment per message, but I added the inner loop for CYA purposes. Might be some memory overhead, but little-to-no scaling factor.
I was wondering if there's a better way, with fewer web service calls. Is there an EWS method that could just download attachments in bulk? I'm pretty new to EWS, so I'm sure I'm lacking understanding here. What I want to do is reduce the pageSize
and Load(path)
a batch of files to disk, reducing the scale.
Any suggestions for improvement on the code below, with respect to reducing EWS calls, would be greatly appreciated.
public void SaveAttachmentsFromInbox(string[] extensionFilter = null)
{
// Default to excel files
extensionFilter = extensionFilter ?? new[] { ".xls", ".xlsx" };
// Config for traversing inbox
int offset = 0;
int pageSize = 100;
ItemView view = new ItemView(pageSize, offset, OffsetBasePoint.Beginning);
view.PropertySet = PropertySet.FirstClassProperties;
FindItemsResults<Item> findResults;
// Loop through the inbox
// and save all attachments of the designated file types
bool more = true;
var fileCount = 0;
while (more)
{
findResults = service.FindItems(WellKnownFolderName.Inbox, view);
// Load each sheet's data into an Object
foreach (var item in findResults.Items)
{
//get FirstClassProperties
item.Load(view.PropertySet);
string vendor = GetVendor(EmailMessage.Bind(service, item.Id));
messageIds.Add(item.Id.ToString());
// Save files to disk
foreach (FileAttachment file in item.Attachments)
{
string fileExtension = file.Name.Substring(file.Name.IndexOf('.'), file.Name.Length - file.Name.IndexOf('.'));
if (extensionFilter.Contains(fileExtension))
{
var fullPath = Path.Combine(path, file.Name);
attachmentInfo.Add(fullPath, vendor);
// Loads attachment and saves to disk
file.Load(fullPath);
fileCount++;
Console.Write("\rFiles received... {0} ", fileCount);
}
}
}
Console.WriteLine(); // Next line
more = findResults.MoreAvailable;
// Page through inbox if more messages remain
if (more)
{
view.Offset += pageSize;
}
}
Console.WriteLine(attachmentInfo.Count + " Excel Attachment Downloads successful.\n");
}
As well as using AQS to cut down the recordset that is returned from the server you should make use of the batchs command to first get the Item properties using LoadPropertiesForItems (replaces the Bind in your code for each items) and then you can batch the Attachment download using GetAttachments (you need to make sure your using version 2.2 of the EWS Managed API) this will mean for example a batch of 100 item your making one FindItems Calls, a batch GetItem call and a Batch GetAttachment Call rather the 1 * 100 * 100 if you use Bind and Load. eg something like
ItemView ivItemView = new ItemView(100);
PropertySet flLevel = new PropertySet(BasePropertySet.IdOnly);
ivItemView.PropertySet = flLevel;
FindItemsResults<Item> faItems = service.FindItems(WellKnownFolderName.Inbox, "attachment:.xlsx OR attachment:xls", ivItemView);
PropertySet slLevel = new PropertySet(BasePropertySet.FirstClassProperties);
if (faItems.Items.Count > 0)
{
service.LoadPropertiesForItems(faItems, slLevel);
}
List<Attachment> atAttachments = new List<Attachment>();
foreach (Item itItem in faItems.Items)
{
foreach (Attachment atAttachment in itItem.Attachments)
{
if (atAttachment is FileAttachment)
{
string fileExtension = atAttachment.Name.Substring(atAttachment.Name.IndexOf('.'), atAttachment.Name.Length - atAttachment.Name.IndexOf('.'));
if (extensionFilter.Contains(fileExtension))
{
atAttachments.Add(atAttachment);
}
}
}
}
service.GetAttachments(atAttachments.ToArray(), BodyType.HTML,null);
foreach (FileAttachment FileAttach in atAttachments)
{
Console.Write(FileAttach.Name);
System.IO.File.WriteAllBytes("c:\\export\\" + FileAttach.Name, FileAttach.Content);
//save off
}