Efficiently Getting and Setting Custom Properties in a Contact Folder in Outlook 2010

Office Visual How To

Summary:  Learn how to efficiently get and set custom properties for contact items in Microsoft Outlook 2010. Specifically, this article describes how to use the GetProperties(Object) and SetProperties(Object, Object) methods of the PropertyAccessor object to get and set many properties at the same time.

Applies to: Office 2010 | Outlook 2010 | Visual Studio

Published:  October 2010

Provided by:  Jonathan Fingold, SDK Bridge, LLC

Overview

The GetProperties(Object) and SetProperties(Object, Object) methods of the PropertyAccessor object enable you to efficiently get and set multiple built-in and custom properties of a Microsoft Outlook 2010 item with one method call. This article uses a C# Outlook add-in project to show how to use these two methods.

Code It

The sample provided with this Visual How To uses a C# Outlook 2010 add-in project written in Microsoft Visual Studio 2010. To use this sample, you should already be familiar with C# and creating custom forms and add-ins for Outlook.

Files

The add-in contains four noteworthy classes:

  • ThisAddin—Initializes the add-in, provides access to the Opportunites contacts folder, and creates the Opportunites folder if it does not already exist.

  • OpportunitiesRibbon—Provides an add-in ribbon that contains two buttons (Import Data and Export Data), and imports and exports data between Outlook and specified data files.

  • Constants—Defines property references for the built-in and custom properties that this add-in imports and exports, and contains other constants, such as the display names and data types of the properties.

  • ContactItemHelper—Handles the file operations associated with importing and exporting the property data.

In addition to the Visual Studio project files, the code sample .zip file also contains a Sales Opportunity.oft file. You will need to publish the Sales Opportunity form to your Personal Forms Library, specifying the name "Sales Opportunity".

Start-up and the Opportunities Folder

The ThisAddin class defines an OpportunitiesFolder property, for getting the Folder object for the Opportunities folder, and a CreateOpportunitiesFolder method, for creating the folder if it does not already exist. When creating the Opportunities folder, the add-in uses the UserDefinedProperties object to define five user properties for the five custom properties that correspond to the fields on the Sales Opportunity form.

When Outlook starts, it calls the add-in's startup method, ThisAddIn_Startup. The add-in checks whether the folder exists, and creates one if it does not already exist, as shown in the following code.

private static readonly string opportunitiesFolderName =
    "Opportunities";

/// <summary>Initializes the add-in.</summary>
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
    Outlook.Folder opportunitiesFolder = OpportunitiesFolder;

    // Create the contacts folder titled Opportunities,
    // if the folder does not already exist.
    if (opportunitiesFolder == null)
    {
        opportunitiesFolder = CreateOpportunitiesFolder();
    }
}

/// <summary>Gets the root contacts folder.</summary>
private Outlook.Folder RootContactsFolder
{
    get
    {
        // Use the MAPI namespace to get the root contacts folder.
        Outlook.NameSpace outlookNameSpace =
            this.Application.GetNamespace("MAPI");

        return outlookNameSpace.GetDefaultFolder(
            Outlook.OlDefaultFolders.olFolderContacts)
            as Outlook.Folder;
    }
}

/// <summary>Gets the contacts folder titled Opportunities.</summary>
internal Outlook.Folder OpportunitiesFolder
{
    get
    {
        // Get and return the Opportunities contacts folder,
        // if it exists.
        foreach (Outlook.Folder folder in RootContactsFolder.Folders)
        {
            if (folder.Name.Equals(opportunitiesFolderName))
            {
                return folder;
            }
        }

        // Otherwise, return null.
        return null;
    }
}

/// <summary>Creates a contacts folder titled Opportunities.</summary>
private Outlook.Folder CreateOpportunitiesFolder()
{
    try
    {
        // Create the folder.
        Outlook.Folder opportunitiesFolder =
            RootContactsFolder.Folders.Add(opportunitiesFolderName,
            Outlook.OlDefaultFolders.olFolderContacts)
            as Outlook.Folder;

        // Set the default form for this folder to the custom form,
        // so that new contacts in this folder will use this form.

        // Set the custom form message class property on the folder.
        opportunitiesFolder.PropertyAccessor.SetProperty(
            Constants.MessageClassID,
            Constants.MessageClassOpportunities);

        // Set the custom form display name property on the folder.
        opportunitiesFolder.PropertyAccessor.SetProperty(
            Constants.MessageClassDisplayNameID,
            Constants.SalesFormDisplayName);

        DefineUserProperties(opportunitiesFolder);

        // Make the Opportunities folder visible.
        opportunitiesFolder.GetExplorer().NavigationPane.
            CurrentModule.Visible = true;

        return opportunitiesFolder;
    }
    catch (Exception ex)
    {
        // Dump exception information to the debugger.
        Debug.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);
        return null;
    }
}

