Optimizing Outlook Add-Ins - c#

My Outlook 2007 Addin works very well, until the point where I have to move mass amounts of messages.
The list of mail items is obtained like this
Items itemObj = lNamespace.GetFolderFromID(Settings.Default.InputFolder).Items;
List<MailItem> totalMailItems = new List<MailItem>();
foreach (MailItem mailItem in itemObj)
{
totalMailItems.Add(mailItem);
}
//Dispose of itemObj
itemObj = null;
MAPIFolder blueFold = lNamespace.GetFolderFromID(Settings.Default.BlueFolder);
MAPIFolder greenFold = lNamespace.GetFolderFromID(Settings.Default.GreenFolder);
MAPIFolder orangeFold = lNamespace.GetFolderFromID(Settings.Default.OrangeFolder);
MAPIFolder redFold = lNamespace.GetFolderFromID(Settings.Default.RedFolder);
foreach (MailItem mailItem in totalMailItems)
{
MailItem xMail = mailItem;
MessageRelevance mRel = new MessageRelevance();
mRel = Process_MailItem(ref xMail);
xMail.Save();
switch(mRel)
{
case MessageRelevance.Red:
xMail.Move(redFold);
_lvl2++;
break;
case MessageRelevance.Orange:
xMail.Move(orangeFold);
_lvl1++;
break;
case MessageRelevance.Blue:
xMail.Move(blueFold);
_nullLev++;
break;
case MessageRelevance.Green:
xMail.Move(greenFold);
_lvl0++;
break;
}
xMail = null;
}
The reason I set xMail to mailItem is because I can't use mailItem as a reference, it's readonly. The rest of the program works great now, I'm just trying to figure out how to move these items faster. Do I have to call Save before? Or is that just an extra call?

