Searching for Contacts in VSTO
I LOVE VSTO v3!
As part of my demos for Graham Seach's Office DevCon, I've developed a VSTO v3 Outlook Add-in that adds a form region to an incoming e-mail if:
- The sender is in my contacts (based on the e-mail address); and
- The contact has a non-null, not-empty company name
Here's what I did to start off with:
// look up the contact from the sender of the mail
Outlook.MailItem thisMail = (Outlook.MailItem)e.OutlookItem;
Outlook.MAPIFolder ContactFolder =
(Outlook.MAPIFolder)thisMail.Application.Session.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderContacts);
foreach (Outlook.ContactItem contact in ContactFolder.Items)
{
Outlook.ContactItem contact = (Outlook.ContactItem)obj;
if ((contact.Email1Address == thisMail.SenderEmailAddress ||
contact.Email2Address == thisMail.SenderEmailAddress ||
contact.Email3Address == thisMail.SenderEmailAddress) &&
!(contact.CompanyName.Trim() == ""))
{
// Instantiate the service and get the details of the company's sales
SalesDetails.svcSalesDetails.SalesDetailsServiceClient svc =
new SalesDetails.svcSalesDetails.SalesDetailsServiceClient();
_salesDetails = svc.GetSalesDetails(contact.CompanyName);
found = true;
break;
}
}
This threw an InvalidCastException in the foreach line complaining that the object (of type System.__comobject) couldn't be cast to a ContactItem.
So next I looped through the items in the folder as a collection of objects and did the cast within the loop.
foreach (System.Object obj in ContactFolder.Items)
{
Outlook.ContactItem contact = (Outlook.ContactItem)obj;
Same issue (at the cast line again). I'm not sure why I thought this would work any better.
I got a little sidetracked by the first paragraph of Sue Mosher's response to this question on OutlookCode.com and added
Marshal.ReleaseComObject(contact);
at the bottom of the loop (especially as by this stage I'd discovered that it wasn't the first object in the collection that was throwing the exception - it was about the 270th - near enough to Sue's 250).
Actually, I was running into the issue addressed in her second paragraph, but I didn't read that. It took me a call to Nick Randolph (just before we went off to play hockey) to realise that a contact folder can contain things other than Contacts. Thanks Nick.
An easy way to filter the collection is to use the Restrict() method:
Outlook.Items colItems = ContactFolder.Items.Restrict("[MessageClass]='IPM.Contact'");
Finally things were working, but they were still SLOW. Turns out it's lots quicker to let the built-in search function do the heavy lifting; in particular the Find() method. The final incarnation of my find code now looks like this:
Outlook.ContactItem contact;
if (e.OutlookItem is Outlook.MailItem)
{
// look up the contact from the sender of the mail
Outlook.MailItem thisMail = (Outlook.MailItem)e.OutlookItem;
Outlook.MAPIFolder ContactFolder =
(Outlook.MAPIFolder)thisMail.Application.Session.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderContacts);
Outlook.Items colItems = ContactFolder.Items.Restrict("[MessageClass]='IPM.Contact'");
string FilterString = "[Email1Address] = '" + thisMail.SenderEmailAddress + "' OR " +
"[Email2Address] = '" + thisMail.SenderEmailAddress + "' OR " +
"[Email3Address] = '" + thisMail.SenderEmailAddress + "'";
contact = (Outlook.ContactItem)colItems.Find(FilterString);
}
else
{ ...
The moral of the story is two-fold:
- Check your types before attempting to cast; and
- The built-in methods for searching or filtering are almost always better than those you try to roll yourself (it's all about the platform, man!)