/// <summary>Defines the sales user properties for a folder.</summary>
/// <param name="opportunitiesFolder">The folder in which to define the 
/// sales user properties.</param>
private void DefineUserProperties(Outlook.Folder opportunitiesFolder)
{
    opportunitiesFolder.UserDefinedProperties.Add(
        Constants.encounterDateDisplayName,
        Outlook.OlUserPropertyType.olDateTime,
        true, Outlook.OlFormatDateTime.OlFormatDateTimeLongDayDate);

    opportunitiesFolder.UserDefinedProperties.Add(
        Constants.purchaseEstimateDisplayName,
        Outlook.OlUserPropertyType.olCurrency,
        true, Outlook.OlFormatCurrency.olFormatCurrencyDecimal);

    opportunitiesFolder.UserDefinedProperties.Add(
        Constants.salesRepDisplayName, Outlook.OlUserPropertyType.olText);

    opportunitiesFolder.UserDefinedProperties.Add(
        Constants.salesValuedisplayName,
        Outlook.OlUserPropertyType.olInteger,
        true, Outlook.OlFormatInteger.olFormatIntegerPlain);

    opportunitiesFolder.UserDefinedProperties.Add(
        Constants.tradeShowDisplayName,
        Outlook.OlUserPropertyType.olYesNo,
        true, Outlook.OlFormatYesNo.olFormatYesNoTrueFalse);
}

Getting and Setting Property Values

The OpportunitiesRibbon class contains the event handlers for the Import Data and Export Data buttons.

The paths for the import and export data files are defined at the top of the file, as shown in the following code. The import data file (ContactData.csv) is included in the code sample .zip file. The export data file is created or overwritten when the add-in exports data. Update the paths, as necessary.

private static readonly string dataImportFile =
    @"D:\ImportData\ContactData.csv";
private static readonly string dataExportFile =
    @"D:\ExportedData\UpdatedContactData.csv";

To import the contact data, the add-in reads the contents of the data file and creates a list of contact item helper objects that temporarily store the data. (In production code, you would probably want to create each contact item as you receive the data from the data source.)

For each contact, the add-in creates a Sales Opportunity contact item, uses the new item's PropertyAccessor object to set all the property values at the same time, and then saves the new contact, as shown in the following code.

The DumpErrorsFromSetProperties method sends information about any errors encountered to the debug output.

private static void ImportData()
{
    List<ContactItemHelper> itemData =
        ContactItemHelper.LoadDataFromFile(dataImportFile);
    foreach (ContactItemHelper contactData in itemData)
    {
        // Create a new sales opportunity contact.
        Outlook.ContactItem newContact =
            Globals.ThisAddIn.OpportunitiesFolder.Items.Add(
            Constants.MessageClassOpportunities)
            as Outlook.ContactItem;

        // Set properties for the contact item.
        object[] errors = newContact.PropertyAccessor.SetProperties(
            ContactItemHelper.PropertyReferences,
            contactData.PropertyValues);

        // Check any errors returned by the SetProperties method.
        DumpErrorsFromSetProperties(contactData, errors);

        newContact.Save();
    }
}

The following code shows how the add-in exports the contact data by iterating through the contact items, compiling a list of the values from the contact items to export, and then writing the data to a .csv file.

For each contact item, the add-in uses the item's PropertyAccessor to get all the specified property values at the same time.

