1024programmer Asp.Net My journey of Office Outlook plug-in development (1)

My journey of Office Outlook plug-in development (1)

My journey of Office Outlook plug-in development (1)

Purpose

Develop a plug-in that can synchronize Outlook email address book information.

Plan

  1. VSTO add-in
  2. COM add-ins

The VSTO add-in supports Outlook starting from the 2010 version.
VSTO 4.0 supports Outlook 2010 and later versions, so you can write the code once and run it on different versions.

COM add-ins are very dependent on the .NET Framework framework and Office version, as you will understand when I talk about it later.

VSTO add-in

VSTO, the full name is Visual Studio Tools for Office, conducts professional development of Office in Microsoft’s Visual Studio platform. VSTO is a replacement for VBA. Using this toolkit makes it easier to develop Office applications. VSTO can also use many functions in the Visual Studio development environment.
VSTO relies on the .NET Framework and cannot run on .net core or .net 5+ platforms.

Create VSTO program

Use Visual Studio 2013 to create a new project. If you use a newer version, you will most likely not find it. Because it was removed. For example, the minimum Outlook 2013 add-in created by Visual Studio 2019

Office/SharePoint -> .Net Framework 4 -> Outlook 2010 Add-in

Afterwards we will get a project structure like this

Open ThisAddIn.cs

using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Xml.Linq;
 using Outlook = Microsoft.Office.Interop.Outlook;
 using Office = Microsoft.Office.Core;
 using Microsoft.Office.Interop.Outlook;
 using System.Windows.Forms;
 using System.Data;
 using System.Data.SqlClient;
 using System.IO;
 using System.Threading;
 using System.Collections;

 namespace ContactsSynchronization
 {
     public partial class ThisAddIn
     {
        
         private void ThisAddIn_Startup(object sender, System.EventArgs e)
         {
             //Execute when Outlook starts
             MessageBox.Show("Hello VSTO!");
         }

         private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
         {
             //Executed when Outlook is closed
         }

         #region VSTO generated code

         /// 
         /// Designer supports required methods - don't
         /// Use the code editor to modify the contents of this method.
         /// 
         private void InternalStartup()
         {
             //Bind lifecycle function
             this.Startup += new System.EventHandler(ThisAddIn_Startup);
             this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
         }

         #endregion
     }
 }
 

Start it and give it a try

At this point we have set up the project, but before writing the code, why not get to know each of the objects in Outlook.

Understand common objects in VSTO

Microsoft Documentation
https://learn.microsoft.com/zh-cn/dotnet/api/microsoft.office.interop.outlook.application?view=outlook-pia

Common types

  • MAPIFolder represents a folder in Outlook
  • ContactItem represents a contact
  • DistListItem represents a group in a contact folder
  • OlDefaultFolders gets the enumeration of default file types
  • OlItemType gets the enumeration of folder sub-item types

Most of the functions and properties we use are mounted on the global instance Application.

Application.Session;// Session instance
 Application.Version;//DLL dynamic link library version
 Application.Name;//Application name
 

Application.Session Session instance can obtain most of the status and data of Outlook. Such as folders, contacts, emails, etc.

Outlook folder structure

Outlook distinguishes user data according to email accounts, that is, each email account has an independent inbox, contacts, etc.

Outlook default folder structure

Get the default contact folder of the first email account

Application.Session.Stores.Cast.First().GetDefaultFolder(OlDefaultFolders.olFolderContacts);
 

Get Outlook status information

Get contact information

MAPIFolder folder = Application.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);//Get the default address book folder
 IEnumerable contactItems = folder.Items.OfType(); // Get the sub-items under the folder, OfType only gets the contacts
 foreach (ContactItem it in contactItems)
 {
     // Get various information of the contact
     string fullName = it.FullName;
     // Note that if you modify the contact information here, Save() will not take effect.
 }
 

Add contact