I am not sure how many mail items you are actually moving, so I will assume alot.
The one thing that may be the issue is that the Save() method could be the bottleneck in your code. I had a similar type issue with an excel add-in that copied files to several locations. The solution to improve the speed and keep excel responsive was to use Asynchronous Delegate Invocation as described in Give Your .NET-based Application a Fast and Responsive UI with Multiple Threads.
So in your example I would wrap the contents of the loop on totalMailItems. Note the code below may not be 100% correct but I hope its enough of an idea and guidance to help you out.
private delegate void SaveEmail(MailItem mailItem);
foreach (MailItem mailItem in totalMailItems)
{
SaveEmail save = SaveMailItem;
IAsyncResult saveResult = save.BeginInvoke(mailItem, SaveCallBack, "MailItem Saved")
xMail = null;
}
private void SaveCallBack(IAsyncResult result)
{ // do stuff here if you want to... }
private void SaveMailItem(MailItem mailItem)
{
MailItem xMail = mailItem;
MessageRelevance mRel = new MessageRelevance();
mRel = Process_MailItem(ref xMail);
xMail.Save();
switch(mRel)
{
case MessageRelevance.Red:
xMail.Move(redFold);
_lvl2++;
break;
case MessageRelevance.Orange:
xMail.Move(orangeFold);
_lvl1++;
break;
case MessageRelevance.Blue:
xMail.Move(blueFold);
_nullLev++;
break;
case MessageRelevance.Green:
xMail.Move(greenFold);
_lvl0++;
break;
}
}

Related

why does the IS operator enters an IF statement when false?

I most be missing something here,
see in the image i made of my debug session.
(items[i] is MailItem) is FALSE according the debugger, but still it enters the if statement.
What am I missing here ?
.
For reference, here is the full code of this method
private MailItem GetMailBySubject(DateTime dateReceived, string subject)
{
MailItem Result = null;
Microsoft.Office.Interop.Outlook.Application OutlookIns = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.NameSpace olNamespace = OutlookIns.GetNamespace("MAPI");
MAPIFolder myInbox = olNamespace.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
Items items = myInbox.Items;
int count = items.Count;
MailItem mail = null;
int i = 1; //DO NOT START ON 0
while ((i < count) && (Result == null))
{
if (items[i] is MailItem)
{
mail = (MailItem)items[i];
if ((mail.ReceivedTime.ToString("yyyyMMdd hh:mm:ss") == dateReceived.ToString("yyyyMMdd hh:mm:ss")) && (mail.Subject == subject))
{
Result = mail;
}
}
i++;
}
return Result;
}
This SO answer explains a bit about why you are seeing the IF condition getting passed even when the expression inside it is false. Apparently it's an issue with the debugger and multiple threads. Also, it suggests a workaround to prevent this issue by using lock. Hope it helps.
I made a workaround using the link provided by Wai Ha Lee. I had to alter it though, because testing if an item is MailItem still behaved strangely.
So I copy the items first into a separate list, and make sure only items of type MailItem are in that list.
The only way I got this filtered is by using try...catch, I would still like a better way and I am still curious why the test if (items[i] is MailItem) behaves so strangely.
List<MailItem> ReceivedEmail = new List<MailItem>();
foreach (var testMail in items)
{
try
{
ReceivedEmail.Add((MailItem)testMail);
}
catch (System.Exception ex)
{
;
}
}
After this I can use the list ReceivedEmail without the check on MailItem.

Outlook Add-in Exception - How to check the type of Outlook.Item?

I have an Outlook 2013 Add-in,
Outlook.MAPIFolder inboxFolder;
Outlook.Items mailInboxItems;
private void ThisAddIn_Startup(object sender, EventArgs e)
{
... other code ---
mailInboxItems = inboxFolder.Items;
mailInboxItems.ItemAdd += mailInboxItems_ItemAdd;
}
private void mailInboxItems_ItemAdd(object item)
{
Outlook.MailItem emailMessage = (Outlook.MailItem)item; // cast error
ProcessEmail(emailMessage);
}
An exception is thrown when, of course, the item coming in is not of type Outlook.MailItem:
Unable to cast COM object of type 'System.__ComObject' to interface
type 'Microsoft.Office.Interop.Outlook.MailItem'.
How can I check that the parameter "item" is of only a valid type, i.e. Outlook.MailItem to avoid any exceptions?
You can use the "is" and "as" operators in C#. See How to: Programmatically Determine the Current Outlook Item for more information.
Also the Outlook object model provides the MessageClass property - a string representing the message class for the Outlook item. Under the hood the message class is used to identify what inspector to use in Outlook for displaying the item.
To your folder you can add different kind of items like MailItem, AppointmentItem and so. To work with MailItems use code like this:
var emailMessage = item as MailItem;
if(emailMessage == null)
{
retrun;
}
// here you can use emailMessage as MailItem
ProcessEmail(emailMessage);
First check item is mailitem like
if(item is Outlook.MailItem){
Outlook.MailItem emailMessage =item as Outlook.MailItem
ProcessEmail(emailMessage);
}
it will work as your expectation.
There are actually, at least, 16 Outlook item types. Documentation itemizing the different types is a bit hard to find, but one reference is here.
So, for example, if you wanted to move all the items from one folder to another, you could do something like:
Items items = source.Items;
foreach (object item in items)
{
switch (item)
{
case AppointmentItem appointmentItem:
appointmentItem.Move(destination);
break;
case ContactItem contactItem:
contactItem.Move(destination);
break;
case DistListItem distListItem:
distListItem.Move(destination);
break;
case DocumentItem documentItem:
documentItem.Move(destination);
break;
case JournalItem journalItem:
journalItem.Move(destination);
break;
case MailItem mailItem:
mailItem.Move(destination);
break;
case MeetingItem meetingItem:
meetingItem.Move(destination);
break;
case NoteItem noteItem:
noteItem.Move(destination);
break;
case PostItem postItem:
postItem.Move(destination);
break;
case RemoteItem remoteItem:
remoteItem.Move(destination);
break;
case ReportItem reportItem:
reportItem.Move(destination);
break;
case TaskItem taskItem:
taskItem.Move(destination);
break;
case TaskRequestAcceptItem taskRequestAcceptItem:
taskRequestAcceptItem.Move(destination);
break;
case TaskRequestDeclineItem taskRequestDeclineItem:
taskRequestDeclineItem.Move(destination);
break;
case TaskRequestItem taskRequestItem:
taskRequestItem.Move(destination);
break;
case TaskRequestUpdateItem taskRequestUpdateItem:
taskRequestUpdateItem.Move(destination);
break;
}
Marshal.ReleaseComObject(item);
}
The type matching in the case statements requires C# 7 or higher.

EWS managed API. IsNew Always return false and can't use TextBody

I am a newbie developer and I have been stuck with EWS for hours now. I need to read through the most recent emails, get all the unread emails and use the data from them to do some stuff.
At this moment My code looks like this.
static void Main(string[] args)
{
ServicePointManager.ServerCertificateValidationCallback = CertificateValidationCallBack;
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013);
service.Credentials = new WebCredentials("support#mycompany.com", "mysupersuperdupersecretpassword");
service.AutodiscoverUrl("support#mycompany.com", RedirectionUrlValidationCallback);
FindItemsResults<Item> findResults = service.FindItems(WellKnownFolderName.Inbox,new ItemView(2));
foreach (Item item in findResults.Items)
{
// works perfectly until here
Console.WriteLine(item.Subject);
Console.WriteLine('\n');
item.Load();
string temp = item.Body.Text;
// I can't seem to get TextBody to work. so I used a RegEx Match match = Regex.Match(temp, "<body>.*?</body>", RegexOptions.Singleline);
string result = match.Value;
result = result.Replace("<body>", "");
result = result.Replace("</body>", "");
Console.Write(result);
Console.WriteLine('\n');
//Now the email boddy is fine but IsNew always returns false.
if (item.IsNew)
{
Console.WriteLine("This message is unread!");
}
else
{
Console.WriteLine("This message is read!");
}
}
}
I have googled and tried and googled some more and I am stuck. How do I now which emails are read, is there a way to get the email body text that's more effective than what I have done ? Any help would be super appreciated.
The MSDN article for usage is pretty good if you haven't already read it.
For your issue, cast your item to an EmailMessage
foreach (Item item in findResults.Items)
{
var mailItem = (EmailMessage)item;
// works perfectly until here
Console.WriteLine(mailItem.Subject);
}
I did notice you are not using Property Sets, but having only used EWS for event notifications and not going through existing mails, it may be different.
UPDATE Additions in light of your changes
use this for your Property Set
new PropertySet(BasePropertySet.FirstClassProperties) {
RequestedBodyType = BodyType.Text
};
Also this reads a little nicer and uses the Body.Text property
foreach (Item myItem in findResults.Items.Where(i=>i is EmailMessage))
{
var mailItem = myItem as EmailMessage;
Console.WriteLine(mailItem.Subject);
mailItem.Load(new PropertySet(BasePropertySet.FirstClassProperties) {
RequestedBodyType = BodyType.Text
}); // Adding this parameter does the trick :)
Console.WriteLine(mailItem.Body.Text);
if(! mailItem.IsRead)
Console.WriteLine("Who is Your Daddy!!!!");
}