private static void ExportData()
{
    // Get a list of the values from the items to export.
    List<object[]> exportItems = new List<object[]>();
    foreach (Outlook.ContactItem item in
        Globals.ThisAddIn.OpportunitiesFolder.Items)
    {
        // Ignore items that are not Sales Opportunity contact items.
        if (item.MessageClass != Constants.MessageClassOpportunities)
            continue;

        // Get values for specified properties from the item's
        // PropertyAccessor.
        object[] values = item.PropertyAccessor.GetProperties(
            ContactItemHelper.PropertyReferences);

        try
        {
            // Convert the encounter date value from a UTC time to 
            // a local time.
            values[5] =
                item.PropertyAccessor.UTCToLocalTime(
                (DateTime)values[5]);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Unable to convert encounter date " +
                "from UTC to local time for contact {0}: {1}",
                item.FullName, ex.Message);
        }

        // Add the values to the list of contact item data.
        exportItems.Add(values);
    }

    // Export the items to a data file.
    ContactItemHelper.ExportData(dataExportFile, exportItems);
}

Referencing Properties by Their Namespace

The GetProperties(Object) and SetProperties(Object, Object) methods reference properties by their namespace. The following code adds property references for the built-in and custom properties as read-only fields in the static Constants class.

The display names for the properties are also included in the Constants class.

Most of the built-in properties use the MAPI proptag namespace.

// Display names for built-in properties:

public static readonly string lastNameDisplayName = "Last Name";
public static readonly string firstNameDisplayName = "First Name";
public static readonly string companyNameDisplayName = "Company";
public static readonly string customerIDDisplayName = "CustomerID";
public static readonly string primaryEmailDisplayName = "email";

// References to built-in properties by their namespaces:

/// <summary>A reference to the last name property by its namespace.</summary>
public static readonly string lastNamePropRef =
    "https://schemas.microsoft.com/mapi/proptag/0x3A11001F";

/// <summary>A reference to the first name property by its namespace.</summary>
public static readonly string firstNamePropRef =
    "https://schemas.microsoft.com/mapi/proptag/0x3A06001F";

/// <summary>A reference to the company name property by its namespace.</summary>
public static readonly string companyNamePropRef =
    "https://schemas.microsoft.com/mapi/proptag/0x3A16001F";

/// <summary>A reference to the customer ID property by its namespace.</summary>
public static readonly string customerIDProperRef =
    "https://schemas.microsoft.com/mapi/proptag/0x3A4A001F";

/// <summary>A reference to the primary email property by its namespace.</summary>
public static readonly string primaryEmailPropRef = "urn:schemas:contacts:email1";

The custom properties, shown in the following code, use the MAPI string namespace.

Note

For the custom properties, the Office contacts GUID is part of the schema name. Also included in this class are display names for the properties. These display names identify the data in the import and export files.

// Display names for custom properties:

public static readonly string encounterDateDisplayName =
    "Encounter Date";
public static readonly string purchaseEstimateDisplayName =
    "Purchase Estimate";
public static readonly string salesRepDisplayName = "Sales Rep";
public static readonly string salesValuedisplayName = "Sales Value";
public static readonly string tradeShowDisplayName = "Trade Show";

// References to custom properties by their namespaces:

private static readonly string baseUri =
    "https://schemas.microsoft.com/mapi/string";
private static readonly string contactItemsGuid =
    "{00020329-0000-0000-C000-000000000046}";

/// <summary>A reference to the encounter date property by its namespace.
/// </summary>
public static readonly string encounterDatePropRef =
    string.Format("{0}/{1}/{2}", baseUri, contactItemsGuid,
    encounterDateDisplayName);

/// <summary>A reference to the purchase estimate property by its namespace.
/// </summary>
public static readonly string purchaseEstimatePropRef =
    string.Format("{0}/{1}/{2}", baseUri, contactItemsGuid,
    purchaseEstimateDisplayName);

/// <summary>A reference to the sales rep property by its namespace.</summary>
public static readonly string salesRepPropRef =
    string.Format("{0}/{1}/{2}", baseUri, contactItemsGuid, salesRepDisplayName);

/// <summary>A reference to the sales value property by its namespace.</summary>
public static readonly string salesValuePropRef =
    string.Format("{0}/{1}/{2}", baseUri, contactItemsGuid, salesValuedisplayName);

/// <summary>A reference to the trade show property by its namespace.</summary>
public static readonly string tradeShowPropRef =
    string.Format("{0}/{1}/{2}", baseUri, contactItemsGuid, tradeShowDisplayName);

The custom properties span several of the possible data types, shown in the following code, and this class provides a GetDataType method that returns the type information for each property.

private static readonly Dictionary<string, Type> dataTypes;

