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:

  1. The sender is in my contacts (based on the e-mail address); and
  2. 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:

  1. Check your types before attempting to cast; and
  2. 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!)