MAPIFolder folder = Application.Session.GetDefaultFolder(OlDefaultFolders.olFolderContacts);//Get the default contact folder
 ContactItem contact = folder.Items.Add(OlItemType.olContactItem);//Add contact item
 //Set various information
 contact.FirstName = "三";
 contact.LastName = "Zhang";
 contact.Email1Address = "[email protected]";
 // store contacts
 contact.Save();
 

Delete Contact

Microsoft.Office.Interop.Outlook.MAPIFolder deletedFolder = application.Session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);//Default contact folder
 int count = deletedFolder.Items.Count; // Get the number of sub-items, including contacts and groups
 for (int i = count; i > 0; i--)// Traverse and delete
 {
     deletedFolder.Items.Remove(i);
 }
 

finished product code

using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Xml.Linq;
 using Outlook = Microsoft.Office.Interop.Outlook;
 using Office = Microsoft.Office.Core;
 using Microsoft.Office.Interop.Outlook;
 using System.Windows.Forms;
 using System.Data;
 using System.Data.SqlClient;
 using System.IO;
 using System.Threading;
 using System.Collections;

 namespace ContactsSynchronization
 {
     public partial class ThisAddIn
     {
        

         private void ThisAddIn_Startup(object sender, System.EventArgs e)
         {
             OperatorContact operatorInstance = new OperatorContact(this.Application);
             operatorInstance.Task();
         }

         private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
         {
         }

         #region VSTO generated code

         /// 
         /// Designer supports required methods - don't
         /// Use the code editor to modify the contents of this method.
         /// 
         private void InternalStartup()
         {
             this.Startup += new System.EventHandler(ThisAddIn_Startup);
             this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
         }

         #endregion
     }

     class OperatorContact
     {
         public OperatorContact(Microsoft.Office.Interop.Outlook.Application application)
         {
             this.application = application;
         }

         Microsoft.Office.Interop.Outlook.Application application = null; // outlook program instance

         private static string addressBookName = "Tangshi Group Address Book"; // Address book name

         private Microsoft.Office.Interop.Outlook.MAPIFolder addressBookFolder = null; // Address book folder instance

         public void Task()
         {
             new Thread(Run).Start();
         }

         /// 
         /// Open a new thread to perform the task, do not block the original thread
         /// 
         private void Run()
         {
             try
             {
                 if (NeedUpdate())
                 {
                     addressBookFolder = getAddressBookFolder();//Create an address book with overwriting
                     List remoteContacts = readRemoteContacts();//Read the remote mailbox address book
                     if (remoteContacts == null) return;
                     Adjust(remoteContacts);//Adjust contacts and groups
                     updateClientVersion();//Update local address book version number
                 }
             }
             catch (System.Exception ex)
             {
                 const string path = @"C:\TONS\email-plugin-error.log";
                 FileInfo fileInfo = new FileInfo(path);
                 long length = 0;
                 if (fileInfo.Exists && fileInfo.Length != 0) length = fileInfo.Length / 1024 / 1024;
                 if (length <= 3) File.AppendAllText(path, ex.Message + "\r\n");
                 else File.WriteAllText(path, ex.Message + "\r\n");
             }
         }

         /// 
         /// Create address book by overwriting
         /// 
         /// Address book folder instance
         private Microsoft.Office.Interop.Outlook.MAPIFolder getAddressBookFolder()
         {
             // Get the enumerator of the address book folder of the user's first PST file
             IEnumerator en = application.Session.Stores.Cast().First()
                 .GetDefaultFolder(OlDefaultFolders.olFolderContacts)
                 .Folders.GetEnumerator();
             bool exits = false;
             Microsoft.Office.Interop.Outlook.MAPIFolder folder = null;

             // Traverse the folder
             while (en.MoveNext()) {
                 Microsoft.Office.Interop.Outlook.MAPIFolder current = (Microsoft.Office.Interop.Outlook.MAPIFolder)en.Current;
                 if (current.Name == addressBookName) {
                     exits = true;
                     folder = current;
                 }
             }

             if (!exits)
              {
                  //Create Tangshi Group address book and map it into address book format
                  Microsoft.Office.Interop.Outlook.MAPIFolder newFolder = application.Session.Stores.Cast().First()
                           .GetDefaultFolder(OlDefaultFolders.olFolderContacts)
                           .Folders.Add(addressBookName);
                  newFolder.ShowAsOutlookAB = true;//Set to "Contacts" folder
                  return newFolder;
              }
              else {
                 // Return to the existing simultaneous group address book folder and delete the contents inside
                 int count = folder.Items.Count;
                 for (int i = count; i > 0; i--)
                 {
                     folder.Items.Remove(i);
                 }
                 Microsoft.Office.Interop.Outlook.MAPIFolder deletedFolder = application.Session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);
                 count = deletedFolder.Items.Count;
                 for (int i = count; i > 0; i--)
                 {
                     deletedFolder.Items.Remove(i);
                 }
                 return folder;
              }
         }



         /// 
         /// Update the local version of Bronze Beard Record
         /// 
         private void updateClientVersion()
         {
             String path = @"C:\TONS\email-plugin-version.conf";
             string version = getRemoteVersion();
             if (!File.Exists(path))
             {
                 File.WriteAllText(path,version);
             }
             else {
                 File.WriteAllText(path, version);
             }
         }

         /// 
         /// Determine whether update is needed
         /// 
         /// boolean value
         private bool NeedUpdate()
         {
             string remoteVersion = getRemoteVersion();
             if (remoteVersion == null) return false;
             string clientVersion = getClientVersion();
             return !(clientVersion == remoteVersion);
         }

         /// 
         /// Read the server's address book version
         /// 
         /// Address book version
         private string getRemoteVersion()
         {
             List<Dictionary> items = SelectList(
                 "SELECT TOP(1) [version] FROM TonsOfficeA..VersionControl WHERE applicationID = N'EmailContact'"
                 , "Server=192.168.2.1;Database=TonsOfficeA;uid=sa;pwd=dsc");
             if (items == null) return null;
             return items[0]["version"].ToString();
         }

         /// 
         /// Get the local address book version
         /// 
         /// Address book version
         private string getClientVersion()
         {
             String path = @"C:\TONS\email-plugin-version.conf";
             if (!File.Exists(path)) return null;
             return File.ReadAllText(path);
         }

         /// 
         /// Read the remote address book
         /// 
         /// Contact instance collection
         private List readRemoteContacts()
         {
             List remoteContacts = new List();
             List<Dictionary> items =
                 SelectList(
                     "select [emailAddress],[firstName],[lastName],[companyName],[department],[_group] as 'group',[jobTitle] from [TonsOfficeA].[dbo].[EmailContacts]",
                     "Server=192.168.2.1;Database=TonsOfficeA;uid=sa;pwd=dsc");
             items.ForEach(it =>
             {
                 Contact contact = new Contact();
                 contact.email1Address = it["emailAddress"].ToString();
                 contact.firstName = it["firstName"].ToString();
                 contact.lastName = it["lastName"].ToString();
                 contact.companyName = it["companyName"].ToString();
                 contact.department = it["department"].ToString();
                 if (it["jobTitle"] != null) contact.jobTitle = it["jobTitle"].ToString();
                 contact.groups = it["group"].ToString().Split(',').ToList();
                 remoteContacts.Add(contact);
             });
             return remoteContacts;
         }

         /// 
         /// Execute select statement
         /// 
         /// select statementMicrosoft.Office.Interop.Outlook.MAPIFolder folder = null;

             // Traverse the folder
             while (en.MoveNext()) {
                 Microsoft.Office.Interop.Outlook.MAPIFolder current = (Microsoft.Office.Interop.Outlook.MAPIFolder)en.Current;
                 if (current.Name == addressBookName) {
                     exits = true;
                     folder = current;
                 }
             }

             if (!exits)
              {
                  //Create Tangshi Group address book and map it into address book format
                  Microsoft.Office.Interop.Outlook.MAPIFolder newFolder = application.Session.Stores.Cast().First()
                           .GetDefaultFolder(OlDefaultFolders.olFolderContacts)
                           .Folders.Add(addressBookName);
                  newFolder.ShowAsOutlookAB = true;//Set to "Contacts" folder
                  return newFolder;
              }
              else {
                 // Return to the existing simultaneous group address book folder and delete the contents inside
                 int count = folder.Items.Count;
                 for (int i = count; i > 0; i--)
                 {
                     folder.Items.Remove(i);
                 }
                 Microsoft.Office.Interop.Outlook.MAPIFolder deletedFolder = application.Session.GetDefaultFolder(OlDefaultFolders.olFolderDeletedItems);
                 count = deletedFolder.Items.Count;
                 for (int i = count; i > 0; i--)
                 {
                     deletedFolder.Items.Remove(i);
                 }
                 return folder;
              }
         }



         /// 
         /// Update the local version of Bronze Beard Record
         /// 
         private void updateClientVersion()
         {
             String path = @"C:\TONS\email-plugin-version.conf";
             string version = getRemoteVersion();
             if (!File.Exists(path))
             {
                 File.WriteAllText(path,version);
             }
             else {
                 File.WriteAllText(path, version);
             }
         }

         /// 
         /// Determine whether update is needed
         /// 
         /// boolean value
         private bool NeedUpdate()
         {
             string remoteVersion = getRemoteVersion();
             if (remoteVersion == null) return false;
             string clientVersion = getClientVersion();
             return !(clientVersion == remoteVersion);
         }

         /// 
         /// Read the server's address book version
         /// 
         /// Address book version
         private string getRemoteVersion()
         {
             List<Dictionary> items = SelectList(
                 "SELECT TOP(1) [version] FROM TonsOfficeA..VersionControl WHERE applicationID = N'EmailContact'"
                 , "Server=192.168.2.1;Database=TonsOfficeA;uid=sa;pwd=dsc");
             if (items == null) return null;
             return items[0]["version"].ToString();
         }

         /// 
         /// Get the local address book version
         /// 
         /// Address book version
         private string getClientVersion()
         {
             String path = @"C:\TONS\email-plugin-version.conf";
             if (!File.Exists(path)) return null;
             return File.ReadAllText(path);
         }

         /// 
         /// Read the remote address book
         /// 
         /// Contact instance collection
         private List readRemoteContacts()
         {
             List remoteContacts = new List();
             List<Dictionary> items =
                 SelectList(
                     "select [emailAddress],[firstName],[lastName],[companyName],[department],[_group] as 'group',[jobTitle] from [TonsOfficeA].[dbo].[EmailContacts]",
                     "Server=192.168.2.1;Database=TonsOfficeA;uid=sa;pwd=dsc");
             items.ForEach(it =>
             {
                 Contact contact = new Contact();
                 contact.email1Address = it["emailAddress"].ToString();
                 contact.firstName = it["firstName"].ToString();
                 contact.lastName = it["lastName"].ToString();
                 contact.companyName = it["companyName"].ToString();
                 contact.department = it["department"].ToString();
                 if (it["jobTitle"] != null) contact.jobTitle = it["jobTitle"].ToString();
                 contact.groups = it["group"].ToString().Split(',').ToList();
                 remoteContacts.Add(contact);
             });
             return remoteContacts;
         }

         /// 
         /// Execute select statement
         /// 
         /// select statement
         /// Database link statement
         /// List<Dictionary>results
         /// 
         public List<Dictionary> SelectList(string sql, string connection)
         {
             if (sql == null || connection == null || sql == "" || connection == "")
                 throw new System.Exception("No SQL statement or Connection link statement was passed in");
             List<Dictionary> list = new List<Dictionary>();
             SqlConnection conn = new SqlConnection(connection);
             SqlCommand cmd = new SqlCommand(sql, conn);
             try
             {
                 conn.Open();
                 SqlDataReader sqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
                 if (sqlDataReader == null) return null;
                 while (sqlDataReader.Read())
                 {
                     int count = sqlDataReader.FieldCount;
                     if (count <= 0) continue;
                     Dictionary map = new Dictionary();
                     for (int i = 0; i < count; i++)
                     {
                         string name = sqlDataReader.GetName(i);
                         object value = sqlDataReader.GetValue(i);
                         map.Add(name, value);
                     }
                     list.Add(map);
                 }

                 conn.Close();
                 return list;
             }
             catch(System.Exception)
             {
                 conn.Close();
                 return null;
             }
         }

         /// 
         /// Adjust address book contacts
         /// 
         /// Source of contact information imported from the database
         private void Adjust(List remoteContacts)
         {
             // Make a copy to create a group
             List distListItems = new List();
             Contact[] tempItems = new Contact[remoteContacts.Count];
             remoteContacts.CopyTo(tempItems);
             tempItems.ToList().ForEach(it =>
             {
                 it.groups.ForEach(g =>
                 {
                     Contact con = new Contact
                     {
                         firstName = it.firstName,
                         lastName = it.lastName,
                         email1Address = it.email1Address,
                         companyName = it.companyName,
                         department = it.department,
                         group=g
                     };
                     distListItems.Add(con);
                 });
             });
           
             // Add contacts
             remoteContacts.ForEach(it =>
             {

                 ContactItem contact = addressBookFolder.Items.Add();
                 contact.FirstName = it.firstName;
                 contact.LastName = it.lastName;
                 contact.Email1Address = it.email1Address;
                 contact.CompanyName = it.companyName;
                 contact.Department = it.department;
                 if (it.jobTitle != null) contact.JobTitle = it.jobTitle;
                 contact.Save();
             });

             //Group by group and create a group to save
             List contactStores = distListItems
                 .GroupBy(it => it.group)
                 .Select(it => new ContactStore { group = it.Key, contacts = it.ToList() })
                 .ToList();
             contactStores.ForEach(it =>
             {
                 DistListItem myItem = addressBookFolder.Items.Add(OlItemType.olDistributionListItem);
                 it.contacts.ForEach(contact =>
                 {
                     string id = String.Format("{0}{1}({2})", contact.lastName, contact.firstName,
                         contact.email1Address);
                     Recipient recipient = application.Session.CreateRecipient(id);
                     recipient.Resolve();
                     myItem.AddMember(recipient);
                 });
                 myItem.DLName = it.group;
                 myItem.Save();
             });
         }

         struct Contact
         {
             public string email1Address; // Email
             public string firstName; // Last name
             public string lastName; // name
             public string companyName; // company name
             public string department; //Department name
             public List groups; // Grouped collection
             public string group; // group
             public string jobTitle; // Job title
         }

         struct ContactStore
         {
             public string group;
             public List contacts;
         }
     }
 }
 