static Constants()
{
    dataTypes = new Dictionary<string, Type>();
    dataTypes[lastNameDisplayName] = typeof(string);
    dataTypes[firstNameDisplayName] = typeof(string);
    dataTypes[companyNameDisplayName] = typeof(string);
    dataTypes[primaryEmailDisplayName] = typeof(string);
    dataTypes[customerIDDisplayName] = typeof(string);
    dataTypes[customerIDProperRef] = typeof(string);
    dataTypes[encounterDateDisplayName] = typeof(DateTime);
    dataTypes[purchaseEstimateDisplayName] = typeof(double);
    dataTypes[salesRepDisplayName] = typeof(string);
    dataTypes[salesValuedisplayName] = typeof(int);
    dataTypes[tradeShowDisplayName] = typeof(bool);
}

/// <summary>Returns the managed data type for a given property.
/// </summary>
/// <param name="fieldName">The property name.</param>
/// <returns>The data type of the property, or typeof(string) if the
/// property name is not recognized.</returns>
internal static Type GetDataType(string fieldName)
{
    if (dataTypes.ContainsKey(fieldName))
    {
        return dataTypes[fieldName];
    }
    else
    {
        return typeof(string);
    }
}

Read It

When Outlook starts, the add-in checks for an Opportunities contacts folder, and creates one if it does not already exist. The add-in enables you to load data from a data file to populate contact items in the Opportunities folder. This folder will contain contacts that include custom sales information, which can be accessed through a custom Sales Opportunity form. The add-in uses the SetProperties(Object, Object) method of the PropertyAccessor object to add the contact information to each new contact, and this method enables you to set built-in and custom properties at the same time.

The PropertyAccessor is good for this scenario. Using the PropertyAccessor object incurs some overhead, but it reduces network traffic. The GetProperties(Object) and SetProperties(Object, Object) methods perform faster than using either the UserProperties object when you have to get or set multiple custom properties at the item level, or the UserDefinedProperties object to get or set multiple custom properties at the folder level. Also, one call to the GetProperties(Object) or SetProperties(Object, Object) method is much faster at setting multiple properties than calling the GetProperty(String) or SetProperty(String, Object) method multiple times.

In this article, the Sales Opportunities form is published to the Personal Forms Library. The message class for the default form for the Opportunities folder is set to IPM.Contact.Sales Opportunity. Also, when new contacts are created from the imported data, they are created as an IPM.Contact.Sales Opportunity type, and the Sales Opportunity form is set as the default form that is displayed for these items.

Property Types

The custom properties in this example span several of the possible data types:

  • Encounter Date contains a date value, which is DateTime in C#.

  • Purchase Estimate contains a currency value, which is double in C#.

  • Sales Rep contains a string value.

  • Sales Value contains a 32-bit signed integer value, and represents the rank of the contact.

  • Trade Show contains a Boolean value, which is bool in C#.

Setting Properties

The SetProperties(Object, Object) method takes two parameters:

  • The SchemaNames parameter contains an array of property references for each property to set.

  • The Values parameter contains an array of the values to set on each property in the SchemaNames parameter.

If the SetProperties(Object, Object) method encounters an error assigning a value to a property, its return value contains an array that contains an HRESULT that corresponds to each property that generated an error. If SetProperties(Object, Object) encounters no errors, its return value is an empty array.

Getting Properties

The GetProperties(Object) method takes one parameter, SchemaNames, which contains an array of schema names for each property to get.

The return value contains an array of the values for each property in the SchemaNames parameter.

If the GetProperties(Object) method encounters an error reading a value from a property, its return value contains an HRESULT value instead of the value for that particular property.

Considerations

There are several things to consider when you use the GetProperties(Object) and SetProperties(Object, Object) methods:

  • Outlook stores time values in UTC format. The PropertyAccessor object provides methods for converting between UTC time and local time.

  • Outlook stores binary values as an array of bytes. The PropertyAccessor provides methods for converting between binary arrays and strings.

  • The PropertyAccessor does not support some MAPI property types, such as the object property type.

For more information, see the Explore It section.

See It

Watch the video

> [!VIDEO https://www.microsoft.com/en-us/videoplayer/embed/0a6243c6-01f5-4419-b39c-382a81981460]

Length: 09:57

Click to grab code

Grab the Code

Explore It