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

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.

Related

How to extract body from email

Here is my code I am trying to get body of the email from textdata property but it is giving error object reference not set to instance of an object I dont have a clue what to do
IMAPConfig config = new IMAPConfig("imap-mail.outlook.com","name#hotmail.com", "password", true, true, "");
config.CacheFile = "";
IMAPClient client = null;
client = new IMAPClient(config, null, 5);
IMAPFolder f = client.Folders["Inbox"];
// Console.WriteLine(f.GetMessageByID(7049)) ;
int[] msgCount = null;
while (msgCount == null || msgCount.Length == 0)
{
msgCount = f.CheckForNewMessages();
Thread.Sleep(1000);
}
foreach (int id in msgCount)
{
IMAPMessage msg = f.GetMessageByID(id);
string a = null;
a = msg.TextData.TextData;
//MessageBox.Show(msg.TextData.ToString());
msg.MarkAsRead();
}
Seeing as how InterIMAP is a dead project, I would highly recommend switching to MailKit which is not only actively maintained (by me), but also has a lot more features (I mean, InterIMAP's home page says it will get support for copying messages soon and hasn't been updated since 2009), has a lot more users, and a lot fewer bugs. It's also a lot easier to use.
For example, here's how you would do what you are trying to do with MailKit:
using (var client = new ImapClient ()) {
client.Connect ("imap-mail.outlook.com", 993, true);
client.Authenticate ("name#hotmail.com", "password");
client.Inbox.Open (FolderAccess.ReadWrite);
for (int i = 0; i < client.Inbox.Count; i++) {
var message = client.Inbox.GetMessage (i);
var html = message.HtmlBody;
var text = message.TextBody;
// mark the message as read
client.Inbox.AddFlags (id, MessageFlags.Seen, true);
}
client.Disconnect (true);
}
I've also got MSDN-style documentation (that I'm always working on improving) at http://www.mimekit.net/docs that you might find helpful. If you have any questions, my email address can be found on the github project page (the "MailKit" link at the top of my answer).
It looks like you are using InterIMAP for this. If you look at the source code for IMAPMessage.TextData (https://interimap.codeplex.com/SourceControl/latest#InterIMAP/InterIMAP/Objects/IMAPMessage.cs) you can see that it will return null if there is no plain text body part in the message.
/// <summary>
/// The content of the message as plain text (if available)
/// </summary>
[XmlIgnore]
public IMAPMessageContent TextData
{
get
{
//RefreshData(true, false);
foreach (IMAPMessageContent content in _bodyParts)
{
if (content.ContentType.ToLower().Contains("plain"))
return content;
}
return null;
}
//set { _textData = value; }
}

Running out of memory looping through mail items

Hi I have a Outlook com addin that is doing some simple searching tricks for me. I am part way through putting it together but I am having issues with it running out of memory. The process is very simple and basically loops through an outlook folder checking each mailItem for a match. given the loop reinitialize the variables each time I would have expected the garbage collector to keep up but when I watch the memory it loses ~10m/sec until the system is out of memory and I get unhandled exceptions.
This is part of the code
private void FindInFolder(Outlook.MAPIFolder FolderToSearch)
{
Outlook.MailItem mailItem;
Outlook.MAPIFolder ParentFolder;
int counter = 0;
StatusBar.Text = "Searching in Folder " + FolderToSearch.FolderPath + "/" + FolderToSearch.Name;
StatusBar.Update();
this.Update();
foreach (COMObject item in FolderToSearch.Items)
{
counter++;
if (counter % 100 == 0)
{
StatusBar.Text = FolderToSearch.FolderPath + "/" + FolderToSearch.Name + " item " + counter + " of " + FolderToSearch.Items.Count;
StatusBar.Update();
if (counter % 1000 == 0)
{
GC.Collect();
}
}
if (item is Outlook.MailItem)
{
mailItem = item as Outlook.MailItem;
if (IsMatch(mailItem))
{
if (mailItem.Parent is Outlook.MAPIFolder)
{
ParentFolder = mailItem.Parent as Outlook.MAPIFolder;
ResultGrd.Rows.Add(mailItem.EntryID, ParentFolder.FolderPath, mailItem.SenderName, mailItem.Subject, mailItem.SentOn);
}
}
}
mailItem = null;
}
}
Which calls
private Boolean IsMatch(Outlook.MailItem inItem)
{
Boolean subBool = false;
Boolean NameBool = false;
try
{
if (null != inItem)
{
if (SubjectTxt.Text != "")
{
if (inItem.Subject.Contains(SubjectTxt.Text))
{
subBool = true;
}
}
else
{
subBool = true;
}
if (NameTxt.Text != "")
{
if (inItem.Sender != null)
{
if (inItem.Sender.Name.Contains(NameTxt.Text))
{
NameBool = true;
}
}
}
else
{
NameBool = true;
}
return subBool && NameBool;
}
}
catch (System.Runtime.InteropServices.COMException ce)
{
if (ce.ErrorCode == -2147467259)
{
//DO nothing just move to the next one
}
else
{
MessageBox.Show("Crash in IsMatch error code = " + ce.ErrorCode + " " + ce.InnerException);
}
}
return false;
}
Please excuse all the error catching part at the bottom and the GC.collect they are some of my attempts to work out what is wrong and free up memory.
Note also FindInFolder is called by a new thread so I can interact with results while it continues to search.
What I have tried so far:
Making variables local to function not class so the are retrievable by G, however the most used variable in 'item' as it is part of foreach it must be declared that way.
every 1000 mailItems do a manual GC, this made no difference at all.
For some reason it needs alot of memory just looping through the items and GC never frees them up.
Please also note I am using netoffice not VSTO for Com addin.
First of it all: This is NetOffice code and you dont need Marshal.ReleaseComObject in NetOffice. (Moreover, its useless to call ReleaseComObject here) Use Dispose() for the instance instead.
Keep in your mind: NetOffice handle COM proxies for you (Thats why its allowed to use two 2 dots in NetOffice).
In your case its internaly stored as:
// FolderToSearch
-- Items
--Enumerator
-- Item
-- Item
-- ....
Use item.Dispose() at the end of each loop to remove/free the item instance or use the following after foreach
FolderToSearch.Dipose()
// dispose folder instance and all proxies there comes from
FolderToSearch.DisposeChildInstances()
// dispose all proxies there comes from but keep the folder instance alive
Next:
The Items enumerator here is a custom enumerator(given by NetOffice)
However it works fine with small amount of items but dont do this in a more
heavier scenario(may exchange server and thousands of items). The local workstation/program can't handle this in memory. For this reason Microsoft provides only the well kown GetFirst/GetNext pattern. In NetOffice it looks like:
Outlook._Items items = FolderToSearch.Items;
COMObject item = null;
do
{
if (null == item)
item = items.GetFirst() as COMObject;
if (null == item)
break;
// do what you want here
item.Dispose();
item = items.GetNext() as COMObject;
} while (null != item);
This should works as well.
When working with COM objects from C#, there were 2 tricks that I used to prevent memory and COM object reference counts from building:
Use System.Runtime.InteropServices.Marshal.ReleaseComObject() to release COM objects as you are done with them. This forces a COM "release" on the object.
Do not foreach to iterate through a COM collection. foreach holds on to an enumerator object, which prevents other objects from being released.
So, instead of this:
foreach (COMObject item in FolderToSearch.Items)
{
// ....
}
do this:
Items items = FolderToSearch.Items;
try
{
for (int i = 0; i < items.Count; ++i)
{
COMObject item = items[i];
try
{
// work
}
finally
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(item);
}
}
}
finally
{
System.Runtime.InteropServices.Marshal.ReleaseComObject(items);
}
These tips helped me reduce memory and object consumption.
Disclaimer: I cannot attest to whether this is good practice or not though.
First, I'd recommend using the Find/FindNext or Restrict methods of the Items class instead of iterating through all items in the folder. For example:
Sub DemoFindNext()
Dim myNameSpace As Outlook.NameSpace
Dim tdystart As Date
Dim tdyend As Date
Dim myAppointments As Outlook.Items
Dim currentAppointment As Outlook.AppointmentItem
Set myNameSpace = Application.GetNamespace("MAPI")
tdystart = VBA.Format(Now, "Short Date")
tdyend = VBA.Format(Now + 1, "Short Date")
Set myAppointments = myNameSpace.GetDefaultFolder(olFolderCalendar).Items
Set currentAppointment = myAppointments.Find("[Start] >= """ & tdystart & """ and [Start] <= """ & tdyend & """")
While TypeName(currentAppointment) <> "Nothing"
MsgBox currentAppointment.Subject
Set currentAppointment = myAppointments.FindNext
Wend
End Sub
See the following articles for more information and sample code:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
Also you may find the AdvancedSearch method of the Application class helpful. The key benefits of using the AdvancedSearch method are listed below:
The search is performed in another thread. You don’t need to run another thread manually since the AdvancedSearch method runs it automatically in the background.
Possibility to search for any item types: mail, appointment, calendar, notes etc. in any location, i.e. beyond the scope of a certain folder. The Restrict and Find/FindNext methods can be applied to a particular Items collection (see the Items property of the Folder class in Outlook).
Full support for DASL queries (custom properties can be used for searching too). You can read more about this in the Filtering article in MSDN. To improve the search performance, Instant Search keywords can be used if Instant Search is enabled for the store (see the IsInstantSearchEnabled property of the Store class).
You can stop the search process at any moment using the Stop method of the Search class.
Second, I always suggest releasing underlying COM objects instantly. Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object. You can read more about that in the Systematically Releasing Objects article.
If you want to use GC, you need to call the Collect and WaitForPendingFinalizers methods twice.
Matt, you still don't release all objects in the code. For example:
for (int i = 0; i < FolderToSearch.Items.Count; ++i)
{
COMObject item = FolderToSearch.Items[i];
The Items property of the Folder class returns an instance of the corresponding class which should be released after. I see at lease two lines of code where the reference counter is increased.

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!!!!");
}

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.

Optimizing Outlook Add-Ins

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;
}
}

Categories

Resources