Reputation: 95
Outlook 2016
.Net Framework 4.5
i encounter a really strange behaviour: when i iterate through the items collection of a contact folder in some very special undefined cases (which i do not really understand) some userproperties of the first item of the collection fail to load. However the UserProperties are definitly set.
The approach is following:
I open the contact folder (to which the items will be moved) in outlook.
then i execute the "test"
the execution of the test can be suammrized as following:
click button ->
start thread
iterate through the items (on first iteration no items are present).
add new items{
create item
set userproperty PRE before item is initially saved
save item
move item to desired folder
set userproperty POST after item is moved
save item
}
end thread
click button ->
start thread
iterate through the items (here the userproperty POST sometimes fails to load on the first item of the collection, however when i investigate it, it IS there. It only fails for the first item and succeeds for every other following item).
...END
it seems to me that outlook somehow fails to update the userproperty definitions timely. But note that the first BackgroundWorker thread is already finished when iterating through the items with the second backgroundworker thread. The problem could be related to the fact that iam viewing the folder in the explorer while the items are added and iterated. This bug is hard to reproduce and does only occur rarely. however i'm really missing insight into the inner workings of outlook so i can only speculate.
Idea for workarounds: I could add an item with all userproperties before moving it. the problem here is that i need to add new userproperties, after the item is initially saved and moved to the folder, in some scenarios. in few cases the userproperty key is dynamically created (with a pattern) so it wouldn't be optimal to predefine all userproperties. It's very important that the userProperties are reliably loaded because some important features are based upon them.
Does anybody has a clue how the problem is caused and how to solve it? because this behaviour is driving me crazy.
some Code (not the original but it should contain all the relevant aspects)
//Ribbon
TestNS.TestCaller testCaller;
string folderID = "00000000BDB409934ED327439481EB6E1E1CC4D3010055B62301B58E32478DCD8C0D3FA6304600002C4CA4400000";
public void testButton0_Action(Office.IRibbonControl control)
{
if(testCaller == null){
testCaller = new TestNS.TestCaller(ThisAddIn.Outlook,folderID);
}
testCaller.Run();
}
//Ribbon end
using System.Runtime.InteropServices;
using Outlook = Microsoft.Office.Interop.Outlook;
using System.Diagnostics;
using System.Windows.Forms;
using System.ComponentModel;
namespace TestNS
{
public class TestCaller{
private Outlook.Application application;
private BackgroundWorker worker = new BackgroundWorker();
private Test test = null;
private string folderId;
private bool init = true;
private bool busy = false;
public TestCaller(Outlook.Application application, string folderId){
this.application = application;
this.folderId = folderId;
worker.DoWork += new DoWorkEventHandler(DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnCompleted);
}
public void Run()
{
if (!busy)
{
busy = true;
test = new Test(application, folderId, init);
worker.RunWorkerAsync();
}
}
private void DoWork(object sender, DoWorkEventArgs e)
{
test.Process();
test = null;
}
private void OnCompleted(object sender, RunWorkerCompletedEventArgs e)
{
busy = false;
init = false;
}
}
class Test
{
public const string key_preCreateProperty ="preCreate";
public const string key_postCreateProperty = "postCreate";
private Outlook.Application application;
private string folderId;
private bool createData;
public Test(Outlook.Application application,string folderId,bool createData)
{
this.application = application;
this.folderId = folderId;
this.createData = createData;
}
public void Process(){
Examine();
if(createData){
CreateData();
}
}
public void CreateData()
{
List<Poco> pocos = new List<Poco>();
for (int i = 0; i < 10; i++)
{
pocos.Add(
new Poco
{
Pre = "Pre" + i,
Post = "Post" + i
}
);
}
CreateContactItems(folderId,pocos);
}
public void Examine()
{
bool preIsLoaded = false;
bool postIsLoaded = false;
Debug.WriteLine(">>>Examine");
Outlook.MAPIFolder folder = application.Session.GetFolderFromID(folderId);
Outlook.Items folderItems = folder.Items;
int i = 0;
//print UserProperties registered to the items
foreach(Outlook.ContactItem contactItem in folderItems){
var itemUserProperties = contactItem.UserProperties;
string itemUserPropertiesString = "";
foreach (var itemProp in itemUserProperties)
{
Outlook.UserProperty prop = (Outlook.UserProperty)itemProp;
itemUserPropertiesString += " " +prop.Name + " " + prop.Value + " \n";
}
//HERE: sometimes it prints only Pre on the first index of the iteration
Debug.WriteLine(string.Format("i={0} , itemUserProperties Count={1} , following UserProperties: \n{2}", i++, itemUserProperties.Count, itemUserPropertiesString));
string pre = null;
string post = null;
try
{
pre = contactItem.GetUserProperty(key_preCreateProperty);
preIsLoaded = true;
}
catch(KeyNotFoundException ex){
Debug.WriteLine("Error: Pre Not found"); //should not happen - doesn't happen
}
try
{
post = contactItem.GetUserProperty(key_postCreateProperty);
postIsLoaded = true;
}
catch (KeyNotFoundException ex)
{
Debug.WriteLine("Error: Post Not found"); //shoul not happen - happens rarely totally indeterminitic
}
Marshal.ReleaseComObject(itemUserProperties);
}
Debug.WriteLine("<<<Examine");
if (folderItems.Count > 0 && (!preIsLoaded || !postIsLoaded))
{
MessageBox.Show("preIsLoaded="+preIsLoaded +" \n" +"postIsLoaded="+postIsLoaded);
}
Marshal.ReleaseComObject(folderItems);
Marshal.ReleaseComObject(folder);
}
public void CreateContactItems(string folderId,List<Poco> pocos)
{
Outlook.MAPIFolder folder = application.Session.GetFolderFromID(folderId);
foreach(Poco poco in pocos){
CreateContactItem(folder,poco);
}
Marshal.ReleaseComObject(folder);
}
public void CreateContactItem(Outlook.MAPIFolder testFolder,Poco data)
{
Outlook.ContactItem contactItem = application.CreateItem(Outlook.OlItemType.olContactItem);
contactItem.SetUserProperty(key_preCreateProperty, data.Pre);
contactItem.Save();
Outlook.ContactItem movedContactItem = (Outlook.ContactItem)contactItem.Move(testFolder);
Marshal.ReleaseComObject(contactItem);
contactItem = movedContactItem;
contactItem.FirstName = data.Pre;
contactItem.LastName = data.Post;
contactItem.SetUserProperty(key_postCreateProperty, data.Post);
contactItem.Save();
Marshal.ReleaseComObject(contactItem);
}
}
public static class Util
{
public static void SetUserProperty(this Outlook.ContactItem item, string name, dynamic value)
{
Outlook.UserProperty property = item.UserProperties[name];
if (property == null)
{
property = item.UserProperties.Add(name, Outlook.OlUserPropertyType.olText);
}
property.Value = value;
}
public static dynamic GetUserProperty(this Outlook.ContactItem item, string name)
{
Outlook.UserProperty property = item.UserProperties[name];
if (property != null)
{
return property.Value;
}
throw new KeyNotFoundException(string.Format("UserProperty name={0} not found", name));
}
}
public class Poco
{
public string Pre
{
get;
set;
}
public string Post
{
get;
set;
}
}
}
Thank you for any replies
Upvotes: 0
Views: 255
Reputation: 66276
Outlook Object Model cannot be used on a secondary thread within a COM addin. Outlook 2016 will raise an exception as soon as it detects an OOM object being accessed on a secondary thread.
Upvotes: 1