Packaging, installation and uninstallation

Right click on the project -> Publish

After publishing you will see a structure like this

Click setup.exe to install
Uninstallation requires VSTOInstaller.exe

"C:\Program Files (x86)\Common Files\microsoft shared\VSTO\10.0\VSTOInstaller.exe" /u "Your .vsto file directory"
 

/// Database link statement
/// List<Dictionary>results
///
public List<Dictionary> SelectList(string sql, string connection)
{
if (sql == null || connection == null || sql == “” || connection == “”)
throw new System.Exception(“No SQL statement or Connection link statement was passed in”);
List<Dictionary> list = new List<Dictionary>();
SqlConnection conn = new SqlConnection(connection);
SqlCommand cmd = new SqlCommand(sql, conn);
try
{
conn.Open();
SqlDataReader sqlDataReader = cmd.ExecuteReader(CommandBehavior.CloseConnection);
if (sqlDataReader == null) return null;
while (sqlDataReader.Read())
{
int count = sqlDataReader.FieldCount;
if (count <= 0) continue;
Dictionary map = new Dictionary();
for (int i = 0; i < count; i++)
{
string name = sqlDataReader.GetName(i);
object value = sqlDataReader.GetValue(i);
map.Add(name, value);
}
list.Add(map);
}

conn.Close();
return list;
}
catch(System.Exception)
{
conn.Close();
return null;
}
}

