Exchange Web Services: Batching with FindItemsResult<Item> - c#

I'm writing an app to process email attachments, using Exchange Web Services.
The general structure of my problem area is as follows:
public static void Main()
{
FindItemsResults<Item> findResults = FindItems();
foreach (Item item in findResults)
{
DoSomethingWithItem(item);
}
}
public static FindItemsResults<Item> FindItems()
{
FindItemsResults<Item> findResults;
ItemView view = new ItemView(10); //batching 10 at a time
view.OrderBy.Add(ItemSchema.DateTimeReceived, SortDirection.Ascending);
view.PropertySet = new PropertySet(
BasePropertySet.IdOnly,
ItemSchema.Subject,
ItemSchema.DateTimeReceived);
findResults = service.FindItems(
WellKnownFolderName.Inbox,
new SearchFilter.SearchFilterCollection(
LogicalOperator.Or,
new SearchFilter.ContainsSubstring(ItemSchema.Subject, Properties.Settings.Default.EmailSubject)),
view);
//return set of emails
return findResults;
}
At first, this looked OK - it processed my earlier test examples perfectly well. But when I start testing with bulk loads, I realised that it was only processing the first 10 items, since I was specifying a batch size of 10 items (ItemView view = new ItemView(10)), but I wasn't checking for further batches.
I could have simply increased the batch size, but a quick google later, I found a better example:
public static FindItemsResults<Item> FindItems()
{
FindItemsResults<Item> findResults;
ItemView view = new ItemView(10, 0, OffsetBasePoint.Beginning);
view.OrderBy.Add(ItemSchema.DateTimeReceived, SortDirection.Ascending);
view.PropertySet = new PropertySet(
BasePropertySet.IdOnly,
ItemSchema.Subject,
ItemSchema.DateTimeReceived);
do
{
findResults = service.FindItems(
WellKnownFolderName.Inbox,
new SearchFilter.SearchFilterCollection(
LogicalOperator.Or,
new SearchFilter.ContainsSubstring(ItemSchema.Subject, Properties.Settings.Default.EmailSubject)),
view);
//any more batches?
if (findResults.NextPageOffset.HasValue)
{
view.Offset = findResults.NextPageOffset.Value;
}
}
while (findResults.MoreAvailable);
return findResults;
}
This loops through as many emails as I care to throw at it, but for reasons I can't yet understand, the foreach loop now only processes the first item in findResults.
Even though findResults contains more than one item (findResults.Items.Count > 1), with my second example, findResults.MoreAvailable = false. Fair enough, I've looped through the batches earlier, so it makes sense that I'm looking at the end of the last batch.
But how do I reset findResults so that it will process the whole lot? I tried setting findResults.MoreAvailable but it is readonly... What am I missing?
CONCLUSION:
OK, so I can either process the items one batch at a time, or I can add each item in each batch to a List and process them later, as I currently do.
There is not a lot between them; I've initially started using a List, but I'll consider the choice further.

It seems to my that your main problem is that you try define a function which returns FindItemsResults<Item>. If you use paring of results you will have not one object of this type. Instead of that on retrieving of every new page the FindItemsResults<Item> will be overwritten. Following example display subjects of all items from the Inbox:
ItemView view = new ItemView(10, 0, OffsetBasePoint.Beginning);
view.OrderBy.Add(ItemSchema.DateTimeReceived, SortDirection.Descending);
view.PropertySet = new PropertySet(
BasePropertySet.IdOnly,
ItemSchema.Subject,
ItemSchema.DateTimeReceived);
// save the folder where we will make searching to do this one time
Folder myInbox = Folder.Bind(service, WellKnownFolderName.Inbox);
FindItemsResults<Item> findResults;
do
{
findResults = myInbox.FindItems(
new SearchFilter.ContainsSubstring(ItemSchema.Subject,
Properties.Settings.Default.EmailSubject)),
view);
foreach (Item item in findResults)
{
// Do something with the item.
Console.WriteLine();
if (item is EmailMessage)
{
EmailMessage em = item as EmailMessage;
Console.WriteLine("Subject: \"{0}\"", em.Subject);
}
else if (item is MeetingRequest)
{
MeetingRequest mr = item as MeetingRequest;
Console.WriteLine("Subject: \"{0}\"", mr.Subject);
}
else
{
// we can handle other item types
}
}
//any more batches?
if (findResults.NextPageOffset.HasValue)
{
view.Offset = findResults.NextPageOffset.Value;
}
}
while (findResults.MoreAvailable);
The code display the subjects on the console output. If you want to use EmailMessage or MeetingRequest in another way you should modify the code correspondent. You can also define a delegate which do something with the found EmailMessage or MeetingRequest and call the delegate on the place of Console.WriteLine. If you do need to same all items somewhere, then you will have to create some collection like List<Item>, fill there in the function and return instead of FindItemsResults<Item> which you currently do.