Searching public Outlook contacts folder from C#

We have a large public contacts folder in Outlook called Global Contacts, I'd like to be able to search through it and return a number of results that match certain criteria, ideally wildcard-style.
E.g. if someone puts "je" in the 'name' textbox, it will return all contacts whose names contain 'je'. This may be coupled as an AND with a companyname textbox.
Most of the examples I've seen are either in VB, or are concerned with doing this form a web app - I'm doing a winforms app, and every machine has Outlook 2002 installed (yeah, I know, update long overdue).
Can anyone point me in the right direction? Some code would be nice as a place to start.
Cheers
I ended up doing this:
Microsoft.Office.Interop.Outlook._Application objOutlook; //declare Outlook application
objOutlook = new Microsoft.Office.Interop.Outlook.Application(); //create it
Microsoft.Office.Interop.Outlook._NameSpace objNS = objOutlook.Session; //create new session
Microsoft.Office.Interop.Outlook.MAPIFolder oAllPublicFolders; //what it says on the tin
Microsoft.Office.Interop.Outlook.MAPIFolder oPublicFolders; // as above
Microsoft.Office.Interop.Outlook.MAPIFolder objContacts; //as above
Microsoft.Office.Interop.Outlook.Items itmsFiltered; //the filtered items list
oPublicFolders = objNS.Folders["Public Folders"];
oAllPublicFolders = oPublicFolders.Folders["All Public Folders"];
objContacts = oAllPublicFolders.Folders["Global Contacts"];
itmsFiltered = objContacts.Items.Restrict(strFilter);//restrict the search to our filter terms
Then just looping through itmsFiltered to add it to an ObjectListView. Hopefully this will be of use to someone else looking to do the same - it took me a while to cobble this together from various sources.
to find contacts folder you can iterate items of olFolderContacts. Here is the code
using System;
using Microsoft.Office.Interop.Outlook;
using Application = Microsoft.Office.Interop.Outlook.Application;
namespace RyanCore
{
public class Loader
{
public static ContactsViewModel LoadModel(Application objOutlook)
{
var viewModel = new ContactsViewModel();
MAPIFolder fldContacts = objOutlook.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);
foreach (object obj in fldContacts.Items)
{
if (obj is _ContactItem)
{
var contact = (_ContactItem) obj;
viewModel.Contacts.Add(new Contact(contact.FirstName + " " + contact.LastName, contact.Email1Address));
}
else if (obj is DistListItem)
{
var distListItem = (DistListItem) obj;
var contactGroup = new ContactGroup(distListItem.Subject);
viewModel.Groups.Add(contactGroup);
for (Int32 i = 1; i <= distListItem.MemberCount; i++)
{
Recipient subMember = distListItem.GetMember(i);
contactGroup.Contacts.Add(new Contact(subMember.Name, subMember.AddressEntry.Address));
}
}
}
return viewModel;
}
}
}