///

/// Adjust address book contacts
///

/// Source of contact information imported from the database
private void Adjust(List remoteContacts)
{
// Make a copy to create a group
List distListItems = new List();
Contact[] tempItems = new Contact[remoteContacts.Count];
remoteContacts.CopyTo(tempItems);
tempItems.ToList().ForEach(it =>
{
it.groups.ForEach(g =>
{
Contact con = new Contact
{
firstName = it.firstName,
lastName = it.lastName,
email1Address = it.email1Address,
companyName = it.companyName,
department = it.department,
group=g
};
distListItems.Add(con);
});
});

// Add contacts
remoteContacts.ForEach(it =>
{

ContactItem contact = addressBookFolder.Items.Add();
contact.FirstName = it.firstName;
contact.LastName = it.lastName;
contact.Email1Address = it.email1Address;
contact.CompanyName = it.companyName;
contact.Department = it.department;
if (it.jobTitle != null) contact.JobTitle = it.jobTitle;
contact.Save();
});

//Group by group and create a group to save
List contactStores = distListItems
.GroupBy(it => it.group)
.Select(it => new ContactStore { group = it.Key, contacts = it.ToList() })
.ToList();
contactStores.ForEach(it =>
{
DistListItem myItem = addressBookFolder.Items.Add(OlItemType.olDistributionListItem);
it.contacts.ForEach(contact =>
{
string id = String.Format(“{0}{1}({2})”, contact.lastName, contact.firstName,
contact.email1Address);
Recipient recipient = application.Session.CreateRecipient(id);
recipient.Resolve();
myItem.AddMember(recipient);
});
myItem.DLName = it.group;
myItem.Save();
});
}