Related

ExchangeService FindItems Suddenly Intermittently Not Working

This has been working perfectly for probably a year or more, then suddenly, I think as of Saturday October 12th it started failing (FindResults returns no items);
//Tag the sent email so we can pull it back in a moment
Guid myPropertySetId = new Guid("{375a1079-a049-4c2d-a2e1-983d588cbed4}");
ExtendedPropertyDefinition myExtendedPropertyDefinition = new ExtendedPropertyDefinition(myPropertySetId, "TelEmailGuid", MapiPropertyType.String);
Guid telEmailGuid = Guid.NewGuid();
message.SetExtendedProperty(myExtendedPropertyDefinition, telEmailGuid.ToString());
//Send the email
message.SendAndSaveCopy(completedFolder);
//Find the sent email
ItemView view = new ItemView(1);
SearchFilter searchFilter = new SearchFilter.IsEqualTo(myExtendedPropertyDefinition, telEmailGuid.ToString());
view.PropertySet = new PropertySet(BasePropertySet.IdOnly);
FindItemsResults<Item> findResults = service.FindItems(completedFolder, searchFilter, view);
return DownloadEmail(findResults.FirstOrDefault().Id.ToString());
I tried tweaking things a little to wait before trying to find the e-mail, this has helped (now maybe 10% succeed). So, I then added a loop, so if not found it will try again a few times. But it seems that if it isn't found the first time, it is not found on subsequent attempts;
//Tag the sent email so we can pull it back in a moment
Guid myPropertySetId = new Guid("{375a1079-a049-4c2d-a2e1-983d588cbed4}");
ExtendedPropertyDefinition myExtendedPropertyDefinition = new ExtendedPropertyDefinition(myPropertySetId, "TelEmailGuid", MapiPropertyType.String);
Guid telEmailGuid = Guid.NewGuid();
message.SetExtendedProperty(myExtendedPropertyDefinition, telEmailGuid.ToString());
//Send the email
message.SendAndSaveCopy(completedFolder);
//Find the sent email
ItemView view = new ItemView(1);
SearchFilter searchFilter = new SearchFilter.IsEqualTo(myExtendedPropertyDefinition, telEmailGuid.ToString());
view.PropertySet = new PropertySet(BasePropertySet.IdOnly);
int attempt = 1;
System.Threading.Thread.Sleep(1000);
FindItemsResults<Item> findResults = service.FindItems(completedFolder, searchFilter, view);
while (findResults.TotalCount == 0 && attempt < 5)
{
findResults = service.FindItems(completedFolder, searchFilter, view);
attempt++;
}
return DownloadEmail(findResults.FirstOrDefault().Id.ToString());
Does anyone have any suggestions? I suspect it is a Microsoft issue but perhaps a different approach might allow us to workaround the issue.
It sounds like its an issue with the Search timing out because your not searching an indexed property as the FolderItem Count grow the performance of the search will decline over time (also other factors like server load etc will have a direct effect when searching for item in a Folder with a large item count).
Your search looks pretty static so you could create a Search Folder https://learn.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/dd633690(v%3Dexchg.80) which would optimize the search.
The other thing you maybe be able to do is add a DateTime restriction for the search eg I have code that searches for messages based on the Internet MessageId and it has a similar problem when Item counts get large the search times out. So because I know what I'm searching for are always recent email adding a Date Time restriction fixed the issue in this instance eg
SearchFilter internetMessageIdFilter = new SearchFilter.IsEqualTo(PidTagInternetMessageId, InternetMessageId);
SearchFilter DateTimeFilter = new SearchFilter.IsGreaterThan(EmailMessageSchema.DateTimeReceived, DateTime.Now.AddDays(-1));
SearchFilter.SearchFilterCollection searchFilterCollection= new SearchFilter.SearchFilterCollection(LogicalOperator.And);
searchFilterCollection.Add(internetMessageIdFilter);
searchFilterCollection.Add(DateTimeFilter);