How to release Outlook objects correctly?

I just can't release my Outlook MailItems. After opening 200 Mails the Exchange Sever returns the maximum open Emails is reached.
I'm remove my UserProperty from all selected Mail.
My Code:
foreach (var selection in Globals.ThisAddIn.Application.ActiveExplorer().Selection)
{
if (selection is MailItem)
{
MailItem mi = (MailItem)selection;
UserProperty up = mi.UserProperties.Find("MyProp");
if (up != null)
{
up.Delete();
//##################################
// I also tried :
//----------------------------------
// Marshal.ReleaseComObject(up);
// up = null;
//----------------------------------
}
mi.Save();
//##################################
// I also tried :
//----------------------------------
// mi.Close(OlInspectorClose.olDiscard);
//----------------------------------
// I don't know if this loop is necessary, but I have found it somewhere on the web
while (Marshal.ReleaseComObject(mi) > 0);
mi = null;
//##################################
// I also tried :
//----------------------------------
// GC.Collect();
// GC.WaitForPendingFinalizers();
//----------------------------------
}
}
Any idea what's wrong?
You can try this: Instead of defining a new MailItem everytime within the For loop, can you define mi outside the For loop or even in your class level, and reuse it for each mailitems? e.g:
MailItem mi;
foreach (var selection in Globals.ThisAddIn.Application.ActiveExplorer().Selection)
{
if (selection is MailItem)
{
mi= (MailItem)selection;
// your other code...
}
}
mi=null;
GC.Collect();
GC.WaitForPendingFinalizers();
EDIT:
Try to create local variable for each references e.g:
Outlook.Explorer myExplorer=Application.ActiveExplorer();
Outlook.Selection mySelection=myexplorer.Selection;
foreach (var selection in mySelection)
{
}
myExplorer=null;
mySelection=null;
//....
EDIT-2:
IF you are using Outlook 2010 check this:
Outlook 2010 addin selection not clearing
I believe its any kind of bug like Bolu said.
Again much thanks for your help Bolu.
I'm now using following workaround:
List entryids = new List();
foreach (var selection in Globals.ThisAddIn.Application.ActiveExplorer().Selection)
{
MailItem mi = selection as MailItem;
if (mi != null)
{
// For any reason it's not possible to change the mail here
entryids.Add(mi.EntryID);
Marshal.ReleaseComObject(mi);
mi = null;
}
}
foreach (string id in entryids)
{
MailItem mi = Globals.ThisAddIn.Application.ActiveExplorer().Session.GetItemFromID(id);
// My changes on the mail
mi.Save();
Marshal.ReleaseComObject(mi);
mi = null;
}
You are using multiple dot notation (mi.UserProperties.Find), which means the compiler creates an implicit variable to hold the result of the mi.UserProperties call; you cannot explicitly release that variable. That object holds a reference to its parent MailItem object.
Store it in an explicit variable and release it explicitly using Marshal.ReleaseComObject. Ditto for the UserProperty up variable.
Also, do not use foreach with Outlook collections - that loop holds a reference to all items until the loop exits. Use a for loop and release the items explicitly on each step of the loop immediately after you are done with that item
Selection selectedItems = Globals.ThisAddIn.Application.ActiveExplorer().Selection;
for (int i = 1; i <= selectedItems.Count; i++)
{
object selection = selectedItems[i];
MailItem mi = selection as MailItem;
if (mi != null) //can have items other than MailItem
{
UserProperties props = mi.UserProperties;
UserProperty up = props.Find("MyProp");
if (up != null)
{
...
Marshal.ReleaseComObject(up);
};
Marshal.ReleaseComObject(props);
Marshal.ReleaseComObject(mi);
}; //if
Marshal.ReleaseComObject(selection);
}; //for
Try to replace foreach with a for loop and do the following
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Also remove all the reference to any outlook COM object that you might be using.
Just put the releasing method outside the main if condition. Items get referenced even when you just loop through them with the foreach loop.
Write
Marshal.ReleaseComObject(mi)
right before the last "}". This works for me. I read that generally you have to release every COM object explicitly.

Categories

Resources