struct Contact
{
public string email1Address; // Email
public string firstName; // Last name
public string lastName; // name
public string companyName; // company name
public string department; //Department name
public List groups; // Grouped collection
public string group; // group
public string jobTitle; // Job title
}

struct ContactStore
{
public string group;
public List contacts;
}
}
}

Packaging, installation and uninstallation

Right click on the project -> Publish

After publishing you will see a structure like this

Click setup.exe to install
Uninstallation requires VSTOInstaller.exe

"C:\Program Files (x86)\Common Files\microsoft shared\VSTO\10.0\VSTOInstaller.exe" /u "Your .vsto file directory"
 

=”Packaging, installation and uninstallation”>Packaging, installation and uninstallation

Right click on the project -> Publish

After publishing you will see a structure like this

Click setup.exe to install
Uninstallation requires VSTOInstaller.exe

"C:\Program Files (x86)\Common Files\microsoft shared\VSTO\10.0\VSTOInstaller.exe" /u "Your .vsto file directory"
 
This article is from the internet and does not represent1024programmerPosition, please indicate the source when reprinting:https://www.1024programmer.com/my-journey-of-office-outlook-plug-in-development-1/

author: admin

Previous article
Next article

Leave a Reply

Your email address will not be published. Required fields are marked *

Contact Us

Contact us

181-3619-1160

Online consultation: QQ交谈

E-mail: [email protected]

Working hours: Monday to Friday, 9:00-17:30, holidays off

Follow wechat
Scan wechat and follow us

Scan wechat and follow us

Follow Weibo
Back to top
首页
微信
电话
搜索