Trouble reading contact.addresses in EWS. All contacts read display "The given key was not present in the dictionary."

Can't get the first email address to display. All records display like this:
"The given key was not present in the dictionary". There are definately email addresses associated with the contacts. Here is the code
....
static void Reademail()
{
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013);
service.UseDefaultCredentials = true;
service.AutodiscoverUrl("validaddress#ddd.com");
// Get the number of items in the contacts folder. To limit the size of the response, request only the TotalCount property.
ContactsFolder contactsfolder = ContactsFolder.Bind(service,WellKnownFolderName.Contacts,new PropertySet(BasePropertySet.IdOnly, FolderSchema.TotalCount));
// Set the number of items to the number of items in the Contacts folder or 50, whichever is smaller.
int numItems = contactsfolder.TotalCount < 50 ? contactsfolder.TotalCount : 50;
// Instantiate the item view with the number of items to retrieve from the Contacts folder.
ItemView view = new ItemView(numItems);
// To keep the request smaller, request only the display name property.
view.PropertySet = new PropertySet(BasePropertySet.IdOnly, ContactSchema.DisplayName);
// Retrieve the items in the Contacts folder that have the properties that you selected.
FindItemsResults<Item> contactItems = service.FindItems(WellKnownFolderName.Contacts, view);
// Display the list of contacts.
foreach (Item item in contactItems)
{
if (item is Contact)
{
Contact contact = item as Contact;
Console.Write(contact.DisplayName + " ");
try
{
Console.WriteLine(contact.EmailAddresses[EmailAddressKey.EmailAddress1].Name);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
Console.ReadKey();
}
.....
the fix I found was to use the contact.Load statement. Although maybe not the most efficient as it mask a call back to EWS. Apparently this loads the primary class and the email addresses are then visible.

EWS search filter to find sender domain C#

I've create a small application that gets all my emails. I add each email to a list. However before I add them to a list I filter them. All of my filters are working apart from one. My search filter for filtering my email by sender is not working as I'm only trying to filter the domain and not the whole email address. For example xxx#xxx.com will filter however I want to filter out everything with xxx.com domain and for some reason it doesn't filter it. I tried using substring and this doesn't work either.
My code is as fallows
private static SearchFilter.SearchFilterCollection sFilter = new SearchFilter.SearchFilterCollection();
private static FindItemsResults<Item> findResults;
sFilter.Add(new SearchFilter.Not(new SearchFilter.ContainsSubstring(EmailMessageSchema.Sender, "#xxx.com",ContainmentMode.Substring,ComparisonMode.IgnoreCase)));
sFilter.Add(new SearchFilter.Not(new SearchFilter.Exists(EmailMessageSchema.InReplyTo)));
DateTime startTime = GetDateValueforFilter();
startTimefilter = new SearchFilter.IsGreaterThanOrEqualTo(EmailMessageSchema.DateTimeReceived, startTime);
sFilter.Add(startTimefilter);
sFilter.Add(startTimefilter);
findResults = service.FindItems(
WellKnownFolderName.Inbox
,sFilter
,new ItemView(25));
foreach (EmailMessage item in findResults.Items)
{
//if (item.IsRead == false)
//{
// if (item.InReplyTo == null)
// {
bool replyToAll = true;
string myReply = "This is the message body of the email reply.";
item.Reply(myReply, replyToAll);
item.IsRead = true;
item.Send();
//}
//}
}
I would suggest you try using the Extended Property PidTagSenderSmtpAddress https://msdn.microsoft.com/en-us/library/office/jj713594.aspx and try something like
ExtendedPropertyDefinition PidTagSenderSmtpAddress = new ExtendedPropertyDefinition(0x5D01,MapiPropertyType.String);
SearchFilter sf = new SearchFilter.ContainsSubstring(PidTagSenderSmtpAddress, "#yahoo.com");

List all available extended properties using EWS

I'm trying to find a way to list ALL extended properties for set of calendar items using EWS.
The problem is that all examples I managed to find online require me to know what those extended properties are, in advance. Here's the official MSDN example.
What am I supposed to do if I do not know the IDs or names of extended properties? Or if I don't even know if any extended properties exist at all?
I've tried the following code but it returns an exception...
var calendarItems = service.FindAppointments(WellKnownFolderName.Calendar, view);
var propertySet = new PropertySet(AppointmentSchema.ExtendedProperties);
service.LoadPropertiesForItems(calendarItems, propertySet);
Here's the exception:
Microsoft.Exchange.WebServices.Data.ServiceResponseException: The request failed schema validation: The required attribute 'FieldURI' is missing.
There is no call in EWS to get all extended properties. The idea behind extended properties is that applications use them to store app-specific data, so only that app needs to know the specifics on their properties.
Extended MAPI can discover this information. https://github.com/stephenegriffin/mfcmapi has a ton of sample code for different tasks, including iterating named properties.
I was also looking similar and I just did a kind of reverse engineering. Since the extended property is the combination of Id (integer) and data type which we could not know as they are not documented on any MSDN. So, iterate 1 to some huge number like 15000 for string type property and find those which can be loaded successfully - this is the main trickest part which we can do by putting try-catch to bind that extended property. Then you could get the required one.
Hope that helps.
List<int> allStringIds = new List<int>();
for (int i = 0; i <= 15000; i++)
{
allStringIds.Add(i);
}
ParallelOptions options = new ParallelOptions
{
MaxDegreeOfParallelism = 200,
CancellationToken = CancellationToken.None,
};
Parallel.For(0, allStringIds.Count, options, index =>
{
try
{
ExtendedPropertyDefinition extendedPropertyDefinition = new ExtendedPropertyDefinition(index,
MapiPropertyType.String);
latestMessage = EmailMessage.Bind(service, item.Id.UniqueId,
new PropertySet(BasePropertySet.FirstClassProperties, extendedPropertyDefinition));
_logger.Write("Supported string property id=" + index);
supportedListId.TryAdd(index, index);
}
catch(Exception ex)
{
}
});
foreach (var a in supportedListId)
{
ExtendedPropertyDefinition extendedPropertyDefinition = new ExtendedPropertyDefinition(a.Key,
MapiPropertyType.String);
allExtendedPropertyDefinitions.Add(extendedPropertyDefinition);
}
latestMessage = EmailMessage.Bind(service, item.Id.UniqueId,
new PropertySet(BasePropertySet.FirstClassProperties, allExtendedPropertyDefinitions));
foreach (var extendedProperty in latestMessage.ExtendedProperties)
{
if (extendedProperty.PropertyDefinition != null && extendedProperty.PropertyDefinition.Tag != null)
{
if (extendedProperty.Value != null)
{
_logger.Write($"OMG... extendedProperty id={extendedProperty.PropertyDefinition.Id}," +
$" name={ extendedProperty.PropertyDefinition.Name}, value={extendedProperty.Value}");
}
}
}

How to restore deleted appointment

How to restore deleted appointment using EWS 2.0?
I think i could search for it in the WellKnownFolderName.RecoverableItemsDeletions folder. But all i have is ItemId. And sadly I cant use it in SearchFilter...
What is the best way?
my try:
ItemView view = new ItemView(10);
SearchFilter searchFilter = new SearchFilter.IsEqualTo(ItemSchema.Id, itemChange.ItemId);
var findResults = exchangeService.FindItems(WellKnownFolderName.RecoverableItemsDeletions, searchFilter, view);
List<ItemId> ids = null;
foreach (var findResult in findResults)
{
Debug.WriteLine(findResult.Id.ToString());
ids.Add(findResult.Id);
}
exchangeService.MoveItems(ids, WellKnownFolderName.Calendar);
an error occurs:
{"Values of type 'ItemId' can't be used as comparison values in search filters."}
Set your WellKnownFolderName to DeletedItems when you are searching for the appointments. And you should set up your search filter to only return appointments since the DeletedItems folder can hold more than just the appointments you are looking for. Here is an example that should work for you.
ItemView view = new ItemView(10);
// Only look for appointments
SearchFilter searchFilter = new SearchFilter.IsEqualTo(ItemSchema.ItemClass, "IPM.Appointment");
// Look for items in the DeletedItems folder
FindItemsResults<Item> results = service.FindItems(WellKnownFolderName.DeletedItems, searchFilter, view);
// Move each of the deleted items back to the calendar
List<ItemId> ItemsToMove = new List<ItemId>();
foreach (Item item in results)
{
ItemsToMove.Add(item.Id);
}
service.MoveItems(ItemsToMove, WellKnownFolderName.Calendar);

Categories

Resources